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
43 changes: 22 additions & 21 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,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 +294,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 +320,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 +357,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 @@ -395,30 +400,25 @@ tasks:
echo "DOCKER_USERNAME and 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: |

echo "Creating Docker registry secret..."
DOCKER_SERVER="${DOCKER_SERVER:-https://index.docker.io/v1/}"
DOCKER_EMAIL="${DOCKER_EMAIL:-dev@helios.io}"

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_PASSWORD" \
--docker-email="$DOCKER_EMAIL" \
--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 +457,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
5 changes: 5 additions & 0 deletions apps/portal/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,14 @@ catalog:
rules:
- allow: [Template]

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

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

rules:
- allow: [Template]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
apiVersion: app.helios.io/v1alpha1
kind: HeliosApp
metadata:
name: ${{ values.name }}
namespace: default
spec:
owner: ${{ values.owner }}
description: "Spring Boot service: ${{ values.name }}"
gitRepo: ${{ values.sourceRepo }}
gitBranch: main
imageRepo: ${{ values.image }}
gitopsRepo: ${{ values.gitopsRepo }}
gitopsPath: ${{ values.name }}
pipelineName: from-code-to-cluster
webhookSecret: git-credentials-${{ values.name }}
port: ${{ values.port }}
testCommand: "gradle test"
components:
- name: ${{ values.name }}
type: web-service
properties:
image: ${{ values.image }}:latest
port: ${{ values.port }}
replicas: 1
traits:
- type: service
properties:
port: ${{ values.port }}
- type: database
properties:
dbType: ${{ values.databaseType | default("postgres") }}
dbName: ${{ values.name | replace('-', '_') }}_db
version: "16"
storage: "1Gi"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Database credentials (injected by Helios Operator in Kubernetes)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=${{ values.name | replace('-', '_') }}_db
DB_USER=postgres
DB_PASS=postgres

# Application
SERVER_PORT=${{ values.port }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ──────────────────────────────────────────────────────
# .gitignore — Spring Boot + Gradle
# ──────────────────────────────────────────────────────

# Gradle
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar
!gradle/wrapper/gradle-wrapper.properties

# IDE
.idea/
*.iml
*.ipr
*.iws
.vscode/
.project
.classpath
.settings/
out/

# OS
.DS_Store
Thumbs.db

# Environment
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ──────────────────────────────────────────────────────────
# Multi-stage Dockerfile for Spring Boot (Gradle)
# ──────────────────────────────────────────────────────────
# Stage 1 — Build
# Uses the official Gradle image with JDK 21. We copy sources
# into the container and run a full Gradle build. The resulting
# fat JAR is extracted for the next stage.
#
# Stage 2 — Runtime
# Uses a minimal Eclipse Temurin JRE-only Alpine image to keep
# the final image small (~200 MB vs ~800 MB with a full JDK).
#
# Kaniko Compatibility
# This Dockerfile avoids features that are problematic for
# Kaniko (e.g. mounting secrets, multi-platform builds).
# The build is fully self-contained.
# ──────────────────────────────────────────────────────────

# ---- Build Stage ----
FROM gradle:8.13-jdk21 AS builder

WORKDIR /workspace

# Copy Gradle wrapper & build scripts first to leverage Docker
# layer caching — dependencies are only re-downloaded when these
# files change.
COPY build.gradle settings.gradle ./
COPY gradle ./gradle/

# Download dependencies in a separate layer for caching
RUN gradle dependencies --no-daemon || true

# Copy source code and build
COPY src ./src/
RUN gradle bootJar --no-daemon -x test

# ---- Production Stage ----
FROM eclipse-temurin:21-jre-alpine AS production

# Create a non-root user for security
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copy the fat JAR from the builder stage
COPY --from=builder /workspace/build/libs/app.jar ./app.jar

# Switch to non-root user
USER appuser

EXPOSE ${{ values.port }}

# Use exec form so the JVM receives signals (SIGTERM) correctly
# for graceful shutdown in Kubernetes.
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", "app.jar"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.5'
id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.helios'
version = '0.0.1-SNAPSHOT'

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

repositories {
mavenCentral()
}

dependencies {
// Spring Boot starters
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// PostgreSQL driver
runtimeOnly 'org.postgresql:postgresql'

// Testing
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'com.h2database:h2'
}

tasks.named('test') {
useJUnitPlatform()
}

// Produce a reproducible, plain JAR name for the Dockerfile
bootJar {
archiveFileName = 'app.jar'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{ values.name }}
description: ${{ values.description | dump }}
annotations:
gitea.org/repo-url: http://localhost:3030/${{ values.owner }}/${{ values.name }}
backstage.io/techdocs-ref: dir:.
backstage.io/kubernetes-id: ${{ values.name }}
backstage.io/kubernetes-label-selector: app.kubernetes.io/name=${{ values.name }}
backstage.io/kubernetes-namespace: default
janus-idp.io/tekton: ${{ values.name }}
tekton.dev/ci-cd: "true"
argocd/app-name: ${{ values.name }}-argocd
spec:
type: service
lifecycle: production
owner: ${{ values.owner }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ──────────────────────────────────────────────────────────
# docker-compose.yml — Local Development Environment
# ──────────────────────────────────────────────────────────
# Usage:
# docker compose up -d # Start PostgreSQL
# ./gradlew bootRun # Start the Spring Boot app
# docker compose down -v # Tear down (remove volumes)
#
# This file starts ONLY the database. The Spring Boot app is
# expected to run on the host (via IDE or Gradle) so that hot
# reload and debugging work seamlessly.
# ──────────────────────────────────────────────────────────

services:
postgres:
image: postgres:16-alpine
container_name: ${{ values.name }}-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${{ values.name | replace('-', '_') }}_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

volumes:
pgdata:
driver: local
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = '${{ values.name }}'
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.helios.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* Entry point for the ${{ values.name }} Spring Boot application.
*
* <p>The {@code @SpringBootApplication} annotation enables:
* <ul>
* <li>Component scanning within the {@code com.helios.app} package</li>
* <li>Auto-configuration of Spring Data JPA, Actuator, etc.</li>
* <li>Property source resolution from {@code application.yml}</li>
* </ul>
*/
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Loading