Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ GITEA_ADMIN_PASS=helios123
# -----------------------------------------------------------------------------
DOCKER_SERVER=https://index.docker.io/v1/
DOCKER_USERNAME=
DOCKER_PASSWORD=
DOCKER_EMAIL=
DOCKER_TOKEN=

# -----------------------------------------------------------------------------
# Git Author (optional, used by operator for GitOps commits)
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ apps/portal/packages/backend/*.sqlite
.claude/

.idea

# mise
mise.toml
16 changes: 8 additions & 8 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -396,22 +396,22 @@ tasks:
desc: Create Docker registry secret and link to pipeline SA
cmds:
- cmd: |
if [ -z "$DOCKER_USERNAME" ] || [ -z "$DOCKER_PASSWORD" ]; then
echo "DOCKER_USERNAME and DOCKER_PASSWORD must be set in .env" >&2
if [ -z "$DOCKER_USERNAME" ] || { [ -z "$DOCKER_TOKEN" ] && [ -z "$DOCKER_PASSWORD" ]; }; then
echo "DOCKER_USERNAME and either DOCKER_TOKEN or DOCKER_PASSWORD must be set in .env" >&2
exit 1
fi

echo "Creating Docker registry secret..."
platforms: [linux, darwin]
- cmd: |
DOCKER_SERVER="${DOCKER_SERVER:-https://index.docker.io/v1/}"
DOCKER_EMAIL="${DOCKER_EMAIL:-dev@helios.io}"
DOCKER_SECRET_PASS="${DOCKER_TOKEN:-$DOCKER_PASSWORD}"

kubectl create secret docker-registry docker-credentials \
--docker-server="$DOCKER_SERVER" \
--docker-username="$DOCKER_USERNAME" \
--docker-password="$DOCKER_PASSWORD" \
--docker-email="$DOCKER_EMAIL" \
--docker-password="$DOCKER_SECRET_PASS" \
--dry-run=client -o yaml | kubectl apply -f -

platforms: [linux, darwin]
- cmd: |
if kubectl get sa pipeline >/dev/null 2>&1; then
kubectl patch sa pipeline -p '{"secrets": [{"name": "docker-credentials"}]}'
else
Expand Down
2 changes: 1 addition & 1 deletion apps/operator/.github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ jobs:
- name: Run linter
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20
with:
version: v2.11.3
version: v2.11.4
2 changes: 1 addition & 1 deletion apps/operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.20.1
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
GOLANGCI_LINT_VERSION ?= v2.11.3
GOLANGCI_LINT_VERSION ?= v2.11.4

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
Expand Down
4 changes: 4 additions & 0 deletions apps/operator/api/v1alpha1/heliosapp_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ type HeliosAppSpec struct {
// +optional
TestCommand string `json:"testCommand,omitempty"`

// TestImage is the container image used to execute testCommand (e.g. "node:24")
// +optional
TestImage string `json:"testImage,omitempty"`

// Env variables for the application
// +optional
Env []corev1.EnvVar `json:"env,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions apps/operator/config/crd/bases/app.helios.io_heliosapps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ spec:
testCommand:
description: TestCommand is the command to run tests (e.g. "npm test")
type: string
testImage:
description: TestImage is the container image used to execute testCommand
(e.g. "node:24")
type: string
triggerType:
default: gitea-push
description: TriggerType is the type of trigger to use for the pipeline
Expand Down
12 changes: 6 additions & 6 deletions apps/operator/internal/controller/database/reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestReconcileSecrets(t *testing.T) {
dbProps := map[string]any{
"dbType": "postgres",
"dbName": "mydb",
"version": "16",
"version": "18.3",
}
dbPropsJSON, _ := json.Marshal(dbProps)

Expand Down Expand Up @@ -193,7 +193,7 @@ func TestReconcileInstances(t *testing.T) {
dbProps := map[string]any{
"dbType": "postgres",
"dbName": "my_custom_db",
"version": "16",
"version": "18.3",
"storage": "2Gi",
}
dbPropsJSON, _ := json.Marshal(dbProps)
Expand Down Expand Up @@ -348,7 +348,7 @@ func TestReconcileInstances(t *testing.T) {
Replicas: func() *int32 { r := int32(1); return &r }(),
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Name: "postgres", Image: "postgres:15"}},
Containers: []corev1.Container{{Name: "postgres", Image: "postgres:18.3"}},
},
},
Comment on lines 349 to 353

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReconcileInstances uses the database trait version ("16" in this test app) to set the StatefulSet image, but this test expects postgres:18. Align the expected image (and the existingSts seed image) with the trait version, or update the trait version if the default is being bumped.

Copilot uses AI. Check for mistakes.
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{
Expand Down Expand Up @@ -379,8 +379,8 @@ func TestReconcileInstances(t *testing.T) {
if err != nil {
t.Fatalf("failed to get updated StatefulSet: %v", err)
}
if got := updatedSts.Spec.Template.Spec.Containers[0].Image; got != "postgres:16" {
t.Fatalf("expected image postgres:16, got %s", got)
if got := updatedSts.Spec.Template.Spec.Containers[0].Image; got != "postgres:18.3" {
t.Fatalf("expected image postgres:18.3, got %s", got)
}

updatedSvc := &corev1.Service{}
Expand Down Expand Up @@ -425,7 +425,7 @@ func TestReconcileInstances(t *testing.T) {
}

func TestReconcileInjection(t *testing.T) {
dbProps := map[string]any{"dbType": "postgres", "dbName": "mydb", "version": "16"}
dbProps := map[string]any{"dbType": "postgres", "dbName": "mydb", "version": "18.3"}
dbPropsJSON, _ := json.Marshal(dbProps)

app := &appv1alpha1.HeliosApp{
Expand Down
8 changes: 4 additions & 4 deletions apps/operator/internal/controller/database/resources_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func TestExtractDatabaseTraits(t *testing.T) {
dbProps := map[string]any{
"dbType": "postgres",
"dbName": "mydb",
"version": "16",
"version": "18.3",
}
dbPropsJSON, _ := json.Marshal(dbProps)

Expand Down Expand Up @@ -248,7 +248,7 @@ func TestExtractDatabaseTraits(t *testing.T) {
func TestGenerateDatabaseStatefulSet(t *testing.T) {
sts, err := GenerateDatabaseStatefulSet(
"test-ns", "my-app-db", "my-app-db-secret",
"my_custom_db", "16", "2Gi", 5432,
"my_custom_db", "18.3", "2Gi", 5432,
)

if err != nil {
Expand Down Expand Up @@ -283,8 +283,8 @@ func TestGenerateDatabaseStatefulSet(t *testing.T) {
}

container := containers[0]
if container.Image != "postgres:16" {
t.Errorf("Expected image %q, got %q", "postgres:16", container.Image)
if container.Image != "postgres:18.3" {
t.Errorf("Expected image %q, got %q", "postgres:18.3", container.Image)
}

if len(container.Ports) != 1 || container.Ports[0].ContainerPort != 5432 {
Expand Down
2 changes: 1 addition & 1 deletion apps/operator/internal/controller/database/traits.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const (
UsernameCharset = "abcdefghijklmnopqrstuvwxyz0123456789"

DatabaseTraitType = "database"
DefaultPostgresVersion = "16"
DefaultPostgresVersion = "18.3"
DefaultPostgresPort = 5432
DefaultDatabaseStorage = "1Gi"
PostgresDataPath = "/var/lib/postgresql/data"
Expand Down
1 change: 1 addition & 0 deletions apps/operator/internal/controller/tekton/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func MapCRDToTektonInput(app *appv1alpha1.HeliosApp) cueModel.TektonInput {
Replicas: int(app.Spec.Replicas),
Port: int(app.Spec.Port),
TestCommand: app.Spec.TestCommand,
TestImage: app.Spec.TestImage,
DockerSecret: "docker-credentials",
ArgoCDNamespace: app.Spec.ArgoCDNamespace,
ArgoCDProject: app.Spec.ArgoCDProject,
Expand Down
4 changes: 3 additions & 1 deletion apps/operator/internal/controller/tekton/pipelinerun.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ func GeneratePipelineRun(heliosApp *appv1alpha1.HeliosApp, pipelineName string)
serviceAccountName := cmp.Or(heliosApp.Spec.ServiceAccount, "default")
gitOpsSecretRef := cmp.Or(heliosApp.Spec.GitOpsSecretRef, "helios-gitops-bot")
argoNS := cmp.Or(heliosApp.Spec.ArgoCDNamespace, "argocd")
testImage := cmp.Or(heliosApp.Spec.TestImage, "node:24")

params := make([]any, 0, 17)
params := make([]any, 0, 18)
params = append(params,
map[string]any{"name": "app-repo-url", "value": shared.RewriteGiteaURL(heliosApp.Spec.GitRepo)},
map[string]any{"name": "app-repo-revision", "value": appRepoRevision},
Expand All @@ -41,6 +42,7 @@ func GeneratePipelineRun(heliosApp *appv1alpha1.HeliosApp, pipelineName string)
map[string]any{"name": "replicas", "value": fmt.Sprintf("%d", heliosApp.Spec.Replicas)},
map[string]any{"name": "port", "value": fmt.Sprintf("%d", heliosApp.Spec.Port)},
map[string]any{"name": "test-command", "value": heliosApp.Spec.TestCommand},
map[string]any{"name": "test-image", "value": testImage},
map[string]any{"name": "argocd-namespace", "value": argoNS},
map[string]any{"name": "argocd-app-name", "value": heliosApp.Name + "-argocd"},
)
Expand Down
2 changes: 1 addition & 1 deletion apps/operator/internal/cue/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func TestEngine_RenderWithDatabaseTrait(t *testing.T) {
Properties: map[string]any{
"dbType": "postgres",
"dbName": "my_custom_db",
"version": "16",
"version": "18.3",
},
},
},
Expand Down
1 change: 1 addition & 0 deletions apps/operator/internal/cue/tekton.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type TektonInput struct {

// === TESTING ===
TestCommand string `json:"testCommand,omitempty"`
TestImage string `json:"testImage,omitempty"`

// === SECRETS ===
DockerSecret string `json:"dockerSecret,omitempty"`
Expand Down
2 changes: 1 addition & 1 deletion apps/operator/tekton/pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ spec:
- name: test-command
default: "" # Empty => no tests (no-op)
- name: test-image
default: "node:20" # Default image for tests; can be overridden per app
default: "node:24" # Default image for tests; can be overridden per app
- name: argocd-namespace
default: "argocd"
- name: argocd-app-name
Expand Down
22 changes: 13 additions & 9 deletions apps/portal/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ backend:
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
# Default Helmet Content-Security-Policy values can be removed by setting the key to false
cors:
origin: http://localhost:3000
origin:
- http://localhost:3000
- http://127.0.0.1:3000
methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
credentials: true
# This is for local development only, it is not recommended to use this in production
Expand Down Expand Up @@ -60,7 +62,7 @@ proxy:
# Local dev: Overridden in app-config.local.yaml
endpoints:
'/argocd/api':
target: http://argocd-server.argocd.svc.cluster.local/api/v1/
target: https://127.0.0.1:8080/api/v1/
changeOrigin: true
secure: false
headers:
Expand Down Expand Up @@ -132,14 +134,21 @@ catalog:
rules:
- allow: [Template]

# .NET Web API Template
- type: file
target: ../../examples/dotnet-template/template.yaml
rules:
- allow: [Template]

# Spring Boot Template
- type: file
target: ../../examples/spring-boot-template/template.yaml
rules:
- allow: [Template]

# PostgREST Template - Instant REST API over PostgreSQL
- type: file
target: ../../examples/postgrest-template/template.yaml

rules:
- allow: [Template]

Expand Down Expand Up @@ -172,12 +181,7 @@ kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: 'config'
clusters:
- url: https://kubernetes.default.svc
name: in-cluster
authProvider: 'serviceAccount'
skipTLSVerify: true
- type: 'localKubectlProxy'
# Required for Tekton visibility - enables fetching PipelineRuns and TaskRuns
customResources:
- group: 'tekton.dev'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ spec:
value: ${{ values.name }}-argocd
# (Optional override – can be omitted to use the pipeline default)
# - name: test-image
# value: node:20
# value: node:24
workspaces:
- name: source-workspace
volumeClaimTemplate:
Expand Down
14 changes: 14 additions & 0 deletions apps/portal/examples/dotnet-template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# .NET Web API Backstage Template

This template scaffolds an ASP.NET Core Web API that is ready for Helios GitOps deployment.

Generated source repository includes:

- ASP.NET Core Web API project targeting .NET 10 (runtime 10.0.5, SDK 10.0.202)
- Multi-stage Dockerfile
- docker-compose setup for local development with PostgreSQL 18.3
- Environment-driven database configuration using `DB_HOST`, `DB_USER`, `DB_PASSWORD`, and `DB_NAME`

Generated GitOps repository includes:

- HeliosApp manifest with web-service + database trait
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
apiVersion: app.helios.io/v1alpha1
kind: HeliosApp
metadata:
name: ${{ values.name }}
namespace: default
spec:

description: "Managed by Helios Operator"
owner: ${{ values.owner }}

imageRepo: ${{ values.dockerOrg }}/${{ values.repoName }}
gitRepo: "${{ values.sourceRepo }}"
gitopsRepo: ${{ values.gitopsRepo }}
gitopsPath: "apps/${{ values.name }}"
port: ${{ values.port }}
replicas: 1
testCommand: ${{ values.testCommand }}
testImage: ${{ values.testImage | dump }}

webhookSecret: "git-credentials-${{ values.name }}"
gitopsSecretRef: "git-credentials-${{ values.name }}"

components:
- name: ${{ values.name }}-backend
type: web-service
properties:
image: ${{ values.dockerOrg }}/${{ values.repoName }}:main
port: ${{ values.port }}
replicas: 1
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
traits:
- type: service
properties:
port: ${{ values.port }}
targetPort: ${{ values.port }}
- type: database
properties:
dbType: ${{ values.dbType | dump }}
dbName: ${{ values.dbName | dump }}
version: ${{ values.dbVersion | dump }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bin/
obj/
.git/
.vscode/
*.user
*.suo
11 changes: 11 additions & 0 deletions apps/portal/examples/dotnet-template/content/source/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Database credentials (injected by Helios Operator)
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=${{ values.name }}-db

# Legacy compatibility fallback used by some templates/operators
DB_PASS=postgres

# Application
PORT=${{ values.port }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
.vs/
*.user
*.suo
Loading
Loading