Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f69be3a
feat(portal): add .NET 10 Web API backstage template with postgres wi…
PhuocHoan Apr 12, 2026
4ea9c93
chore: update prereq scripts and operator test expectations
PhuocHoan Apr 12, 2026
95e1f1a
portal: refine template descriptions and local CORS origins
PhuocHoan Apr 12, 2026
7cf6573
Enhance database environment variable injection in Deployment (#67)
NgocAnhDo26 Apr 12, 2026
f67ca74
Users/hpn/implement postg rest auto api template (#65)
nghiaz160904 Apr 12, 2026
e7946e9
Upgrade dotnet template and postgres defaults to latest
PhuocHoan Apr 13, 2026
cf86730
Merge branch 'features/sprint4-backend-templates-db-injection' into u…
PhuocHoan Apr 13, 2026
fb2bfff
feat: add cross-platform support (Windows & Linux) for bootstrap proc…
PhamHoangKha1403 Apr 14, 2026
18cc2e1
Merge base branch and resolve template/setup conflicts
PhuocHoan Apr 15, 2026
2edfa6a
Update dotnet template to SDK 10.0.202 and add PR68 evidence guide
PhuocHoan Apr 15, 2026
1a68e88
Remove PR68 evidence guide from tracked files
PhuocHoan Apr 15, 2026
2f1779e
fix tekton and portal local dev integration
PhuocHoan Apr 15, 2026
8663db2
fix portal prettier formatting
PhuocHoan Apr 15, 2026
c53892c
feat(postgrest-template): Remove obsolete files and enhance template …
nghiaz160904 Apr 9, 2026
9a91d2e
feat(postgrest-template): Enhance PostgREST template with detailed RE…
nghiaz160904 Apr 9, 2026
3ce52d2
feat(postgrest-template): Remove obsolete GitOps files and update val…
nghiaz160904 Apr 12, 2026
b85b094
feat: Implement database migration trigger for PostgREST
nghiaz160904 Apr 12, 2026
1aedf76
temp: enhance PostgREST template with default values and new actions …
nghiaz160904 Apr 15, 2026
a0db3a4
Add initial migration
nghiaz160904 Apr 16, 2026
edd5a5a
feat: Enhance database migration support and improve webhook handling
nghiaz160904 Apr 16, 2026
ea40c07
chore: remove initial schema migration file
nghiaz160904 Apr 16, 2026
89ff59f
feat: Add database secret reference and validation for HeliosApp conf…
nghiaz160904 Apr 18, 2026
c30b5b9
Merge pull request #68 from helios-platform-team/users/hph/impl/templ…
NgocAnhDo26 Apr 18, 2026
8a5ef55
fix: Update secret validation to exclude database secrets due to auto…
nghiaz160904 Apr 19, 2026
37ac092
feat: Add database secret reference to WebhookIngress configuration
nghiaz160904 Apr 19, 2026
aa28e11
fix: Add databaseSecretRef to Tekton schema for improved secret manag…
nghiaz160904 Apr 19, 2026
b70dd0f
Merge pull request #70: branch 'bug/user/hpn/postgrest-template-fail…
VH3956 Apr 19, 2026
17539f6
setup hasura-graphql-template
VH3956 Apr 5, 2026
dfd936b
comment's review fix + change of workflow
VH3956 Apr 15, 2026
e0c011b
fix gitops namespace
VH3956 Apr 20, 2026
6ef3a7b
feat: Enhance Hasura GraphQL template with database secret reference …
nghiaz160904 Apr 20, 2026
25360f8
adding Docker workflow
VH3956 Apr 20, 2026
917bf8f
fix: Add new steps in taskfile to resolve startup problem, fix duplic…
hoangphuc841 Apr 21, 2026
eeba8db
fix: update application catalog metadata and fix an issue with compon…
hoangphuc841 Apr 21, 2026
7f837e9
fix: resolve db-migrate trigger and postgrest template issues
hoangphuc841 Apr 21, 2026
6cb1c63
- fix(crd): remove hardcoded 'api-db-secret' kubebuilder default from
hoangphuc841 Apr 21, 2026
2641c6a
update gitosPath + readme
VH3956 Apr 21, 2026
f6b8f1b
fix(test): updates tests for the recent changes
hoangphuc841 Apr 21, 2026
02e186d
chore: regenerate the crd from the go type annotations to match CI ch…
hoangphuc841 Apr 21, 2026
1c1fab8
fix(cue): repalce unsafe [:32] string slice with safe conditional let…
hoangphuc841 Apr 21, 2026
7fd9b97
fix(template): updated jwt references after switching to secret field
hoangphuc841 Apr 21, 2026
6bdddb2
fix(lint): extract repeated postgres string into dbTypePostgres const…
hoangphuc841 Apr 21, 2026
1c8e862
fix(template): update jwtSecret description and enforce length constr…
nghiaz160904 Apr 21, 2026
aa57f62
fix: accessing DB url + add:database migration
VH3956 Apr 22, 2026
4c363f9
fix: using PGRST_DB_URI + update cue logic for kube secret injections
VH3956 Apr 28, 2026
be601cd
fix: update manifest namespace
VH3956 Apr 28, 2026
b6faeb3
Merge pull request #71 from helios-platform-team/user/nhphuc/test-and…
NgocAnhDo26 May 3, 2026
062708c
Merge branch 'features/sprint4-backend-templates-db-injection' into u…
nghiaz160904 May 15, 2026
dd7acd2
Merge pull request #64 from helios-platform-team/users/ndvh/impl/hasu…
nghiaz160904 May 15, 2026
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
57 changes: 38 additions & 19 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ tasks:
deps: [check:env]
cmds:
- task: setup:cluster
- task: setup:coredns
- task: setup:tekton
- task: setup:tekton-pruner
- task: setup:argocd
Expand Down Expand Up @@ -93,6 +94,21 @@ tasks:
- kubectl config set-cluster k3d-{{.CLUSTER_NAME}} --server=https://localhost:6550
- kubectl cluster-info

setup:coredns:
desc: Patch CoreDNS to use robust external DNS servers instead of k3d node's resolv.conf
cmds:
- echo "Patching CoreDNS to use 8.8.8.8 and 1.1.1.1..."
- cmd: |
kubectl get configmap coredns -n kube-system -o yaml > coredns.yaml
sed 's/forward . \/etc\/resolv.conf/forward . 8.8.8.8 1.1.1.1/' coredns.yaml > coredns-patched.yaml
kubectl apply -f coredns-patched.yaml
rm coredns.yaml coredns-patched.yaml
platforms: [linux, darwin]
- cmd: |
powershell -ExecutionPolicy Bypass -Command "$coredns = kubectl get configmap coredns -n kube-system -o yaml; $coredns = $coredns -replace 'forward \. /etc/resolv\.conf', 'forward . 8.8.8.8 1.1.1.1'; $coredns | Out-File -Encoding utf8 coredns.yaml; kubectl apply -f coredns.yaml; Remove-Item coredns.yaml"
platforms: [windows]
- kubectl rollout restart -n kube-system deployment/coredns

setup:tekton:
desc: Install Tekton Pipeline, Triggers, and Interceptors
status:
Expand Down Expand Up @@ -217,6 +233,8 @@ tasks:
--set postgresql-ha.enabled=false
--set redis-cluster.enabled=false
--set redis.enabled=false
--set valkey-cluster.enabled=false
--set valkey.enabled=false
--set gitea.config.database.DB_TYPE=sqlite3
--set gitea.config.session.PROVIDER=memory
--set gitea.config.cache.ADAPTER=memory
Expand All @@ -227,15 +245,15 @@ tasks:
--set persistence.size=1Gi
--set resources.requests.memory=128Mi
--set resources.limits.memory=256Mi
platforms: [linux, darwin]
- echo "Waiting for Gitea deployment..."
- kubectl rollout status deployment/gitea -n gitea --timeout=300s

setup:gitea-token:
desc: Create Gitea API token, default org, and write to .env
cmds:
- echo "Port-forwarding Gitea for setup..."
- echo "Configuring Gitea (Cross-platform)..."
- cmd: |
echo "Port-forwarding Gitea for setup..."
kubectl port-forward -n gitea svc/gitea-http {{.GITEA_PORT}}:3000 &
PF_PID=$!
sleep 3
Expand Down Expand Up @@ -294,6 +312,8 @@ tasks:

kill $PF_PID 2>/dev/null || true
platforms: [linux, darwin]
- cmd: powershell -ExecutionPolicy Bypass -File scripts/config-gitea.ps1 -GiteaPort {{.GITEA_PORT}}
platforms: [windows]

setup:argocd-gitea:
desc: Register Gitea repository credentials with ArgoCD
Expand All @@ -318,6 +338,8 @@ tasks:
password: "$GITEA_ADMIN_PASS"
EOF
platforms: [linux, darwin]
- cmd: powershell -ExecutionPolicy Bypass -File scripts/setup-argocd-creds.ps1 -GiteaInternalHost {{.GITEA_INTERNAL_HOST}}
platforms: [windows]
- echo "ArgoCD can now access Gitea repositories."

setup:gitea-gitops-secret:
Expand Down Expand Up @@ -353,9 +375,10 @@ tasks:
for f in "$ENV_FILE" "$PORTAL_ENV"; do
update_env_var "$f" "GITOPS_SECRET_REF" "$GITOPS_SECRET_NAME"
done

echo "Created secret '$GITOPS_SECRET_NAME' and wrote GITOPS_SECRET_REF to .env files."
platforms: [linux, darwin]
- cmd: powershell -ExecutionPolicy Bypass -File scripts/setup-gitops-creds.ps1 -GiteaInternalHost {{.GITEA_INTERNAL_HOST}} -SecretName {{.GITOPS_SECRET_NAME}}
platforms: [windows]
- echo "Created secret '{{.GITOPS_SECRET_NAME}}' and wrote GITOPS_SECRET_REF to .env files."

setup:crds:
desc: Install Helios CRDs into the cluster
Expand Down Expand Up @@ -391,34 +414,29 @@ 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
platforms: [linux, darwin]
- cmd: |
powershell -Command "if ([string]::IsNullOrEmpty($env:DOCKER_USERNAME) -or [string]::IsNullOrEmpty($env:DOCKER_PASSWORD)) { Write-Error 'DOCKER_USERNAME and DOCKER_PASSWORD must be set in .env'; exit 1 }"
platforms: [windows]
- cmd: |
DOCKER_SERVER="${DOCKER_SERVER:-https://index.docker.io/v1/}"
DOCKER_SECRET_PASS="${DOCKER_TOKEN:-$DOCKER_PASSWORD}"

kubectl create secret docker-registry docker-credentials \
--docker-server=${DOCKER_SERVER:-https://index.docker.io/v1/} \
--docker-username=$DOCKER_USERNAME \
--docker-password=$DOCKER_PASSWORD \
--docker-email=${DOCKER_EMAIL:-dev@helios.io} \
--docker-server="$DOCKER_SERVER" \
--docker-username="$DOCKER_USERNAME" \
--docker-password="$DOCKER_SECRET_PASS" \
--dry-run=client -o yaml | kubectl apply -f -
platforms: [linux, darwin]
- cmd: |
powershell -Command "$server = if ([string]::IsNullOrEmpty($env:DOCKER_SERVER)) { 'https://index.docker.io/v1/' } else { $env:DOCKER_SERVER }; $email = if ([string]::IsNullOrEmpty($env:DOCKER_EMAIL)) { 'dev@helios.io' } else { $env:DOCKER_EMAIL }; kubectl create secret docker-registry docker-credentials --docker-server=$server --docker-username=$env:DOCKER_USERNAME --docker-password=$env:DOCKER_PASSWORD --docker-email=$email --dry-run=client -o yaml | kubectl apply -f -"
platforms: [windows]
- cmd: |
if kubectl get sa pipeline >/dev/null 2>&1; then
kubectl patch sa pipeline -p '{"secrets": [{"name": "docker-credentials"}]}'
else
echo "pipeline ServiceAccount not found yet; skipping patch (will be created by Tekton)"
fi
platforms: [linux, darwin]
- cmd: |
powershell -Command "kubectl get sa pipeline *> $null; if ($LASTEXITCODE -eq 0) { kubectl patch sa pipeline -p '{\"secrets\": [{\"name\": \"docker-credentials\"}]}' } else { Write-Host 'pipeline ServiceAccount not found yet; skipping patch (will be created by Tekton)' }"
- cmd: powershell -ExecutionPolicy Bypass -File scripts/setup-credentials.ps1
platforms: [windows]

setup:portal-deps:
Expand Down Expand Up @@ -457,8 +475,9 @@ tasks:

dev:portal:
desc: Run the Backstage portal with ArgoCD + kubectl proxy
dir: apps/portal
cmds:
- cmd: cd apps/portal && ./start-dev.sh
- cmd: ./start-dev.sh
platforms: [linux, darwin]
- cmd: powershell -ExecutionPolicy Bypass -File ../../scripts/start-portal.ps1 -ArgocdPort {{.ARGOCD_PORT}}
platforms: [windows]
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
15 changes: 15 additions & 0 deletions apps/operator/api/v1alpha1/heliosapp_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ type HeliosAppSpec struct {
// +kubebuilder:default="from-code-to-cluster"
PipelineName string `json:"pipelineName,omitempty"`

// TriggerType is the type of trigger to use for the pipeline
// +optional
// +kubebuilder:validation:Enum=gitea-push;db-migrate
// +kubebuilder:default="gitea-push"
TriggerType string `json:"triggerType,omitempty"`

// WebhookSecret is the name of the secret containing the Gitea webhook secret token
// +optional
// +kubebuilder:default="gitea-webhook-secret"
Expand Down Expand Up @@ -100,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 All @@ -120,6 +130,11 @@ type HeliosAppSpec struct {
// +optional
ContextSubpath string `json:"contextSubpath,omitempty"`

// DatabaseSecretRef is the name of the secret containing database credentials for migrations.
// Defaults to {appName}-db-secret if not set.
// +optional
DatabaseSecretRef string `json:"databaseSecretRef,omitempty"`

// Components define the workloads of the application
Components []Component `json:"components"`
}
Expand Down
16 changes: 16 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 @@ -100,6 +100,11 @@ spec:
contextSubpath:
description: ContextSubpath is the path where the Dockerfile is located
type: string
databaseSecretRef:
default: api-db-secret
description: DatabaseSecretRef is the name of the secret containing
database credentials for migrations
type: string
description:
description: Description of the application
type: string
Expand Down Expand Up @@ -370,6 +375,17 @@ 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
enum:
- gitea-push
- db-migrate
type: string
webhookDomain:
description: WebhookDomain is the external domain (e.g., ngrok) for
Git webhooks
Expand Down
67 changes: 61 additions & 6 deletions apps/operator/internal/controller/database/injection.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,55 @@ package database

import (
"strconv"
"strings"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
)

const dbPortEnvName = "DB_PORT"
const (
dbPortEnvName = "DB_PORT"
dbNameEnvName = "DB_NAME"
databaseURLEnvName = "DATABASE_URL"
// dbTypePostgres is the canonical DB type identifier used across the database package.
dbTypePostgres = "postgres"
// postgresDatabaseURLTemplate uses Kubernetes $(VAR) expansion from earlier env entries.
postgresDatabaseURLTemplate = "postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)"
)

// connectionURLTemplateForDBType returns a DATABASE_URL value for the given engine type.
// Only types with a defined URL layout are supported; others return ok=false.
func connectionURLTemplateForDBType(dbType string) (template string, ok bool) {
switch strings.ToLower(strings.TrimSpace(dbType)) {
case dbTypePostgres, "postgresql":
return postgresDatabaseURLTemplate, true
default:
return "", false
}
}

// databaseSecretEnvVarNames lists env vars resolved from Secret keys.
var databaseSecretEnvVarNames = []string{"DB_HOST", "DB_USER", "DB_PASS"}
// These are injected as environment variable references into application containers.
var databaseSecretEnvVarNames = []string{"DB_HOST", "DB_USER", "DB_PASS", "PGRST_DB_URI"}

// InjectDatabaseEnvVars patches a Deployment's first container to include
// DB_HOST, DB_USER, DB_PASS env vars referencing the given K8s Secret.
// The function is idempotent.
func InjectDatabaseEnvVars(deploy *appsv1.Deployment, secretName string) bool {
changed, _ := InjectDatabaseEnvVarsForContainer(deploy, secretName, "", DefaultPostgresPort)
// dbName is the logical database name (e.g. from the database trait); when empty,
// DB_NAME is not injected.
// dbType selects whether DATABASE_URL is set (only types with a known URL template, e.g. postgres).
func InjectDatabaseEnvVars(deploy *appsv1.Deployment, secretName, dbName, dbType string) bool {
changed, _ := InjectDatabaseEnvVarsForContainer(deploy, secretName, "", DefaultPostgresPort, dbName, dbType)
return changed
}

// InjectDatabaseEnvVarsForContainer patches a Deployment container to include
// DB_HOST, DB_USER, DB_PASS env vars referencing the given K8s Secret, plus a
// literal DB_PORT value.
// literal DB_PORT value. When dbName is non-empty, it sets DB_NAME. DATABASE_URL
// is added only when dbType has a defined connection string template (see connectionURLTemplateForDBType).
// If preferredContainerName is not found, it falls back to the first container.
// Returns (changed, exactMatch).
func InjectDatabaseEnvVarsForContainer(deploy *appsv1.Deployment, secretName, preferredContainerName string, dbPort int32) (bool, bool) {
func InjectDatabaseEnvVarsForContainer(deploy *appsv1.Deployment, secretName, preferredContainerName string, dbPort int32, dbName, dbType string) (bool, bool) {
if len(deploy.Spec.Template.Spec.Containers) == 0 {
return false, false
}
Expand Down Expand Up @@ -99,9 +124,39 @@ func InjectDatabaseEnvVarsForContainer(deploy *appsv1.Deployment, secretName, pr
changed = true
}

if dbName != "" {
if ensureLiteralEnvVar(container, dbNameEnvName, dbName) {
changed = true
}
if urlTpl, ok := connectionURLTemplateForDBType(dbType); ok {
if ensureLiteralEnvVar(container, databaseURLEnvName, urlTpl) {
changed = true
}
}
}

return changed, exactMatch
}

func ensureLiteralEnvVar(container *corev1.Container, name, value string) bool {
for i := range container.Env {
if container.Env[i].Name != name {
continue
}
if container.Env[i].Value == value && container.Env[i].ValueFrom == nil {
return false
}
container.Env[i].Value = value
container.Env[i].ValueFrom = nil
return true
}
container.Env = append(container.Env, corev1.EnvVar{
Name: name,
Value: value,
})
return true
}

func selectTargetContainerIndex(containers []corev1.Container, preferredContainerName string) (int, bool) {
if len(containers) == 0 {
return -1, false
Expand Down
Loading
Loading