diff --git a/.github/workflows/platform-validate.yml b/.github/workflows/platform-validate.yml index 649c8d9..3ea1580 100644 --- a/.github/workflows/platform-validate.yml +++ b/.github/workflows/platform-validate.yml @@ -122,7 +122,7 @@ jobs: - name: Setup conftest run: | - CONFTEST_VERSION=$(curl -sL https://api.github.com/repos/open-policy-agent/conftest/releases/latest | grep tag_name | cut -d'"' -f4) + CONFTEST_VERSION="v0.68.2" wget -q "https://github.com/open-policy-agent/conftest/releases/download/${CONFTEST_VERSION}/conftest_${CONFTEST_VERSION#v}_Linux_x86_64.tar.gz" -O /tmp/conftest.tar.gz tar xzf /tmp/conftest.tar.gz -C /usr/local/bin/ conftest @@ -167,7 +167,7 @@ jobs: - name: Setup conftest run: | - CONFTEST_VERSION=$(curl -sL https://api.github.com/repos/open-policy-agent/conftest/releases/latest | grep tag_name | cut -d'"' -f4) + CONFTEST_VERSION="v0.68.2" wget -q "https://github.com/open-policy-agent/conftest/releases/download/${CONFTEST_VERSION}/conftest_${CONFTEST_VERSION#v}_Linux_x86_64.tar.gz" -O /tmp/conftest.tar.gz tar xzf /tmp/conftest.tar.gz -C /usr/local/bin/ conftest diff --git a/Makefile b/Makefile index 7576623..80a40c2 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ MAKEFLAGS += --no-print-directory CLUSTER_NAME ?= platform-demo NAMESPACE ?= argocd KIND_IMG ?= kindest/node:v1.30.0 +AWS_ACCOUNT_ID ?= 944684220857 +AWS_REGION ?= eu-north-1 +IMAGE_REPO ?= $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com/app .PHONY: help help: @@ -66,11 +69,13 @@ deploy: @echo "=== Deploying simple-app ===" helm template simple-app-dev standardized-path/app \ -f platform/apps/dev/values.yaml \ + --set image.repository=$(IMAGE_REPO) \ | kubectl apply -f - 2>&1 | grep -v 'unchanged' || true @echo "=== Deploying app-b ===" helm template app-b-dev standardized-path/app \ -f platform/apps/app-b/dev/values.yaml \ + --set image.repository=$(IMAGE_REPO) \ | kubectl apply -f - 2>&1 | grep -v 'unchanged' || true @echo "=== App status ===" diff --git a/backstage/app-config.yaml b/backstage/app-config.yaml new file mode 100644 index 0000000..2868d12 --- /dev/null +++ b/backstage/app-config.yaml @@ -0,0 +1,53 @@ +app: + title: Internal Developer Platform + baseUrl: http://backstage.platform.example.com + +backend: + baseUrl: http://backstage.platform.example.com + listen: + port: 7007 + cors: + origin: http://backstage.platform.example.com + +organization: + name: Platform Engineering + +auth: + environment: development + providers: + guest: {} + +integrations: + github: + - host: github.com + apps: [] + +catalog: + import: + entityFilename: catalog-info.yaml + locations: + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/catalog-info.yaml + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/standardized-path/app/catalog-info.yaml + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/platform/apps/simple-app/catalog-info.yaml + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/platform/apps/app-b/catalog-info.yaml + +scaffolder: + locations: + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/backstage/templates/new-tenant-app/template.yaml + +kubernetes: + serviceLocatorMethod: + type: multiTenant + clusterLocatorMethods: + - type: config + clusters: + - name: in-cluster + url: https://kubernetes.default.svc + authProvider: serviceAccount + skipTLSVerify: true + skipMetricsLookup: true diff --git a/backstage/catalog-ui.html b/backstage/catalog-ui.html new file mode 100644 index 0000000..62b4dbf --- /dev/null +++ b/backstage/catalog-ui.html @@ -0,0 +1,77 @@ + +Backstage Catalog + +

Backstage Catalog

+

Internal Developer Platform — Entity overview

+ +
+
9
Entities
+
1
Templates
+
0
Errors
+
+
Entities
+
NameKindOwnerSystemDescription
simple-app-infra-codeComponentplatform-teamplatformInternal Developer Platform for EKS and OpenShift
platformSystemplatform-teamInternal Developer Platform providing golden path, GitOps, and observability
platform-teamGroupPlatform engineering team
app-developersGroupApplication development teams
all-catalog-filesLocationDiscovers all catalog-info.yaml files across the repository
app-chartComponentplatform-teamplatformGolden path Helm chart — the tenant contract for deploying services on the platform
tenant-contractAPIplatform-teamplatformContract defining what app teams provide and what the platform guarantees
simple-appComponentapp-developersplatformDemo tenant application deployed via the platform golden path
app-bComponentapp-developersplatformSecond tenant application proving contract reusability across dev/stage/prod
+
Dependency Graph
simple-app-infra-code
Component
⊢ providestenant-contract
app-chart
Component
⊢ providestenant-contract
simple-app
Component
→ dependsOnapp-chart
app-b
Component
→ dependsOnapp-chart
+
Scaffolder Templates

New Tenant Application

Scaffold a new application using the platform's golden path Helm chart

Parameters:
[
+  {
+    "appName": {
+      "title": "Application Name",
+      "type": "string",
+      "description": "Unique name for the application (e.g., app-c)",
+      "pattern": "^[a-z][a-z0-9-]*$"
+    },
+    "description": {
+      "title": "Description",
+      "type": "string",
+      "description": "Purpose of the application"
+    },
+    "owner": {
+      "title": "Owner",
+      "type": "string",
+      "description": "Backstage Group or User that owns this app",
+      "default": "app-developers",
+      "ui:field": "OwnerPicker",
+      "ui:options": {
+        "allowedKinds": [
+          "Group"
+        ]
+      }
+    }
+  },
+  {
+    "awsAccountId": {
+      "title": "AWS Account ID",
+      "type": "string",
+      "description": "12-digit AWS account number for ECR",
+      "pattern": "^\\d{12}$"
+    },
+    "awsRegion": {
+      "title": "AWS Region",
+      "type": "string",
+      "description": "AWS region for ECR (e.g., eu-north-1, us-east-1)",
+      "default": "eu-north-1"
+    },
+    "port": {
+      "title": "Application Port",
+      "type": "integer",
+      "default": 8080
+    }
+  }
+]
Steps:
  fetch-skeleton: Fetch Skeleton (fetch:template)
+  publish: Publish to GitHub (publish:github)
+  register: Register in Backstage (catalog:register)
+
Catalog Files
📄 catalog-info.yaml
📄 standardized-path/app/catalog-info.yaml
📄 platform/apps/simple-app/catalog-info.yaml
📄 platform/apps/app-b/catalog-info.yaml
📄 backstage/templates/new-tenant-app/template.yaml
\ No newline at end of file diff --git a/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/dev/values.yaml b/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/dev/values.yaml new file mode 100644 index 0000000..ddf742a --- /dev/null +++ b/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/dev/values.yaml @@ -0,0 +1,26 @@ +replicaCount: 1 + +image: + repository: ${{ imageRepo }}/${{ appName }} + tag: "1.0.0-dev.1" + +ingress: + enabled: true + hosts: + - host: ${{ appName }}-dev.example.com + paths: + - path: / + pathType: Prefix + +env: + - name: ENVIRONMENT + value: "dev" + - name: PORT + value: "${{ port }}" + +externalSecret: + enabled: true + data: + - secretKey: db-password + remoteRef: + key: dev/${{ appName }}/db-password diff --git a/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/prod/values.yaml b/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/prod/values.yaml new file mode 100644 index 0000000..a1e0d26 --- /dev/null +++ b/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/prod/values.yaml @@ -0,0 +1,26 @@ +replicaCount: 3 + +image: + repository: ${{ imageRepo }}/${{ appName }} + tag: "1.0.0" + +ingress: + enabled: true + hosts: + - host: ${{ appName }}.example.com + paths: + - path: / + pathType: Prefix + +env: + - name: ENVIRONMENT + value: "prod" + - name: PORT + value: "${{ port }}" + +externalSecret: + enabled: true + data: + - secretKey: db-password + remoteRef: + key: prod/${{ appName }}/db-password diff --git a/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/stage/values.yaml b/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/stage/values.yaml new file mode 100644 index 0000000..936ef7d --- /dev/null +++ b/backstage/templates/new-tenant-app/skeleton/platform/apps/${{parameters.appName}}/stage/values.yaml @@ -0,0 +1,26 @@ +replicaCount: 2 + +image: + repository: ${{ imageRepo }}/${{ appName }} + tag: "1.0.0-rc1" + +ingress: + enabled: true + hosts: + - host: ${{ appName }}-stage.example.com + paths: + - path: / + pathType: Prefix + +env: + - name: ENVIRONMENT + value: "stage" + - name: PORT + value: "${{ port }}" + +externalSecret: + enabled: true + data: + - secretKey: db-password + remoteRef: + key: stage/${{ appName }}/db-password diff --git a/backstage/templates/new-tenant-app/template.yaml b/backstage/templates/new-tenant-app/template.yaml new file mode 100644 index 0000000..6b4d4e4 --- /dev/null +++ b/backstage/templates/new-tenant-app/template.yaml @@ -0,0 +1,132 @@ +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: new-tenant-app + title: New Tenant Application + description: Scaffold a new application using the platform's golden path Helm chart + tags: + - platform + - helm + - golden-path +spec: + owner: platform-team + type: service + + parameters: + - title: Application Details + required: + - appName + - description + - owner + properties: + appName: + title: Application Name + type: string + description: Unique name for the application (e.g., app-c) + pattern: '^[a-z][a-z0-9-]*$' + description: + title: Description + type: string + description: Purpose of the application + owner: + title: Owner + type: string + description: Backstage Group or User that owns this app + default: app-developers + ui:field: OwnerPicker + ui:options: + allowedKinds: + - Group + + - title: Container Configuration + required: + - awsAccountId + - awsRegion + properties: + awsAccountId: + title: AWS Account ID + type: string + description: 12-digit AWS account number for ECR + pattern: '^\d{12}$' + awsRegion: + title: AWS Region + type: string + description: AWS region for ECR (e.g., eu-north-1, us-east-1) + default: eu-north-1 + port: + title: Application Port + type: integer + default: 8080 + + steps: + - id: fetch-skeleton + name: Fetch Skeleton + action: fetch:template + input: + url: ./skeleton + values: + appName: ${{ parameters.appName }} + description: ${{ parameters.description }} + awsAccountId: ${{ parameters.awsAccountId }} + awsRegion: ${{ parameters.awsRegion }} + imageRepo: ${{ parameters.awsAccountId }}.dkr.ecr.${{ parameters.awsRegion }}.amazonaws.com + port: ${{ parameters.port }} + owner: ${{ parameters.owner }} + + - id: publish + name: Publish to GitHub + action: publish:github + input: + repoUrl: github.com?owner=tukue&repo=simpleAppInfraCode + branch: feature/${{ parameters.appName }} + defaultBranch: main + repoVisibility: public + + - id: register + name: Register in Backstage + action: catalog:register + input: + repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }} + catalogInfoPath: /catalog-info.yaml + + output: + links: + - title: Open in GitHub + url: ${{ steps.publish.output.remoteUrl }} + - title: Open Pull Request + url: ${{ steps.publish.output.pullRequestUrl }} + text: + - title: Next Steps + content: | + ### After Scaffolding + + 1. Add the app entries to `argocd/appsets/environments.yaml`: + ```yaml + - env: dev + app: ${{ parameters.appName }} + valuesPath: platform/apps/${{ parameters.appName }}/dev/values.yaml + - env: stage + app: ${{ parameters.appName }} + valuesPath: platform/apps/${{ parameters.appName }}/stage/values.yaml + - env: prod + app: ${{ parameters.appName }} + valuesPath: platform/apps/${{ parameters.appName }}/prod/values.yaml + ``` + + 2. Add a `catalog-info.yaml` at `platform/apps/${{ parameters.appName }}/catalog-info.yaml`: + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: ${{ parameters.appName }} + description: ${{ parameters.description }} + spec: + type: service + owner: ${{ parameters.owner }} + system: platform + dependsOn: + - component:app-chart + ``` + + 3. Open a PR to merge the new app into main. + 4. Argo CD will automatically pick up the new ApplicationSet entries. diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000..8fafa7b --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,54 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: simple-app-infra-code + description: Internal Developer Platform for EKS and OpenShift + annotations: + github.com/project-slug: tukue/simpleAppInfraCode + backstage.io/techdocs-ref: dir:. +spec: + type: repository + lifecycle: experimental + owner: platform-team + system: platform + providesApis: + - tenant-contract +--- +apiVersion: backstage.io/v1alpha1 +kind: System +metadata: + name: platform + description: Internal Developer Platform providing golden path, GitOps, and observability +spec: + owner: platform-team +--- +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: platform-team + description: Platform engineering team +spec: + type: team + profile: + displayName: Platform Team + children: [] +--- +apiVersion: backstage.io/v1alpha1 +kind: Group +metadata: + name: app-developers + description: Application development teams +spec: + type: team + profile: + displayName: App Developers + children: [] +--- +apiVersion: backstage.io/v1alpha1 +kind: Location +metadata: + name: all-catalog-files + description: Discovers all catalog-info.yaml files across the repository +spec: + targets: + - ./**/catalog-info.yaml diff --git a/platform/addons/backstage.yaml b/platform/addons/backstage.yaml new file mode 100644 index 0000000..ce4e7dc --- /dev/null +++ b/platform/addons/backstage.yaml @@ -0,0 +1,96 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: backstage + namespace: argocd +spec: + project: platform + source: + repoURL: https://backstage.github.io/charts + chart: backstage + targetRevision: 2.x + helm: + values: | + backend: + image: + repository: backstage/backstage + tag: latest + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 250m + memory: 512Mi + + service: + type: ClusterIP + port: 80 + + ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "false" + hosts: + - host: backstage.platform.example.com + paths: + - path: / + pathType: Prefix + + appConfig: + app: + title: Internal Developer Platform + baseUrl: http://backstage.platform.example.com + backend: + baseUrl: http://backstage.platform.example.com + listen: + port: 7007 + cors: + origin: http://backstage.platform.example.com + organization: + name: Platform Engineering + + auth: + environment: development + providers: + guest: {} + + integrations: + github: + - host: github.com + apps: [] + + catalog: + import: + entityFilename: catalog-info.yaml + locations: + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/catalog-info.yaml + + scaffolder: + locations: + - type: url + target: https://github.com/tukue/simpleAppInfraCode/blob/main/backstage/templates/new-tenant-app/template.yaml + + kubernetes: + serviceLocatorMethod: + type: multiTenant + clusterLocatorMethods: + - type: config + clusters: + - name: in-cluster + url: https://kubernetes.default.svc + authProvider: serviceAccount + skipTLSVerify: true + skipMetricsLookup: true + + destination: + server: https://kubernetes.default.svc + namespace: backstage + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/platform/apps/app-b/catalog-info.yaml b/platform/apps/app-b/catalog-info.yaml new file mode 100644 index 0000000..2dae5e0 --- /dev/null +++ b/platform/apps/app-b/catalog-info.yaml @@ -0,0 +1,14 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: app-b + description: Second tenant application proving contract reusability across dev/stage/prod + annotations: + github.com/project-slug: tukue/simpleAppInfraCode +spec: + type: service + lifecycle: experimental + owner: app-developers + system: platform + dependsOn: + - component:app-chart diff --git a/platform/apps/simple-app/catalog-info.yaml b/platform/apps/simple-app/catalog-info.yaml new file mode 100644 index 0000000..23a850c --- /dev/null +++ b/platform/apps/simple-app/catalog-info.yaml @@ -0,0 +1,14 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: simple-app + description: Demo tenant application deployed via the platform golden path + annotations: + github.com/project-slug: tukue/simpleAppInfraCode +spec: + type: service + lifecycle: experimental + owner: app-developers + system: platform + dependsOn: + - component:app-chart diff --git a/standardized-path/app/catalog-info.yaml b/standardized-path/app/catalog-info.yaml new file mode 100644 index 0000000..c9c83ba --- /dev/null +++ b/standardized-path/app/catalog-info.yaml @@ -0,0 +1,29 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: app-chart + description: Golden path Helm chart — the tenant contract for deploying services on the platform + annotations: + backstage.io/techdocs-ref: dir:. +spec: + type: library + lifecycle: experimental + owner: platform-team + system: platform + providesApis: + - tenant-contract +--- +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: tenant-contract + description: Contract defining what app teams provide and what the platform guarantees +spec: + type: helm-values + lifecycle: experimental + owner: platform-team + system: platform + definition: | + App Team provides: image, port, replicas, env vars, secret refs + Platform guarantees: non-root, dropped capabilities, resource boundaries, + network isolation, drift detection, immutable tags, cluster portability