├── .bootstrap ├── argocd │ ├── manifests │ │ ├── argocd-cm-patch.yaml │ │ ├── deployment.yaml │ │ └── service.yaml │ └── up.sh ├── backstage │ ├── manifests │ │ ├── config-map.yaml │ │ ├── deployment.yaml │ │ ├── rbac.yaml │ │ ├── secrets.yaml │ │ └── service.yaml │ └── up.sh ├── crossplane │ ├── manifests │ │ └── aws-credentials.txt │ └── up.sh ├── komoplane │ ├── manifests │ │ ├── deployment.yaml │ │ └── service.yaml │ └── up.sh ├── kyverno │ └── up.sh └── localstack │ ├── manifests │ ├── deployment.yaml │ └── service.yaml │ └── up.sh ├── .docs ├── cover.png ├── github-com-settings-tokens-new.png └── high-level-architecture.png ├── .env.example ├── .github └── workflows │ └── validate-claims.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── api-server ├── cmd │ └── main.go ├── go.mod └── go.sum ├── argocd ├── apps.yaml ├── apps │ ├── backstage │ │ └── app.yaml │ ├── crossplane │ │ └── app.yaml │ ├── komoplane │ │ └── app.yaml │ ├── kyverno │ │ ├── app.yaml │ │ └── values.yaml │ └── localstack │ │ ├── app.yaml │ │ └── values.yaml └── crossplane-system.yaml ├── backstage ├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .yarn │ └── releases │ │ └── yarn-4.4.1.cjs ├── .yarnrc.yml ├── Makefile ├── README.md ├── app-config.production.yaml ├── app-config.yaml ├── backstage.json ├── catalog-info.yaml ├── catalog │ ├── all.yaml │ ├── components.yaml │ └── templates │ │ ├── application │ │ └── .gitkeep │ │ └── xqueue-claim │ │ ├── skeleton │ │ └── ${{values.queueName}}.yaml │ │ └── template.yaml ├── examples │ ├── entities.yaml │ ├── org.yaml │ └── template │ │ ├── content │ │ ├── catalog-info.yaml │ │ ├── index.js │ │ └── package.json │ │ └── template.yaml ├── package.json ├── packages │ ├── README.md │ ├── app │ │ ├── .eslintignore │ │ ├── .eslintrc.js │ │ ├── e2e-tests │ │ │ └── app.test.ts │ │ ├── package.json │ │ ├── public │ │ │ ├── android-chrome-192x192.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ ├── manifest.json │ │ │ ├── robots.txt │ │ │ └── safari-pinned-tab.svg │ │ └── src │ │ │ ├── App.test.tsx │ │ │ ├── App.tsx │ │ │ ├── apis.ts │ │ │ ├── components │ │ │ ├── Root │ │ │ │ ├── LogoFull.tsx │ │ │ │ ├── LogoIcon.tsx │ │ │ │ ├── Root.tsx │ │ │ │ └── index.ts │ │ │ ├── catalog │ │ │ │ └── EntityPage.tsx │ │ │ └── search │ │ │ │ └── SearchPage.tsx │ │ │ ├── index.tsx │ │ │ └── setupTests.ts │ └── backend │ │ ├── .eslintrc.js │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── package.json │ │ └── src │ │ ├── crossplane-entity-provider.ts │ │ └── index.ts ├── playwright.config.ts ├── plugins │ └── README.md ├── tsconfig.json └── yarn.lock ├── crossplane ├── claims │ ├── debug-pre-talk.yaml │ ├── lorem-ipsum-dolor.yaml │ ├── lorem-ipsum-foo.yaml │ ├── lorem-ipsum.yaml │ ├── platform-rock-vai.yaml │ └── platform-rocks.yaml ├── compositions │ └── aws │ │ └── xqueue.yaml ├── functions │ └── function-patch-and-transform.yaml ├── providers-config │ ├── kubernetes-provider-config.yaml │ └── localstack-provider-config.yaml ├── providers │ ├── aws │ │ └── sqs.yaml │ └── kubernetes.yaml └── xrds │ └── xqueue.yaml └── kyverno └── validate-xqueue-fields.yaml /.bootstrap/argocd/manifests/argocd-cm-patch.yaml: -------------------------------------------------------------------------------- 1 | # @see https://docs.crossplane.io/latest/guides/crossplane-with-argo-cd 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: argocd-cm 6 | namespace: argocd-system 7 | data: 8 | application.resourceTrackingMethod: annotation 9 | resource.customizations: | 10 | "*.upbound.io/*": 11 | health.lua: | 12 | health_status = { 13 | status = "Progressing", 14 | message = "Provisioning ..." 15 | } 16 | 17 | local function contains (table, val) 18 | for i, v in ipairs(table) do 19 | if v == val then 20 | return true 21 | end 22 | end 23 | return false 24 | end 25 | 26 | local has_no_status = { 27 | "ProviderConfig", 28 | "ProviderConfigUsage" 29 | } 30 | 31 | if obj.status == nil or next(obj.status) == nil and contains(has_no_status, obj.kind) then 32 | health_status.status = "Healthy" 33 | health_status.message = "Resource is up-to-date." 34 | return health_status 35 | end 36 | 37 | if obj.status == nil or next(obj.status) == nil or obj.status.conditions == nil then 38 | if obj.kind == "ProviderConfig" and obj.status.users ~= nil then 39 | health_status.status = "Healthy" 40 | health_status.message = "Resource is in use." 41 | return health_status 42 | end 43 | return health_status 44 | end 45 | resource.exclusions: | 46 | - apiGroups: 47 | - "*" 48 | kinds: 49 | - ProviderConfigUsage 50 | -------------------------------------------------------------------------------- /.bootstrap/argocd/manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: argocd-server 5 | labels: 6 | app: argocd-server 7 | backstage.io/kubernetes-id: argocd-server 8 | backstage.io/kubernetes-namespace: argocd-system 9 | -------------------------------------------------------------------------------- /.bootstrap/argocd/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: argocd-server 6 | backstage.io/kubernetes-id: argocd-server 7 | backstage.io/kubernetes-namespace: argocd-system 8 | name: argocd-server 9 | -------------------------------------------------------------------------------- /.bootstrap/argocd/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Switch to the platform cluster if not already set 6 | if [[ "$(kubectl config current-context)" != "kind-platform" ]]; then 7 | kubectl config use-context kind-platform || { 8 | echo "Failed to switch context" 9 | exit 1 10 | } 11 | fi 12 | 13 | NS=argocd-system 14 | BASE_DIR="$(dirname "$0")" 15 | MANIFESTS_DIR="$BASE_DIR/manifests" 16 | PORT=8080 17 | ARGO_PWD_NEW="12345678" 18 | REPO_URL="git@github.com:wnqueiroz/platform-engineering-backstack.git" 19 | 20 | echo "Adding Argo CD Helm repository..." 21 | helm repo add argo https://argoproj.github.io/argo-helm 2>/dev/null || true 22 | helm repo update 23 | 24 | echo "Installing or upgrading Argo CD..." 25 | helm upgrade --install argocd \ 26 | --namespace "$NS" \ 27 | --create-namespace argo/argo-cd 28 | 29 | echo "Waiting for Argo CD deployment to be ready..." 30 | kubectl wait --for=condition=available --timeout=120s deployment/argocd-server -n "$NS" || { 31 | echo "Argo CD deployment is not ready" 32 | exit 1 33 | } 34 | 35 | kubectl patch configmap argocd-cm -n $NS --patch-file $MANIFESTS_DIR/argocd-cm-patch.yaml 36 | kubectl patch deploy argocd-server -n $NS --patch-file $MANIFESTS_DIR/deployment.yaml 37 | kubectl patch svc argocd-server -n $NS --patch-file $MANIFESTS_DIR/service.yaml 38 | kubectl rollout restart deployment argocd-server -n $NS 39 | kubectl rollout status deployment argocd-server -n $NS 40 | 41 | # Start port-forward only if not already active 42 | if ! lsof -i TCP:$PORT >/dev/null 2>&1; then 43 | if kubectl get svc/argocd-server -n "$NS" >/dev/null 2>&1; then 44 | echo "Starting port-forward for Argo CD on port $PORT..." 45 | nohup kubectl --namespace "$NS" port-forward svc/argocd-server $PORT:443 >/dev/null 2>&1 & 46 | else 47 | echo "Argo CD service not found. Skipping port-forward." 48 | exit 1 49 | fi 50 | else 51 | echo "Port $PORT is already in use. Assuming port-forward is running." 52 | fi 53 | 54 | # Wait briefly for port-forward to establish 55 | sleep 5 56 | 57 | # Get the initial admin password from the secret 58 | INITIAL_PASSWORD=$(kubectl -n "$NS" get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d) 59 | 60 | echo "Attempting Argo CD login..." 61 | if argocd login localhost:$PORT --username admin --password "$INITIAL_PASSWORD" --insecure >/dev/null 2>&1; then 62 | echo "Logged in using initial password." 63 | echo "Updating Argo CD admin password..." 64 | argocd account update-password --account admin --current-password "$INITIAL_PASSWORD" --new-password "$ARGO_PWD_NEW" 65 | elif argocd login localhost:$PORT --username admin --password "$ARGO_PWD_NEW" --insecure >/dev/null 2>&1; then 66 | echo "Already using the updated password. Login succeeded." 67 | else 68 | echo "Failed to login with both initial and updated password. Aborting." 69 | exit 1 70 | fi 71 | 72 | # Add repo if not already added 73 | if ! argocd repo list | grep -q "$REPO_URL"; then 74 | echo "Adding Git repo to Argo CD..." 75 | argocd repo add "$REPO_URL" --ssh-private-key-path ~/.ssh/id_ed25519 76 | else 77 | echo "Git repo already added." 78 | fi 79 | 80 | # Register cluster if not already registered 81 | if ! argocd cluster list | grep -q "kind-platform"; then 82 | echo "Registering kind-platform cluster to Argo CD..." 83 | argocd cluster add kind-platform --insecure --in-cluster -y 84 | else 85 | echo "Cluster kind-platform already registered." 86 | fi 87 | 88 | # Apply Argo CD applications or configs 89 | echo "Applying Argo CD manifests..." 90 | kubectl apply -f "./argocd/apps.yaml" 91 | kubectl apply -f "./argocd/crossplane-system.yaml" 92 | 93 | echo "✅ Argo CD setup completed successfully!" 94 | -------------------------------------------------------------------------------- /.bootstrap/backstage/manifests/config-map.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: backstage-config 5 | namespace: backstage-system 6 | data: 7 | NODE_OPTIONS: --no-node-snapshot 8 | POSTGRES_HOST: postgres.backstage-system.svc.cluster.local 9 | POSTGRES_PORT: "5432" 10 | -------------------------------------------------------------------------------- /.bootstrap/backstage/manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backstage 5 | namespace: backstage-system 6 | labels: 7 | app: backstage 8 | backstage.io/kubernetes-id: backstage 9 | backstage.io/kubernetes-namespace: backstage-system 10 | spec: 11 | replicas: 1 12 | selector: 13 | matchLabels: 14 | app: backstage 15 | template: 16 | metadata: 17 | labels: 18 | app: backstage 19 | backstage.io/kubernetes-id: backstage 20 | backstage.io/kubernetes-namespace: backstage-system 21 | spec: 22 | serviceAccountName: backstage-user 23 | containers: 24 | - name: backstage 25 | image: backstage:latest 26 | imagePullPolicy: IfNotPresent 27 | ports: 28 | - name: http 29 | containerPort: 7007 30 | envFrom: 31 | - configMapRef: 32 | name: backstage-config 33 | - secretRef: 34 | name: postgres-secrets 35 | - secretRef: 36 | name: backstage-secrets 37 | env: 38 | - name: SERVICE_ACCOUNT_TOKEN 39 | valueFrom: 40 | secretKeyRef: 41 | name: backstage-token 42 | key: token 43 | --- 44 | apiVersion: apps/v1 45 | kind: Deployment 46 | metadata: 47 | name: postgres 48 | namespace: backstage-system 49 | labels: 50 | app: postgres 51 | backstage.io/kubernetes-id: backstage-postgres 52 | backstage.io/kubernetes-namespace: backstage-system 53 | spec: 54 | replicas: 1 55 | selector: 56 | matchLabels: 57 | app: postgres 58 | template: 59 | metadata: 60 | labels: 61 | app: postgres 62 | backstage.io/kubernetes-id: backstage-postgres 63 | backstage.io/kubernetes-namespace: backstage-system 64 | spec: 65 | containers: 66 | - name: postgres 67 | image: postgres:13.2-alpine 68 | imagePullPolicy: "IfNotPresent" 69 | ports: 70 | - containerPort: 5432 71 | envFrom: 72 | - secretRef: 73 | name: postgres-secrets 74 | env: 75 | - name: POSTGRES_HOST 76 | value: postgres.backstage-system 77 | - name: POSTGRES_PORT 78 | value: "5432" 79 | -------------------------------------------------------------------------------- /.bootstrap/backstage/manifests/rbac.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: backstage-system 5 | --- 6 | apiVersion: v1 7 | kind: ServiceAccount 8 | metadata: 9 | name: backstage-user 10 | namespace: backstage-system 11 | --- 12 | apiVersion: v1 13 | kind: Secret 14 | metadata: 15 | name: backstage-token 16 | namespace: backstage-system 17 | annotations: 18 | kubernetes.io/service-account.name: backstage-user 19 | type: kubernetes.io/service-account-token 20 | --- 21 | apiVersion: rbac.authorization.k8s.io/v1 22 | kind: ClusterRoleBinding 23 | metadata: 24 | name: backstage-kubernetes-ingestor-rbac 25 | roleRef: 26 | apiGroup: rbac.authorization.k8s.io 27 | kind: ClusterRole 28 | name: view 29 | subjects: 30 | - kind: ServiceAccount 31 | name: backstage-user 32 | namespace: backstage-system 33 | --- 34 | apiVersion: rbac.authorization.k8s.io/v1 35 | kind: ClusterRole 36 | metadata: 37 | name: backstage-crd-viewer 38 | rules: 39 | - apiGroups: 40 | - apiextensions.k8s.io 41 | resources: 42 | - customresourcedefinitions 43 | verbs: 44 | - get 45 | - list 46 | - watch 47 | --- 48 | apiVersion: rbac.authorization.k8s.io/v1 49 | kind: ClusterRoleBinding 50 | metadata: 51 | name: backstage-crossplane-ingestion-crd-rbac 52 | roleRef: 53 | apiGroup: rbac.authorization.k8s.io 54 | kind: ClusterRole 55 | name: backstage-crd-viewer 56 | subjects: 57 | - kind: ServiceAccount 58 | name: backstage-user 59 | namespace: backstage-system 60 | --- 61 | apiVersion: rbac.authorization.k8s.io/v1 62 | kind: ClusterRoleBinding 63 | metadata: 64 | name: backstage-crossplane-ingestion-rbac 65 | roleRef: 66 | apiGroup: rbac.authorization.k8s.io 67 | kind: ClusterRole 68 | name: crossplane-view 69 | subjects: 70 | - kind: ServiceAccount 71 | name: backstage-user 72 | namespace: backstage-system 73 | --- 74 | apiVersion: rbac.authorization.k8s.io/v1 75 | kind: ClusterRole 76 | metadata: 77 | name: backstage-k8s-pod-actions 78 | rules: 79 | - apiGroups: [""] 80 | resources: ["pods"] 81 | verbs: ["get", "list", "watch", "delete"] 82 | --- 83 | apiVersion: rbac.authorization.k8s.io/v1 84 | kind: ClusterRoleBinding 85 | metadata: 86 | name: backstage-k8s-pod-actions-binding 87 | roleRef: 88 | apiGroup: rbac.authorization.k8s.io 89 | kind: ClusterRole 90 | name: backstage-k8s-pod-actions 91 | subjects: 92 | - kind: ServiceAccount 93 | name: backstage-user 94 | namespace: backstage-system 95 | -------------------------------------------------------------------------------- /.bootstrap/backstage/manifests/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: backstage-secrets 5 | namespace: backstage-system 6 | type: Opaque 7 | data: 8 | GITHUB_TOKEN: 9 | --- 10 | apiVersion: v1 11 | kind: Secret 12 | metadata: 13 | name: postgres-secrets 14 | namespace: backstage-system 15 | type: Opaque 16 | data: 17 | POSTGRES_USER: YmFja3N0YWdl 18 | POSTGRES_PASSWORD: aHVudGVyMg== 19 | -------------------------------------------------------------------------------- /.bootstrap/backstage/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: backstage 5 | namespace: backstage-system 6 | labels: 7 | app: backstage 8 | backstage.io/kubernetes-id: backstage 9 | spec: 10 | selector: 11 | app: backstage 12 | ports: 13 | - name: http 14 | port: 80 15 | targetPort: http 16 | --- 17 | apiVersion: v1 18 | kind: Service 19 | metadata: 20 | name: postgres 21 | namespace: backstage-system 22 | labels: 23 | app: postgres 24 | backstage.io/kubernetes-id: backstage-postgres 25 | spec: 26 | selector: 27 | app: postgres 28 | ports: 29 | - port: 5432 30 | -------------------------------------------------------------------------------- /.bootstrap/backstage/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Configuration 6 | NS=backstage-system 7 | BASE_DIR="$(dirname "$0")" 8 | PORT=3000 9 | IMAGE="backstage:latest" 10 | CLUSTER_NAME="platform" 11 | CONTEXT_NAME="kind-${CLUSTER_NAME}" 12 | 13 | # Switch kubectl context if not already set 14 | if [[ "$(kubectl config current-context)" != "$CONTEXT_NAME" ]]; then 15 | echo "Switching kubectl context to $CONTEXT_NAME..." 16 | kubectl config use-context "$CONTEXT_NAME" || { 17 | echo "❌ Failed to switch context to $CONTEXT_NAME" 18 | exit 1 19 | } 20 | fi 21 | 22 | # Create namespace if it doesn't exist 23 | if ! kubectl get namespace "$NS" >/dev/null 2>&1; then 24 | echo "Creating namespace: $NS" 25 | kubectl create namespace "$NS" 26 | else 27 | echo "✅ Namespace $NS already exists." 28 | fi 29 | 30 | echo "Checking if Backstage image '$IMAGE' already exists..." 31 | if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then 32 | echo "🔨 Building Backstage image $IMAGE..." 33 | cd ./backstage 34 | yarn install 35 | yarn build:all 36 | yarn build-image --tag "$IMAGE" --no-cache 37 | cd .. 38 | else 39 | echo "✅ Docker image $IMAGE already exists. Skipping build." 40 | fi 41 | 42 | kind load docker-image "$IMAGE" --name "$CLUSTER_NAME" 43 | 44 | export $(cat .env | xargs) && 45 | sed "s||$(echo "$GITHUB_TOKEN" | base64)|" $BASE_DIR/manifests/secrets.yaml | 46 | kubectl apply -n $NS -f - 47 | 48 | # Wait for postgres deployment to be ready 49 | echo "Waiting for postgres deployment to be ready..." 50 | kubectl rollout status deployment/postgres -n "$NS" --timeout=120s || { 51 | echo "❌ Postgres deployment is not ready" 52 | exit 1 53 | } 54 | 55 | # Wait for backstage deployment to be ready 56 | echo "Waiting for backstage deployment to be ready..." 57 | kubectl rollout status deployment/backstage -n "$NS" --timeout=120s || { 58 | echo "❌ Backstage deployment is not ready" 59 | exit 1 60 | } 61 | 62 | # Start port-forward in background if not already running 63 | if ! lsof -i TCP:$PORT | grep LISTEN >/dev/null 2>&1; then 64 | if kubectl get svc/backstage -n "$NS" >/dev/null 2>&1; then 65 | echo "Starting port-forward for Backstage on port $PORT..." 66 | nohup kubectl --namespace "$NS" port-forward svc/backstage ${PORT}:80 >/dev/null 2>&1 & 67 | echo "Port-forward started in background." 68 | else 69 | echo "Backstage service not found. Skipping port-forward." 70 | exit 1 71 | fi 72 | else 73 | echo "Port $PORT is already in use. Assuming port-forward is running." 74 | fi 75 | 76 | echo "✅ Backstage setup completed successfully!" 77 | -------------------------------------------------------------------------------- /.bootstrap/crossplane/manifests/aws-credentials.txt: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id = test 3 | aws_secret_access_key = test 4 | -------------------------------------------------------------------------------- /.bootstrap/crossplane/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Change to platform cluster 6 | if [[ "$(kubectl config current-context)" != "kind-platform" ]]; then 7 | kubectl config use-context kind-platform || { 8 | echo "Failed to switch context to kind-platform" 9 | exit 1 10 | } 11 | fi 12 | 13 | NS=crossplane-system 14 | BASE_DIR=./crossplane 15 | MANIFESTS_DIR="$(dirname "$0")/manifests" 16 | REQUIRED_PROVIDERS=("provider-kubernetes" "provider-aws-sqs") 17 | TIMEOUT=600 18 | INTERVAL=5 19 | ELAPSED=0 20 | 21 | echo "Waiting for Crossplane to be ready..." 22 | kubectl wait --for=condition=available --timeout=120s deployment -n "$NS" -l app.kubernetes.io/name=crossplane 23 | 24 | echo "Ensuring AWS (LocalStack) secret exists..." 25 | if kubectl get secret aws-secret -n "$NS" >/dev/null 2>&1; then 26 | echo "AWS secret already exists. Skipping creation." 27 | else 28 | kubectl create secret generic aws-secret -n "$NS" \ 29 | --from-file=creds="$MANIFESTS_DIR/aws-credentials.txt" 30 | fi 31 | 32 | # Function to check if all required providers are healthy 33 | check_providers_health() { 34 | for provider in "${REQUIRED_PROVIDERS[@]}"; do 35 | health_status=$(kubectl get providerrevisions.pkg.crossplane.io | grep "$provider" | awk '{print $2}') 36 | if [ "$health_status" != "True" ]; then 37 | echo "Provider $provider is not healthy yet. Waiting..." 38 | return 1 39 | fi 40 | done 41 | return 0 42 | } 43 | 44 | echo "Checking provider health status..." 45 | while ! check_providers_health; do 46 | if [ "$ELAPSED" -ge "$TIMEOUT" ]; then 47 | echo "Timeout reached. Some providers did not become healthy in time." 48 | exit 1 49 | fi 50 | sleep $INTERVAL 51 | ELAPSED=$((ELAPSED + INTERVAL)) 52 | done 53 | 54 | echo "All providers are healthy!" 55 | echo "✅ Crossplane setup completed successfully!" 56 | -------------------------------------------------------------------------------- /.bootstrap/komoplane/manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: komoplane 5 | labels: 6 | app: komoplane 7 | backstage.io/kubernetes-id: komoplane 8 | backstage.io/kubernetes-namespace: komoplane-system 9 | -------------------------------------------------------------------------------- /.bootstrap/komoplane/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: komoplane 6 | backstage.io/kubernetes-id: komoplane 7 | backstage.io/kubernetes-namespace: komoplane-system 8 | name: komoplane 9 | -------------------------------------------------------------------------------- /.bootstrap/komoplane/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Change to platform cluster only if needed 6 | if [[ "$(kubectl config current-context)" != "kind-platform" ]]; then 7 | kubectl config use-context kind-platform || { 8 | echo "Failed to switch context to kind-platform" 9 | exit 1 10 | } 11 | fi 12 | 13 | NS=komoplane-system 14 | PORT=8090 15 | BASE_DIR="$(dirname "$0")" 16 | MANIFESTS_DIR="$BASE_DIR/manifests" 17 | 18 | # Wait for Komoplane to be ready 19 | echo "Waiting for Komoplane deployment to be ready..." 20 | kubectl wait --for=condition=available --timeout=120s deployment/komoplane -n "$NS" || { 21 | echo "Komoplane deployment is not ready" 22 | exit 1 23 | } 24 | 25 | kubectl patch deploy komoplane -n $NS --patch-file $MANIFESTS_DIR/deployment.yaml 26 | kubectl patch svc komoplane -n $NS --patch-file $MANIFESTS_DIR/service.yaml 27 | kubectl rollout restart deployment komoplane -n $NS 28 | kubectl rollout status deployment komoplane -n $NS 29 | 30 | # Background port-forward only if not already running 31 | if ! lsof -i TCP:$PORT >/dev/null 2>&1; then 32 | if kubectl get svc/komoplane -n "$NS" >/dev/null 2>&1; then 33 | echo "Starting port-forward for Komoplane on port $PORT..." 34 | nohup kubectl --namespace "$NS" port-forward svc/komoplane $PORT:$PORT >/dev/null 2>&1 & 35 | echo "Port-forward started in background." 36 | else 37 | echo "Komoplane service not found. Skipping port-forward." 38 | exit 1 39 | fi 40 | else 41 | echo "Port $PORT is already in use. Assuming port-forward is running." 42 | fi 43 | 44 | echo "✅ Komoplane setup completed successfully!" 45 | -------------------------------------------------------------------------------- /.bootstrap/kyverno/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Change to platform cluster 6 | if [[ "$(kubectl config current-context)" != "kind-platform" ]]; then 7 | kubectl config use-context kind-platform || { 8 | echo "Failed to switch context to kind-platform" 9 | exit 1 10 | } 11 | fi 12 | 13 | NS=kyverno-system 14 | POLICIES_DIR=./kyverno 15 | TIMEOUT=240 16 | INTERVAL=5 17 | ELAPSED=0 18 | 19 | echo "Waiting for Kyverno webhook to be ready..." 20 | kubectl wait deployment/kyverno-admission-controller \ 21 | -n "$NS" \ 22 | --for=condition=Available=True \ 23 | --timeout=${TIMEOUT}s 24 | 25 | echo "Ensuring all Kyverno pods are ready..." 26 | while true; do 27 | READY=$(kubectl get pods -n "$NS" -o jsonpath='{.items[*].status.containerStatuses[*].ready}' | tr " " "\n" | grep -c false || true) 28 | if [[ "$READY" -eq 0 ]]; then 29 | break 30 | fi 31 | if [[ "$ELAPSED" -ge "$TIMEOUT" ]]; then 32 | echo "Timeout reached. Kyverno pods are not ready." 33 | exit 1 34 | fi 35 | echo "Waiting for Kyverno pods to become ready..." 36 | sleep $INTERVAL 37 | ELAPSED=$((ELAPSED + INTERVAL)) 38 | done 39 | 40 | echo "All Kyverno pods are ready!" 41 | 42 | apply_kyverno_policies() { 43 | local retries=20 44 | local delay=3 45 | 46 | for i in $(seq 1 $retries); do 47 | if kubectl apply -f "$POLICIES_DIR" --recursive; then 48 | echo "✅ Kyverno policies applied successfully!" 49 | return 0 50 | else 51 | echo "⚠️ Failed to apply policies, retrying in ${delay}s... (${i}/${retries})" 52 | sleep $delay 53 | fi 54 | done 55 | 56 | echo "❌ Failed to apply Kyverno policies after ${retries} attempts." 57 | exit 1 58 | } 59 | 60 | echo "Applying Kyverno policies from $POLICIES_DIR..." 61 | apply_kyverno_policies 62 | 63 | echo "✅ Kyverno setup and policy application completed successfully!" 64 | -------------------------------------------------------------------------------- /.bootstrap/localstack/manifests/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: localstack 5 | labels: 6 | app: localstack 7 | backstage.io/kubernetes-id: localstack 8 | backstage.io/kubernetes-namespace: localstack-system 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | app: localstack 14 | template: 15 | metadata: 16 | labels: 17 | app: localstack 18 | backstage.io/kubernetes-id: localstack 19 | backstage.io/kubernetes-namespace: localstack-system 20 | spec: 21 | containers: 22 | - name: localstack 23 | image: localstack/localstack:latest 24 | ports: 25 | - containerPort: 4566 26 | - containerPort: 4571 27 | env: 28 | - name: SERVICES 29 | value: "s3,sqs,ec2,iam,lambda,dynamodb" 30 | - name: DEBUG 31 | value: "1" 32 | - name: HOSTNAME 33 | value: "localhost" 34 | - name: LAMBDA_EXECUTOR 35 | value: "docker" 36 | resources: 37 | limits: 38 | memory: "1Gi" 39 | cpu: "1000m" 40 | requests: 41 | memory: "512Mi" 42 | cpu: "500m" 43 | livenessProbe: 44 | httpGet: 45 | path: /_localstack/health 46 | port: 4566 47 | initialDelaySeconds: 10 48 | periodSeconds: 5 49 | readinessProbe: 50 | httpGet: 51 | path: /_localstack/health 52 | port: 4566 53 | initialDelaySeconds: 15 54 | periodSeconds: 5 55 | -------------------------------------------------------------------------------- /.bootstrap/localstack/manifests/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: localstack 5 | labels: 6 | backstage.io/kubernetes-id: localstack 7 | backstage.io/kubernetes-namespace: localstack-system 8 | spec: 9 | selector: 10 | app: localstack 11 | ports: 12 | - protocol: TCP 13 | port: 4566 14 | targetPort: 4566 15 | type: ClusterIP 16 | -------------------------------------------------------------------------------- /.bootstrap/localstack/up.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Change to platform cluster only if needed 6 | if [[ "$(kubectl config current-context)" != "kind-platform" ]]; then 7 | kubectl config use-context kind-platform || { 8 | echo "Failed to switch context to kind-platform" 9 | exit 1 10 | } 11 | fi 12 | 13 | BASE_DIR="$(dirname "$0")" 14 | NS="localstack-system" 15 | PORT=4566 16 | 17 | # Create namespace if it does not exist 18 | if ! kubectl get namespace "$NS" >/dev/null 2>&1; then 19 | echo "Creating namespace: $NS" 20 | kubectl create namespace "$NS" 21 | else 22 | echo "Namespace $NS already exists." 23 | fi 24 | 25 | # Wait for Deployment to exist 26 | echo "Waiting for LocalStack Deployment to be created..." 27 | for i in {1..24}; do 28 | if kubectl get deployment/localstack -n "$NS" >/dev/null 2>&1; then 29 | echo "LocalStack Deployment found." 30 | break 31 | fi 32 | echo "Deployment not found yet. Retrying in 5s..." 33 | sleep 5 34 | done 35 | 36 | # After Deployment exists, wait for it to become available 37 | echo "Waiting for LocalStack Deployment to become available..." 38 | kubectl wait --for=condition=available --timeout=120s deployment/localstack -n "$NS" || { 39 | echo "LocalStack Deployment is not ready after waiting." 40 | exit 1 41 | } 42 | 43 | # Background port-forward (only if not already running) 44 | if ! lsof -i TCP:$PORT >/dev/null 2>&1; then 45 | if kubectl get svc/localstack -n "$NS" >/dev/null 2>&1; then 46 | echo "Starting port-forward for LocalStack on port $PORT..." 47 | nohup kubectl --namespace "$NS" port-forward svc/localstack ${PORT}:${PORT} >/dev/null 2>&1 & 48 | echo "Port-forward started in background." 49 | else 50 | echo "LocalStack service not found. Skipping port-forward." 51 | exit 1 52 | fi 53 | else 54 | echo "Port $PORT is already in use. Assuming port-forward is running." 55 | fi 56 | 57 | echo "✅ LocalStack setup completed successfully!" 58 | -------------------------------------------------------------------------------- /.docs/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/.docs/cover.png -------------------------------------------------------------------------------- /.docs/github-com-settings-tokens-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/.docs/github-com-settings-tokens-new.png -------------------------------------------------------------------------------- /.docs/high-level-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/.docs/high-level-architecture.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GITHUB_TOKEN="placeholder" # get it at https://github.com/settings/tokens/new 2 | -------------------------------------------------------------------------------- /.github/workflows/validate-claims.yaml: -------------------------------------------------------------------------------- 1 | name: Validate Crossplane Claims 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "crossplane/claims/**/*.yaml" 7 | - "crossplane/claims/**/*.yml" 8 | - "kyverno/**/*.yaml" 9 | - "kyverno/**/*.yml" 10 | 11 | jobs: 12 | validate-claims: 13 | name: Validate Claims YAML with Kyverno 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | 20 | - name: Install Kyverno CLI 21 | uses: kyverno/action-install-cli@v0.2.0 22 | with: 23 | release: "v1.13.4" 24 | 25 | - name: Check install 26 | run: kyverno version 27 | 28 | - name: Run Kyverno policy checks on claims 29 | run: | 30 | echo "## 🛡️ Kyverno Policy Validation Results" >> $GITHUB_STEP_SUMMARY 31 | echo "" >> $GITHUB_STEP_SUMMARY 32 | echo '```' >> $GITHUB_STEP_SUMMARY 33 | 34 | set +e 35 | kyverno apply ./kyverno --resource ./crossplane/claims 2>&1 | tee result.txt 36 | KYVERNO_EXIT_CODE=${PIPESTATUS[0]} 37 | set -e 38 | 39 | cat result.txt >> $GITHUB_STEP_SUMMARY 40 | echo '```' >> $GITHUB_STEP_SUMMARY 41 | 42 | if [[ $KYVERNO_EXIT_CODE -ne 0 ]]; then 43 | echo "" >> $GITHUB_STEP_SUMMARY 44 | echo "❌ One or more Kyverno policies failed. Please fix the issues above." >> $GITHUB_STEP_SUMMARY 45 | exit 1 46 | else 47 | echo "✅ All policies passed." >> $GITHUB_STEP_SUMMARY 48 | fi 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore common system files 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # Ignore editor-specific files 6 | *.swp 7 | *.swo 8 | 9 | # Ignore YAML backup and temporary files 10 | *.yaml~ 11 | *.yml~ 12 | 13 | # Ignore shell script backup files 14 | *.sh~ 15 | *.bak 16 | *.swp 17 | 18 | # Ignore log and temporary files generated by shell scripts 19 | *.log 20 | *.tmp 21 | *.out 22 | *.err 23 | 24 | # Ignore env files 25 | 26 | .env* 27 | !.env.example 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering contributing to this project! 🚀 4 | 5 | Before submitting a new issue or pull request, please: 6 | 7 | - **Check if an open issue already exists** for the problem or feature you intend to address. 8 | - If a related issue already exists, consider contributing to the discussion there instead of opening a duplicate. 9 | 10 | ## How to Contribute 11 | 12 | Follow these steps to contribute effectively: 13 | 14 | 1. **Clone the repository** 15 | 16 | ```bash 17 | git clone git@github.com:wnqueiroz/platform-engineering-backstack.git 18 | cd platform-engineering-backstack 19 | ``` 20 | 21 | 2. **Create a new branch** using the following naming convention: 22 | 23 | ```bash 24 | git checkout -b feature- 25 | ``` 26 | 27 | 3. **Follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification** when writing commit messages. 28 | 29 | A Conventional Commit is a lightweight convention for commit messages that provides a consistent way to structure the history of changes. The general format is: 30 | 31 | ``` 32 | (optional-scope): 33 | ``` 34 | 35 | Examples: 36 | 37 | - `feat: add new authentication flow` 38 | - `fix(login): correct password validation error` 39 | - `docs: update README with setup instructions` 40 | 41 | Common types include: 42 | 43 | - `feat`: a new feature 44 | - `fix`: a bug fix 45 | - `docs`: documentation only changes 46 | - `chore`: maintenance tasks (e.g., build system, CI) 47 | 48 | 4. **Push your branch** and open a **Pull Request** (PR) targeting the `main` branch. 49 | 50 | 5. In your PR description, link to any relevant issues and provide a clear summary of the changes you are proposing. 51 | 52 | --- 53 | 54 | Thanks for helping us make this project better! 🙌 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 William Queiroz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: up down check_bins 3 | 4 | check_bins: 5 | @command -v kind >/dev/null 2>&1 || { echo >&2 "kind not found! Please install it before continuing."; exit 1; } 6 | @command -v kubectl >/dev/null 2>&1 || { echo >&2 "kubectl not found! Please install it before continuing."; exit 1; } 7 | @command -v argocd >/dev/null 2>&1 || { echo >&2 "argocd not found! Please install it before continuing."; exit 1; } 8 | @command -v helm >/dev/null 2>&1 || { echo >&2 "helm not found! Please install it before continuing."; exit 1; } 9 | 10 | up: check_bins 11 | @echo "Creating environment..." 12 | @if kind get clusters | grep -q "^platform$$"; then \ 13 | echo "Cluster 'platform' already exists. Skipping..."; \ 14 | else \ 15 | kind create cluster --name platform; \ 16 | fi 17 | 18 | @./.bootstrap/argocd/up.sh 19 | @./.bootstrap/backstage/up.sh 20 | @./.bootstrap/localstack/up.sh 21 | @./.bootstrap/crossplane/up.sh 22 | @./.bootstrap/komoplane/up.sh 23 | @./.bootstrap/kyverno/up.sh 24 | 25 | @make setup-local-config 26 | 27 | @echo 28 | @echo "---------------------------------------------------------------------------------------------------------------------------" 29 | @echo "Backstage is accessible at http://localhost:3000" 30 | @echo "Argo CD is accessible at http://localhost:8080" 31 | @echo "Komoplane is accessible at http://localhost:8090" 32 | @echo "LocalStack is accessible at http://localhost:4566 (Manage through the platform at: https://app.localstack.cloud/instances)" 33 | 34 | down: check_bins 35 | @echo "Deleting environment..." 36 | @if kind get clusters | grep -q "^platform$$"; then \ 37 | kind delete cluster --name platform; \ 38 | else \ 39 | echo "Cluster 'platform' not found. Skipping..."; \ 40 | fi 41 | 42 | setup-local-config: check_bins 43 | @echo "Updating app-config.local.yaml..." 44 | @test -f backstage/app-config.local.yaml || echo "{}" > backstage/app-config.local.yaml 45 | @export SERVICE_ACCOUNT_TOKEN=$$(kubectl get secret -n backstage-system backstage-token -o jsonpath='{.data.token}' | base64 --decode); \ 46 | export CLUSTER_URL=$$(kubectl cluster-info | grep 'Kubernetes control plane' | awk '{print $$NF}'); \ 47 | FILE="backstage/app-config.local.yaml"; \ 48 | yq -i '.kubernetes.clusterLocatorMethods[0].clusters[0].serviceAccountToken = strenv(SERVICE_ACCOUNT_TOKEN)' $$FILE; \ 49 | yq -i '.kubernetes.clusterLocatorMethods[0].clusters[0].url = strenv(CLUSTER_URL)' $$FILE 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Platform Engineering | BACK Stack

4 | 5 | _A ready-to-use environment for modern platform engineering experimentation, combining (B)ackstage, (A)rgoCD, (C)rossplane, and (K)yverno! 🚀_ 6 | 7 |
8 | Interfaces and Capabilities of a Development Platform 9 |
10 |
11 | 12 | Interfaces and Capabilities of a Development Platform. Source: CLOUD NATIVE COMPUTING FOUNDATION. Platforms whitepaper. Available at: https://tag-app-delivery.cncf.io/whitepapers/platforms/#capabilities-of-platforms. 13 | 14 |
15 |
16 |
17 | 18 | > _🚧 Under construction 🚧_ 19 | 20 |
21 | 22 | ## Summary 23 | 24 | - [Motivation ✨](#motivation-) 25 | - [Stack](#stack) 26 | - [Prerequisites](#prerequisites) 27 | - [Up \& Running](#up--running) 28 | - [Troubleshooting](#troubleshooting) 29 | - [Accessing Applications](#accessing-applications) 30 | - [Architecture](#architecture) 31 | - [Roadmap 🚧](#roadmap-) 32 | - [How to Contribute](#how-to-contribute) 33 | 34 | ## Motivation ✨ 35 | 36 | **Platform Engineering** requires integrating multiple tools to provide developers with a seamless and efficient experience. Building an **Internal Developer Platform (IDP)** involves solutions for automation, infrastructure provisioning, access control, observability, and continuous delivery workflows, which makes it challenging for both beginners and experienced teams. 37 | 38 | Tools like **Backstage, Crossplane, and ArgoCD** are commonly used to create a unified developer experience, but experimenting with and understanding how they work together can be hard without a properly configured environment. Each technology brings its own concepts and abstraction layers, making the learning and implementation process fragmented. 39 | 40 | This project was created to meet the need for an **easy-to-run local environment**, enabling quick experimentation with the core technologies involved in platform engineering. The goal is to provide a **functional and reproducible stack**, reducing initial complexity and enabling hands-on exploration before deploying these solutions in a production setting. 41 | 42 | With this stack, you can: 43 | 44 | - ✅ Quickly test integration between Backstage, Crossplane, and ArgoCD. 45 | - ✅ Simulate an IDP experience in a local setup. 46 | - ✅ Understand the challenges and benefits of each tool. 47 | - ✅ Create and modify infrastructure compositions using GitOps. 48 | 49 | If you're interested in platform engineering and want to explore how these tools fit together, this repository is a great place to start! 🚀 50 | 51 | ## Stack 52 | 53 | This repository brings together essential tools to build and experiment with a local **Internal Developer Platform (IDP)**. Below is a brief description of each component: 54 | 55 | - **Backstage**: An open-source developer portal created by Spotify, designed to unify tools, services, and documentation into a single interface. It provides a **service catalog**, allowing teams to organize and discover APIs, infrastructure, and documentation centrally, promoting standardization and development efficiency. 56 | 57 | - **ArgoCD**: A GitOps continuous delivery controller for Kubernetes, responsible for managing and syncing applications defined via Git-based manifests. 58 | 59 | - **Crossplane**: A Kubernetes-native infrastructure provisioning tool that enables declarative cloud and on-prem resource management via **Compositions**. 60 | 61 | - **Kyverno**: A policy engine for Kubernetes that enables enforcement and validation of compliance and security rules in clusters. 62 | 63 | - **LocalStack**: A fully functional local AWS cloud emulator that enables developers to test and build applications interacting with AWS services without needing a real AWS account. 64 | 65 | - **Komoplane**: A project to experiment with Crossplane resource visualization. The goal is to help Crossplane users understand control plane resource structure and accelerate troubleshooting. 66 | 67 | - **Helm**: A package manager for Kubernetes that simplifies deploying complex applications using reusable _charts_. 68 | 69 | - **kind** (Kubernetes in Docker): A tool for running local Kubernetes clusters using Docker containers, ideal for testing and development. 70 | 71 | - **kubectl**: The official Kubernetes command-line tool (CLI) for interacting with clusters, applying configurations, and managing resources. 72 | 73 | ## Prerequisites 74 | 75 | Make sure the following dependencies are installed before running any commands: 76 | 77 | - [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start/) (version v0.27.0 or higher); 78 | - [`kubectl`](https://kubernetes.io/docs/tasks/tools/) (version v1.32.2 or higher); 79 | - [`argocd`](https://argo-cd.readthedocs.io/en/stable/cli_installation/) (version v2.14.2 or higher); 80 | - [`helm`](https://helm.sh/docs/intro/install/) (version v3.17.1 or higher); 81 | - [Docker](https://docs.docker.com/engine/install/) (version 27.4.0 or higher). 82 | 83 | > _⚠️ Installation of these basic tools is not covered here as it varies by operating system. The following scripts assume you have each one properly set up._ 84 | 85 | Before proceeding, make sure you have also completed the following steps: 86 | 87 | 1. **Fork this repository** to your GitHub account. 88 | 2. **Clone your fork** locally: 89 | ```bash 90 | git clone https://github.com//.git 91 | cd 92 | ``` 93 | 3. **Create a `.env` file** at the root of the project based on the provided `.env.example`: 94 | ```bash 95 | cp .env.example .env 96 | ``` 97 | 4. **Generate a GitHub Personal Access Token** with full `repo` permissions by visiting [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new). 98 | ![https://github.com/settings/tokens/new](.docs/github-com-settings-tokens-new.png) 99 | 5. **Update the `.env` file** with your generated token, replacing the `"placeholder"` value. 100 | 101 | > 💡 The GitHub token will be used to open pull requests in the repository and to read the catalog file for loading into Backstage. 102 | 103 | ## Up & Running 104 | 105 | This project uses a Makefile to simplify environment setup and teardown. Follow the steps below to get started. 106 | 107 | If you don't have the `make` command in your environment, install it according to your operating system: 108 | 109 |
110 | Ubuntu/Debian 111 | 112 | ```sh 113 | sudo apt update && sudo apt install -y make 114 | ``` 115 | 116 | Verify installation: 117 | 118 | ```sh 119 | make --version 120 | ``` 121 | 122 | Install build tools if needed: 123 | 124 | ```sh 125 | sudo apt update && sudo apt install -y build-essential 126 | ``` 127 | 128 |
129 | 130 |
131 | Fedora 132 | 133 | ```sh 134 | sudo dnf install -y make 135 | ``` 136 | 137 | Verify installation: 138 | 139 | ```sh 140 | make --version 141 | ``` 142 | 143 | Install build tools if needed: 144 | 145 | ```sh 146 | sudo dnf groupinstall -y "Development Tools" 147 | ``` 148 | 149 |
150 | 151 |
152 | Arch Linux 153 | 154 | ```sh 155 | sudo pacman -Syu make 156 | ``` 157 | 158 | Verify installation: 159 | 160 | ```sh 161 | make --version 162 | ``` 163 | 164 | Install build tools if needed: 165 | 166 | ```sh 167 | sudo pacman -Syu base-devel 168 | ``` 169 | 170 |
171 | 172 |
173 | macOS (via Homebrew) 174 | 175 | ```sh 176 | brew install make 177 | ``` 178 | 179 | Verify installation: 180 | 181 | ```sh 182 | make --version 183 | ``` 184 | 185 | Install Xcode development tools if needed: 186 | 187 | ```sh 188 | xcode-select --install 189 | ``` 190 | 191 |
192 | 193 |
194 | Windows (via MSYS2) 195 | 196 | 1. Download and install [MSYS2](https://www.msys2.org/). 197 | 2. Open the MSYS2 terminal and run: 198 | 199 | ```sh 200 | pacman -S make 201 | ``` 202 | 203 | Verify installation: 204 | 205 | ```sh 206 | make --version 207 | ``` 208 | 209 | For a complete development environment: 210 | 211 | ```sh 212 | pacman -S base-devel 213 | ``` 214 | 215 |
216 | 217 |
218 | 219 | To set up the environment, run: 220 | 221 | ```sh 222 | make up 223 | ``` 224 | 225 | This command will: 226 | 227 | - Check if required dependencies are installed. 228 | - Create a Kubernetes cluster named `platform` (if it does not already exist). 229 | - Run bootstrap scripts for LocalStack, Crossplane, Komoplane, and ArgoCD. 230 | 231 | To tear down the environment, run: 232 | 233 | ```sh 234 | make down 235 | ``` 236 | 237 | This command will: 238 | 239 | - Check if required dependencies are installed. 240 | - Delete the Kubernetes `platform` cluster if it exists. 241 | 242 | ### Troubleshooting 243 | 244 | If you encounter issues, ensure that: 245 | 246 | - All required binaries are installed and available in your system `PATH`. 247 | - The `kind` clusters are running before applying any `kubectl` instructions. 248 | 249 | For additional help, refer to the documentation links in the prerequisites section. 250 | 251 | ### Accessing Applications 252 | 253 | Applications are exposed via `nohup` + `kubectl port-forward`. 254 | 255 | | Application | Address | Notes | 256 | | ----------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------- | 257 | | Backstage | [http://localhost:3000](http://localhost:3000) | Enter as a Guest User. | 258 | | Argo CD | [http://localhost:8080](http://localhost:8080) | Username: `admin`
Password: `12345678` | 259 | | Komoplane | [http://localhost:8090](http://localhost:8090) | - | 260 | | Localstack | [http://localhost:4566](http://localhost:4566) | Manage it via: [https://app.localstack.cloud/instances](https://app.localstack.cloud/instances) | 261 | 262 | ## Architecture 263 | 264 | Below is a high-level architecture diagram showing how the components interact: 265 | 266 | ![Architecture](.docs/high-level-architecture.png) 267 | 268 | ## Roadmap 🚧 269 | 270 | This section outlines upcoming improvements and planned changes for this project: 271 | 272 | - [ ] Reduce the responsibility of the `.bootstrap/**/up.sh` scripts: shift tool installation and configuration to ArgoCD so that it manages not only Crossplane claims but also the cluster setup itself — making the environment closer to real-world GitOps practices. 273 | 274 | - [ ] Improve the Kyverno GitHub Action: update the CI pipeline to apply only the policies related to the resources changed in a given Pull Request. 275 | 276 | - [ ] Evaluate the use of the **TeraSky Kubernetes Ingestor plugin** for Backstage ([link](https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugins/kubernetes-ingestor)): current integration attempts led to infinite loops, very few users seem to rely on it, and documentation is limited. 277 | 278 | - [ ] Evaluate the **TeraSky Crossplane Resources plugin** for Backstage ([link](https://github.com/TeraSky-OSS/backstage-plugins/tree/main/plugins/crossplane-resources)): provides a useful UI and presents a compelling case for exposing infrastructure views not just to solution engineers but also to platform engineers. However, it depends on the Kubernetes Ingestor plugin. 279 | 280 | ## How to Contribute 281 | 282 | I welcome contributions! 🎉 283 | 284 | Before starting, please take a moment to review the [Contributing Guidelines](./CONTRIBUTING.md). 285 | -------------------------------------------------------------------------------- /api-server/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | 9 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 10 | "k8s.io/client-go/rest" 11 | "k8s.io/client-go/tools/clientcmd" 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | ) 14 | 15 | func getKubeConfig() (*rest.Config, error) { 16 | kubeconfig := flag.String("kubeconfig", os.Getenv("HOME")+"/.kube/config", "path to kubeconfig") 17 | flag.Parse() 18 | 19 | config, err := rest.InClusterConfig() 20 | if err != nil { 21 | config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig) 22 | if err != nil { 23 | return nil, fmt.Errorf("failed to load kubeconfig: %v", err) 24 | } 25 | } 26 | return config, nil 27 | } 28 | 29 | func main() { 30 | config, err := getKubeConfig() 31 | if err != nil { 32 | fmt.Printf("Error retrieving Kubernetes configuration: %v\n", err) 33 | return 34 | } 35 | 36 | dynClient, err := client.New(config, client.Options{}) 37 | if err != nil { 38 | fmt.Printf("Error creating dynamic client: %v\n", err) 39 | return 40 | } 41 | 42 | claim := &unstructured.Unstructured{ 43 | Object: map[string]any{ 44 | "apiVersion": "hooli.tech/v1alpha1", 45 | "kind": "NoSQLClaim", 46 | "metadata": map[string]any{ 47 | "name": "my-nosql-database", 48 | "namespace": "default", 49 | }, 50 | "spec": map[string]any{ 51 | "location": "US", 52 | }, 53 | }, 54 | } 55 | 56 | err = dynClient.Create(context.TODO(), claim) 57 | if err != nil { 58 | fmt.Printf("Error creating Claim: %v\n", err) 59 | return 60 | } 61 | 62 | fmt.Println("Claim successfully created!") 63 | } 64 | -------------------------------------------------------------------------------- /api-server/go.mod: -------------------------------------------------------------------------------- 1 | module api-server 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | k8s.io/apimachinery v0.32.2 7 | k8s.io/client-go v0.32.2 8 | sigs.k8s.io/controller-runtime v0.20.3 9 | ) 10 | 11 | require ( 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 13 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 14 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 15 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 16 | github.com/go-logr/logr v1.4.2 // indirect 17 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 18 | github.com/go-openapi/jsonreference v0.20.2 // indirect 19 | github.com/go-openapi/swag v0.23.0 // indirect 20 | github.com/gogo/protobuf v1.3.2 // indirect 21 | github.com/golang/protobuf v1.5.4 // indirect 22 | github.com/google/gnostic-models v0.6.8 // indirect 23 | github.com/google/go-cmp v0.6.0 // indirect 24 | github.com/google/gofuzz v1.2.0 // indirect 25 | github.com/google/uuid v1.6.0 // indirect 26 | github.com/josharian/intern v1.0.0 // indirect 27 | github.com/json-iterator/go v1.1.12 // indirect 28 | github.com/mailru/easyjson v0.7.7 // indirect 29 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 30 | github.com/modern-go/reflect2 v1.0.2 // indirect 31 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 32 | github.com/spf13/pflag v1.0.5 // indirect 33 | github.com/x448/float16 v0.8.4 // indirect 34 | golang.org/x/net v0.37.0 // indirect 35 | golang.org/x/oauth2 v0.23.0 // indirect 36 | golang.org/x/sys v0.31.0 // indirect 37 | golang.org/x/term v0.30.0 // indirect 38 | golang.org/x/text v0.23.0 // indirect 39 | golang.org/x/time v0.7.0 // indirect 40 | google.golang.org/protobuf v1.35.1 // indirect 41 | gopkg.in/inf.v0 v0.9.1 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | k8s.io/api v0.32.2 // indirect 44 | k8s.io/klog/v2 v2.130.1 // indirect 45 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect 46 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 47 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 48 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect 49 | sigs.k8s.io/yaml v1.4.0 // indirect 50 | ) 51 | -------------------------------------------------------------------------------- /api-server/go.sum: -------------------------------------------------------------------------------- 1 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 7 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 8 | github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= 9 | github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 10 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 11 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 12 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 13 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 14 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 15 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 16 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 17 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 18 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 19 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 20 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 21 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 22 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 23 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 24 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 25 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 26 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 27 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 28 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 29 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 30 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 31 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 32 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 33 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 34 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 35 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 36 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 37 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 38 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 39 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 40 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 41 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 42 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 43 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 44 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 45 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 46 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 47 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 48 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 49 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 50 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 51 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 52 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 53 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 54 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 55 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 56 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 57 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 59 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 60 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 61 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 62 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 63 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 64 | github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= 65 | github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 66 | github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= 67 | github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 68 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 69 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 70 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 71 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 72 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 73 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 74 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 75 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 76 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 77 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 78 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 79 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 80 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 81 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 82 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 83 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 84 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 85 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 86 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 87 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 88 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 89 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 90 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 91 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 92 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 93 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 94 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 95 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 96 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 97 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 98 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 99 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 100 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 101 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 102 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 103 | golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= 104 | golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 105 | golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= 106 | golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 107 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 108 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 109 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 110 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 111 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 112 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 113 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 114 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 115 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 116 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 117 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 118 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 119 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 120 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 121 | golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= 122 | golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 123 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 124 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 125 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 126 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 127 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 128 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 129 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 130 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 131 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 132 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 133 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 134 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 135 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 136 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 137 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 138 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 139 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 140 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 141 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 142 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 143 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 144 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 145 | k8s.io/api v0.32.2 h1:bZrMLEkgizC24G9eViHGOPbW+aRo9duEISRIJKfdJuw= 146 | k8s.io/api v0.32.2/go.mod h1:hKlhk4x1sJyYnHENsrdCWw31FEmCijNGPJO5WzHiJ6Y= 147 | k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= 148 | k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= 149 | k8s.io/apimachinery v0.32.2 h1:yoQBR9ZGkA6Rgmhbp/yuT9/g+4lxtsGYwW6dR6BDPLQ= 150 | k8s.io/apimachinery v0.32.2/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 151 | k8s.io/client-go v0.32.2 h1:4dYCD4Nz+9RApM2b/3BtVvBHw54QjMFUl1OLcJG5yOA= 152 | k8s.io/client-go v0.32.2/go.mod h1:fpZ4oJXclZ3r2nDOv+Ux3XcJutfrwjKTCHz2H3sww94= 153 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 154 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 155 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= 156 | k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= 157 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 158 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 159 | sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI= 160 | sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= 161 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 162 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 163 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= 164 | sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= 165 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 166 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 167 | -------------------------------------------------------------------------------- /argocd/apps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: apps 5 | namespace: argocd-system 6 | spec: 7 | project: default 8 | source: 9 | repoURL: git@github.com:wnqueiroz/platform-engineering-backstack.git 10 | targetRevision: HEAD 11 | path: argocd/apps 12 | directory: 13 | recurse: true 14 | destination: 15 | server: https://kubernetes.default.svc 16 | namespace: argocd-system 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=true 23 | -------------------------------------------------------------------------------- /argocd/apps/backstage/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: backstage-app 5 | namespace: argocd-system 6 | spec: 7 | project: default 8 | source: 9 | repoURL: git@github.com:wnqueiroz/platform-engineering-backstack.git 10 | targetRevision: HEAD 11 | path: .bootstrap/backstage/manifests 12 | directory: 13 | recurse: true 14 | ignoreDifferences: 15 | - group: "" 16 | kind: Secret 17 | name: backstage-secrets 18 | namespace: backstage-system 19 | - group: "" 20 | kind: Secret 21 | name: postgres-secrets 22 | namespace: backstage-system 23 | destination: 24 | server: https://kubernetes.default.svc 25 | namespace: backstage-system 26 | syncPolicy: 27 | automated: 28 | prune: true 29 | selfHeal: true 30 | syncOptions: 31 | - CreateNamespace=true 32 | - Replace=true 33 | -------------------------------------------------------------------------------- /argocd/apps/crossplane/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: crossplane-app 5 | namespace: argocd-system 6 | spec: 7 | project: default 8 | source: 9 | repoURL: https://charts.crossplane.io/stable 10 | chart: crossplane 11 | targetRevision: 1.19.0 12 | helm: 13 | releaseName: crossplane 14 | destination: 15 | server: https://kubernetes.default.svc 16 | namespace: crossplane-system 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=true 23 | -------------------------------------------------------------------------------- /argocd/apps/komoplane/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: komoplane-app 5 | namespace: argocd-system 6 | spec: 7 | project: default 8 | source: 9 | repoURL: https://helm-charts.komodor.io 10 | chart: komoplane 11 | targetRevision: 0.1.6 12 | helm: 13 | releaseName: komoplane 14 | destination: 15 | server: https://kubernetes.default.svc 16 | namespace: komoplane-system 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=true 23 | -------------------------------------------------------------------------------- /argocd/apps/kyverno/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: kyverno-app 5 | namespace: argocd-system 6 | spec: 7 | project: default 8 | source: 9 | repoURL: https://kyverno.github.io/kyverno 10 | chart: kyverno 11 | targetRevision: 3.4.0 12 | helm: 13 | releaseName: kyverno 14 | valueFiles: 15 | - values.yaml 16 | destination: 17 | server: https://kubernetes.default.svc 18 | namespace: kyverno-system 19 | syncPolicy: 20 | automated: 21 | prune: true 22 | selfHeal: true 23 | syncOptions: 24 | - CreateNamespace=true 25 | - Replace=true 26 | -------------------------------------------------------------------------------- /argocd/apps/kyverno/values.yaml: -------------------------------------------------------------------------------- 1 | config: 2 | webhooks: 3 | serverTLSBootstrap: false 4 | -------------------------------------------------------------------------------- /argocd/apps/localstack/app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: localstack-app 5 | namespace: argocd-system 6 | spec: 7 | project: default 8 | source: 9 | repoURL: https://localstack.github.io/helm-charts 10 | chart: localstack 11 | targetRevision: 0.6.7 12 | helm: 13 | releaseName: localstack 14 | valueFiles: 15 | - values.yaml 16 | destination: 17 | server: https://kubernetes.default.svc 18 | namespace: localstack-system 19 | syncPolicy: 20 | automated: 21 | prune: true 22 | selfHeal: true 23 | syncOptions: 24 | - CreateNamespace=true 25 | -------------------------------------------------------------------------------- /argocd/apps/localstack/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | image: 4 | repository: localstack/localstack 5 | tag: latest 6 | 7 | service: 8 | type: ClusterIP 9 | ports: 10 | - port: 4566 11 | targetPort: 4566 12 | protocol: TCP 13 | 14 | extraEnvVars: 15 | - name: SERVICES 16 | value: "s3,sqs,ec2,iam,lambda,dynamodb" 17 | - name: DEBUG 18 | value: "1" 19 | - name: HOSTNAME 20 | value: "localhost" 21 | - name: LAMBDA_EXECUTOR 22 | value: "docker" 23 | 24 | resources: 25 | limits: 26 | memory: "1Gi" 27 | cpu: "1000m" 28 | requests: 29 | memory: "512Mi" 30 | cpu: "500m" 31 | 32 | livenessProbe: 33 | httpGet: 34 | path: /_localstack/health 35 | port: 4566 36 | initialDelaySeconds: 10 37 | periodSeconds: 5 38 | 39 | readinessProbe: 40 | httpGet: 41 | path: /_localstack/health 42 | port: 4566 43 | initialDelaySeconds: 15 44 | periodSeconds: 5 45 | -------------------------------------------------------------------------------- /argocd/crossplane-system.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: ApplicationSet 3 | metadata: 4 | name: crossplane-system 5 | namespace: argocd-system 6 | spec: 7 | generators: 8 | - list: 9 | elements: 10 | - name: providers 11 | path: crossplane/providers 12 | wave: "0" 13 | - name: providers-config 14 | path: crossplane/providers-config 15 | wave: "1" 16 | - name: functions 17 | path: crossplane/functions 18 | wave: "2" 19 | - name: compositions 20 | path: crossplane/compositions/aws 21 | wave: "3" 22 | - name: xrds 23 | path: crossplane/xrds 24 | wave: "4" 25 | - name: claims 26 | path: crossplane/claims 27 | wave: "5" 28 | 29 | template: 30 | metadata: 31 | name: crossplane-{{name}} 32 | annotations: 33 | argocd.argoproj.io/sync-wave: "{{wave}}" 34 | spec: 35 | project: default 36 | source: 37 | repoURL: git@github.com:wnqueiroz/platform-engineering-backstack.git 38 | targetRevision: HEAD 39 | path: "{{path}}" 40 | directory: 41 | recurse: true 42 | destination: 43 | server: https://kubernetes.default.svc 44 | namespace: crossplane-system 45 | syncPolicy: 46 | automated: 47 | prune: true 48 | selfHeal: true 49 | -------------------------------------------------------------------------------- /backstage/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .yarn/cache 3 | .yarn/install-state.gz 4 | node_modules 5 | packages/*/src 6 | packages/*/node_modules 7 | plugins 8 | *.local.yaml 9 | -------------------------------------------------------------------------------- /backstage/.eslintignore: -------------------------------------------------------------------------------- 1 | playwright.config.ts 2 | -------------------------------------------------------------------------------- /backstage/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | }; 4 | -------------------------------------------------------------------------------- /backstage/.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Coverage directory generated when running tests with coverage 13 | coverage 14 | 15 | # Dependencies 16 | node_modules/ 17 | 18 | # Yarn files 19 | .pnp.* 20 | .yarn/* 21 | !.yarn/patches 22 | !.yarn/plugins 23 | !.yarn/releases 24 | !.yarn/sdks 25 | !.yarn/versions 26 | 27 | # Node version directives 28 | .nvmrc 29 | 30 | # dotenv environment variables file 31 | .env 32 | .env.test 33 | 34 | # Build output 35 | dist 36 | dist-types 37 | 38 | # Temporary change files created by Vim 39 | *.swp 40 | 41 | # MkDocs build output 42 | site 43 | 44 | # Local configuration files 45 | *.local.yaml 46 | 47 | # Sensitive credentials 48 | *-credentials.yaml 49 | 50 | # vscode database functionality support files 51 | *.session.sql 52 | 53 | # E2E test reports 54 | e2e-test-report/ 55 | -------------------------------------------------------------------------------- /backstage/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-types 3 | coverage 4 | .vscode 5 | -------------------------------------------------------------------------------- /backstage/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.4.1.cjs 4 | -------------------------------------------------------------------------------- /backstage/Makefile: -------------------------------------------------------------------------------- 1 | start: 2 | @echo "Starting Backstage with --no-node-snapshot..." 3 | NODE_OPTIONS=--no-node-snapshot yarn start 4 | -------------------------------------------------------------------------------- /backstage/README.md: -------------------------------------------------------------------------------- 1 | # [Backstage](https://backstage.io) 2 | 3 | This is your newly scaffolded Backstage App, Good Luck! 4 | 5 | To start the app, run: 6 | 7 | ```sh 8 | yarn install 9 | yarn start 10 | ``` 11 | -------------------------------------------------------------------------------- /backstage/app-config.production.yaml: -------------------------------------------------------------------------------- 1 | app: 2 | # Should be the same as backend.baseUrl when using the `app-backend` plugin. 3 | baseUrl: http://localhost:7007 4 | 5 | backend: 6 | # Note that the baseUrl should be the URL that the browser and other clients 7 | # should use when communicating with the backend, i.e. it needs to be 8 | # reachable not just from within the backend host, but from all of your 9 | # callers. When its value is "http://localhost:7007", it's strictly private 10 | # and can't be reached by others. 11 | baseUrl: http://localhost:7007 12 | # The listener can also be expressed as a single : string. In this case we bind to 13 | # all interfaces, the most permissive setting. The right value depends on your specific deployment. 14 | listen: ':7007' 15 | 16 | cors: 17 | origin: http://localhost:7007 18 | 19 | # config options: https://node-postgres.com/apis/client 20 | database: 21 | client: pg 22 | connection: 23 | host: ${POSTGRES_HOST} 24 | port: ${POSTGRES_PORT} 25 | user: ${POSTGRES_USER} 26 | password: ${POSTGRES_PASSWORD} 27 | # https://node-postgres.com/features/ssl 28 | # you can set the sslmode configuration option via the `PGSSLMODE` environment variable 29 | # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) 30 | # ssl: 31 | # ca: # if you have a CA file and want to verify it you can uncomment this section 32 | # $file: /ca/server.crt 33 | 34 | integrations: 35 | github: 36 | - host: github.com 37 | # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information 38 | # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration 39 | token: ${GITHUB_TOKEN} 40 | ### Example for how to add your GitHub Enterprise instance using the API: 41 | # - host: ghe.example.net 42 | # apiBaseUrl: https://ghe.example.net/api/v3 43 | # token: ${GHE_TOKEN} 44 | 45 | auth: 46 | providers: 47 | # See https://backstage.io/docs/auth/guest/provider 48 | guest: 49 | dangerouslyAllowOutsideDevelopment: true # Development purposes 50 | 51 | catalog: 52 | import: 53 | entityFilename: catalog-info.yaml 54 | pullRequestBranchName: backstage-integration 55 | rules: 56 | - allow: [Component, System, API, Resource, Location] 57 | locations: 58 | # All Templates 59 | - type: url 60 | target: https://github.com/wnqueiroz/platform-engineering-backstack/blob/main/backstage/catalog/all.yaml 61 | rules: 62 | - allow: [Template, System, User, Group] 63 | 64 | # Experimental: Always use the search method in UrlReaderProcessor. 65 | # New adopters are encouraged to enable it as this behavior will be the default in a future release. 66 | useUrlReadersSearch: true 67 | 68 | kubernetes: 69 | # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options 70 | frontend: 71 | podDelete: 72 | enabled: true 73 | serviceLocatorMethod: 74 | type: 'multiTenant' 75 | clusterLocatorMethods: 76 | - type: 'config' 77 | clusters: 78 | - url: https://kubernetes.default.svc 79 | name: kind 80 | authProvider: 'serviceAccount' 81 | skipTLSVerify: true 82 | skipMetricsLookup: true 83 | serviceAccountToken: ${SERVICE_ACCOUNT_TOKEN} 84 | serviceAccountNamespace: backstage-system 85 | -------------------------------------------------------------------------------- /backstage/app-config.yaml: -------------------------------------------------------------------------------- 1 | app: 2 | title: Scaffolded Backstage App 3 | baseUrl: http://localhost:3000 4 | 5 | organization: 6 | name: My Company 7 | 8 | backend: 9 | # Used for enabling authentication, secret is shared by all backend plugins 10 | # See https://backstage.io/docs/auth/service-to-service-auth for 11 | # information on the format 12 | # auth: 13 | # keys: 14 | # - secret: ${BACKEND_SECRET} 15 | baseUrl: http://localhost:7007 16 | listen: 17 | port: 7007 18 | # Uncomment the following host directive to bind to specific interfaces 19 | # host: 127.0.0.1 20 | csp: 21 | connect-src: ["'self'", 'http:', 'https:'] 22 | # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference 23 | # Default Helmet Content-Security-Policy values can be removed by setting the key to false 24 | cors: 25 | origin: http://localhost:3000 26 | methods: [GET, HEAD, PATCH, POST, PUT, DELETE] 27 | credentials: true 28 | # This is for local development only, it is not recommended to use this in production 29 | # The production database configuration is stored in app-config.production.yaml 30 | database: 31 | client: better-sqlite3 32 | connection: ':memory:' 33 | # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir 34 | 35 | integrations: 36 | github: 37 | - host: github.com 38 | # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information 39 | # about setting up the GitHub integration here: https://backstage.io/docs/integrations/github/locations#configuration 40 | token: ${GITHUB_TOKEN} 41 | ### Example for how to add your GitHub Enterprise instance using the API: 42 | # - host: ghe.example.net 43 | # apiBaseUrl: https://ghe.example.net/api/v3 44 | # token: ${GHE_TOKEN} 45 | 46 | proxy: 47 | ### Example for how to add a proxy endpoint for the frontend. 48 | ### A typical reason to do this is to handle HTTPS and CORS for internal services. 49 | # endpoints: 50 | # '/test': 51 | # target: 'https://example.com' 52 | # changeOrigin: true 53 | 54 | # Reference documentation http://backstage.io/docs/features/techdocs/configuration 55 | # Note: After experimenting with basic setup, use CI/CD to generate docs 56 | # and an external cloud storage when deploying TechDocs for production use-case. 57 | # https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach 58 | techdocs: 59 | builder: 'local' # Alternatives - 'external' 60 | generator: 61 | runIn: 'docker' # Alternatives - 'local' 62 | publisher: 63 | type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives. 64 | 65 | auth: 66 | # see https://backstage.io/docs/auth/ to learn about auth providers 67 | providers: 68 | # See https://backstage.io/docs/auth/guest/provider 69 | guest: {} 70 | 71 | scaffolder: 72 | # see https://backstage.io/docs/features/software-templates/configuration for software template options 73 | 74 | catalog: 75 | import: 76 | entityFilename: catalog-info.yaml 77 | pullRequestBranchName: backstage-integration 78 | rules: 79 | - allow: [Component, System, API, Resource, Location] 80 | locations: 81 | # All Templates 82 | - type: url 83 | target: https://github.com/wnqueiroz/platform-engineering-backstack/blob/main/backstage/catalog/all.yaml 84 | rules: 85 | - allow: [Template, System, User, Group] 86 | 87 | # Local example data, file locations are relative to the backend process, typically `packages/backend` 88 | - type: file 89 | target: ../../catalog/components.yaml 90 | 91 | # # Local example template 92 | # - type: file 93 | # target: ../../examples/template/template.yaml 94 | # rules: 95 | # - allow: [Template] 96 | 97 | # Local example organizational data 98 | - type: file 99 | target: ../../examples/org.yaml 100 | rules: 101 | - allow: [User, Group] 102 | 103 | ## Uncomment these lines to add more example data 104 | # - type: url 105 | # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml 106 | 107 | ## Uncomment these lines to add an example org 108 | # - type: url 109 | # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml 110 | # rules: 111 | # - allow: [User, Group] 112 | # Experimental: Always use the search method in UrlReaderProcessor. 113 | # New adopters are encouraged to enable it as this behavior will be the default in a future release. 114 | useUrlReadersSearch: true 115 | 116 | kubernetes: 117 | # see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options 118 | frontend: 119 | podDelete: 120 | enabled: true 121 | serviceLocatorMethod: 122 | type: 'multiTenant' 123 | clusterLocatorMethods: 124 | - type: 'config' 125 | clusters: 126 | - url: https://127.0.0.1:52484 127 | name: kind 128 | authProvider: 'serviceAccount' 129 | skipTLSVerify: true 130 | skipMetricsLookup: true 131 | serviceAccountToken: ${SERVICE_ACCOUNT_TOKEN} 132 | serviceAccountNamespace: backstage-system 133 | 134 | # see https://backstage.io/docs/permissions/getting-started for more on the permission framework 135 | permission: 136 | # setting this to `false` will disable permissions 137 | enabled: true 138 | -------------------------------------------------------------------------------- /backstage/backstage.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.38.0" 3 | } 4 | -------------------------------------------------------------------------------- /backstage/catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: backstage 5 | description: An example of a Backstage application. 6 | # Example for optional annotations 7 | # annotations: 8 | # github.com/project-slug: backstage/backstage 9 | # backstage.io/techdocs-ref: dir:. 10 | spec: 11 | type: website 12 | owner: john@example.com 13 | lifecycle: experimental 14 | -------------------------------------------------------------------------------- /backstage/catalog/all.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Location 3 | metadata: 4 | name: all 5 | title: 'All' 6 | spec: 7 | type: url 8 | targets: 9 | - ./templates/xqueue-claim/template.yaml 10 | - ./components.yaml 11 | - ../examples/org.yaml 12 | -------------------------------------------------------------------------------- /backstage/catalog/components.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: System 3 | metadata: 4 | name: backstage 5 | description: Represents the Backstage developer portal ecosystem. 6 | spec: 7 | owner: guests 8 | --- 9 | apiVersion: backstage.io/v1alpha1 10 | kind: System 11 | metadata: 12 | name: infrastructure 13 | description: Manages infrastructure resources provisioned through Crossplane across environments and cloud accounts. 14 | spec: 15 | owner: guests 16 | --- 17 | apiVersion: backstage.io/v1alpha1 18 | kind: System 19 | metadata: 20 | name: localstack 21 | description: Represents the local development environment that emulates AWS services through LocalStack for testing infrastructure provisioning and integrations. 22 | spec: 23 | owner: guests 24 | --- 25 | apiVersion: backstage.io/v1alpha1 26 | kind: System 27 | metadata: 28 | name: argocd 29 | description: Represents the Argo CD system responsible for GitOps-based application deployment and continuous delivery workflows. 30 | spec: 31 | owner: guests 32 | --- 33 | apiVersion: backstage.io/v1alpha1 34 | kind: System 35 | metadata: 36 | name: komoplane 37 | description: Represents the Komoplane system, offering a web-based UI to enhance the visibility and management of Crossplane resources. 38 | spec: 39 | owner: guests 40 | --- 41 | apiVersion: backstage.io/v1alpha1 42 | kind: Component 43 | metadata: 44 | name: backstage 45 | description: Backstage is an internal developer portal that centralizes software components, documentation, and tools to streamline developer workflows. 46 | annotations: 47 | backstage.io/kubernetes-id: backstage 48 | links: 49 | - url: http://localhost:3000 50 | title: Backstage UI 51 | icon: dashboard 52 | spec: 53 | type: service 54 | lifecycle: production 55 | owner: guests 56 | system: backstage 57 | --- 58 | apiVersion: backstage.io/v1alpha1 59 | kind: Component 60 | metadata: 61 | name: backstage-postgres 62 | description: PostgreSQL database instance used to persist Backstage metadata and catalog information. 63 | annotations: 64 | backstage.io/kubernetes-id: backstage-postgres 65 | spec: 66 | type: service 67 | lifecycle: production 68 | owner: guests 69 | system: backstage 70 | --- 71 | apiVersion: backstage.io/v1alpha1 72 | kind: Component 73 | metadata: 74 | name: localstack 75 | description: LocalStack simulates AWS cloud services locally, enabling development and testing of infrastructure components without requiring access to real AWS environments. 76 | annotations: 77 | backstage.io/kubernetes-id: localstack 78 | spec: 79 | type: service 80 | lifecycle: production 81 | owner: guests 82 | system: localstack 83 | --- 84 | apiVersion: backstage.io/v1alpha1 85 | kind: Component 86 | metadata: 87 | name: argocd-server 88 | description: Argo CD Server component responsible for providing the user interface and API server functionalities for managing application deployments through GitOps workflows. 89 | annotations: 90 | backstage.io/kubernetes-id: argocd-server 91 | spec: 92 | type: service 93 | lifecycle: production 94 | owner: guests 95 | system: argocd 96 | --- 97 | apiVersion: backstage.io/v1alpha1 98 | kind: Component 99 | metadata: 100 | name: komoplane 101 | description: Komoplane provides a user-friendly interface for visualizing, managing, and interacting with Crossplane resources across clusters. 102 | annotations: 103 | backstage.io/kubernetes-id: komoplane 104 | spec: 105 | type: service 106 | lifecycle: production 107 | owner: guests 108 | system: komoplane 109 | 110 | -------------------------------------------------------------------------------- /backstage/catalog/templates/application/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/backstage/catalog/templates/application/.gitkeep -------------------------------------------------------------------------------- /backstage/catalog/templates/xqueue-claim/skeleton/${{values.queueName}}.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: ${{ values.queueName }} 5 | spec: 6 | location: ${{ values.location }} 7 | providerName: ${{ values.providerName }} 8 | visibilityTimeoutSeconds: ${{ values.visibilityTimeoutSeconds }} 9 | maxMessageSize: ${{ values.maxMessageSize }} 10 | {%- if values.tags %} 11 | tags: 12 | {%- for key, val in values.tags %} 13 | ${{ key }}: ${{ val }} 14 | {%- endfor %} 15 | {%- endif %} 16 | -------------------------------------------------------------------------------- /backstage/catalog/templates/xqueue-claim/template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scaffolder.backstage.io/v1beta3 2 | kind: Template 3 | metadata: 4 | name: create-xqueue-claim 5 | title: Create XQueue Claim 6 | description: Creates a Crossplane XQueueClaim and opens a PR with the YAML. 7 | spec: 8 | owner: guests 9 | type: infrastructure 10 | 11 | parameters: 12 | - title: Queue Configuration 13 | required: [queueName, location, providerName] 14 | properties: 15 | queueName: 16 | type: string 17 | title: Queue name 18 | pattern: '^[a-z0-9]+(-[a-z0-9]+)*$' 19 | description: Must be in kebab-case (e.g. "my-queue-name") 20 | errorMessage: Queue name must be in kebab-case, using only lowercase letters, numbers, and hyphens (e.g. "my-queue-name") 21 | location: 22 | type: string 23 | title: Location 24 | enum: [EU, US] 25 | providerName: 26 | type: string 27 | title: Provider Name 28 | description: Select the provider to use for this queue. 29 | enum: 30 | - default 31 | enumNames: 32 | - Localstack 33 | visibilityTimeoutSeconds: 34 | type: integer 35 | title: Visibility Timeout 36 | default: 30 37 | description: The duration (in seconds) that a received message is hidden from subsequent receive requests after being retrieved from the queue. 38 | maxMessageSize: 39 | type: integer 40 | title: Max Message Size (bytes) 41 | default: 262144 42 | description: The limit of how many bytes a message can contain before being rejected. # Valid values: 1024 (1 KiB) to 262144 (256 KiB). 43 | tags: 44 | type: object 45 | title: Tags (optional) 46 | description: Key-value tags to help identify or categorize the queue. 47 | additionalProperties: 48 | type: string 49 | default: {} 50 | 51 | steps: 52 | - id: fetch-template 53 | name: Render local XQueueClaim template 54 | action: fetch:template 55 | input: 56 | url: ./skeleton 57 | targetPath: ./output 58 | values: 59 | queueName: ${{ parameters.queueName }} 60 | location: ${{ parameters.location }} 61 | providerName: ${{ parameters.providerName }} 62 | visibilityTimeoutSeconds: ${{ parameters.visibilityTimeoutSeconds }} 63 | maxMessageSize: ${{ parameters.maxMessageSize }} 64 | tags: ${{ parameters.tags }} 65 | 66 | - id: publish-pr 67 | name: Publish to GitHub 68 | action: publish:github:pull-request 69 | input: 70 | allowedHosts: ['github.com'] 71 | repoUrl: github.com?repo=platform-engineering-backstack&owner=wnqueiroz 72 | branchName: feature/add-xqueue-${{ parameters.queueName }} 73 | title: 'feat(xqueue): add claim for ${{ parameters.queueName }}' 74 | description: | 75 | This PR was automatically created via [Backstage Scaffolder](https://backstage.io/). 76 | 77 | A new **XQueueClaim** has been scaffolded with the following configuration: 78 | 79 | - **Queue Name**: `${{ parameters.queueName }}` 80 | - **Location**: `${{ parameters.location }}` 81 | - **Provider**: `${{ parameters.providerName }}` 82 | - **Visibility Timeout**: `${{ parameters.visibilityTimeoutSeconds }}s` 83 | - **Max Message Size**: `${{ parameters.maxMessageSize }} bytes` 84 | 85 | > Tags and additional configuration can be found in the generated YAML file. 86 | sourcePath: ./output 87 | targetPath: crossplane/claims 88 | 89 | output: 90 | links: 91 | - title: View Pull Request 92 | icon: github 93 | url: ${{ steps['publish-pr'].output.remoteUrl }} 94 | -------------------------------------------------------------------------------- /backstage/examples/entities.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system 3 | apiVersion: backstage.io/v1alpha1 4 | kind: System 5 | metadata: 6 | name: examples 7 | spec: 8 | owner: guests 9 | --- 10 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component 11 | apiVersion: backstage.io/v1alpha1 12 | kind: Component 13 | metadata: 14 | name: example-website 15 | spec: 16 | type: website 17 | lifecycle: experimental 18 | owner: guests 19 | system: examples 20 | providesApis: [example-grpc-api] 21 | --- 22 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api 23 | apiVersion: backstage.io/v1alpha1 24 | kind: API 25 | metadata: 26 | name: example-grpc-api 27 | spec: 28 | type: grpc 29 | lifecycle: experimental 30 | owner: guests 31 | system: examples 32 | definition: | 33 | syntax = "proto3"; 34 | 35 | service Exampler { 36 | rpc Example (ExampleMessage) returns (ExampleMessage) {}; 37 | } 38 | 39 | message ExampleMessage { 40 | string example = 1; 41 | }; 42 | -------------------------------------------------------------------------------- /backstage/examples/org.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user 3 | apiVersion: backstage.io/v1alpha1 4 | kind: User 5 | metadata: 6 | name: guest 7 | spec: 8 | memberOf: [guests] 9 | --- 10 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group 11 | apiVersion: backstage.io/v1alpha1 12 | kind: Group 13 | metadata: 14 | name: guests 15 | spec: 16 | type: team 17 | children: [] 18 | -------------------------------------------------------------------------------- /backstage/examples/template/content/catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: ${{ values.name | dump }} 5 | spec: 6 | type: service 7 | owner: user:guest 8 | lifecycle: experimental 9 | -------------------------------------------------------------------------------- /backstage/examples/template/content/index.js: -------------------------------------------------------------------------------- 1 | console.log('Hello from ${{ values.name }}!'); 2 | -------------------------------------------------------------------------------- /backstage/examples/template/content/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "${{ values.name }}", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /backstage/examples/template/template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scaffolder.backstage.io/v1beta3 2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template 3 | kind: Template 4 | metadata: 5 | name: example-nodejs-template 6 | title: Example Node.js Template 7 | description: An example template for the scaffolder that creates a simple Node.js service 8 | spec: 9 | owner: user:guest 10 | type: service 11 | 12 | # These parameters are used to generate the input form in the frontend, and are 13 | # used to gather input data for the execution of the template. 14 | parameters: 15 | - title: Fill in some steps 16 | required: 17 | - name 18 | properties: 19 | name: 20 | title: Name 21 | type: string 22 | description: Unique name of the component 23 | ui:autofocus: true 24 | ui:options: 25 | rows: 5 26 | - title: Choose a location 27 | required: 28 | - repoUrl 29 | properties: 30 | repoUrl: 31 | title: Repository Location 32 | type: string 33 | ui:field: RepoUrlPicker 34 | ui:options: 35 | allowedHosts: 36 | - github.com 37 | 38 | # These steps are executed in the scaffolder backend, using data that we gathered 39 | # via the parameters above. 40 | steps: 41 | # Each step executes an action, in this case one templates files into the working directory. 42 | - id: fetch-base 43 | name: Fetch Base 44 | action: fetch:template 45 | input: 46 | url: ./content 47 | values: 48 | name: ${{ parameters.name }} 49 | 50 | # This step publishes the contents of the working directory to GitHub. 51 | # If you or your organization prefer another default branch name over 'main' 52 | # you can change that here. 53 | - id: publish 54 | name: Publish 55 | action: publish:github 56 | input: 57 | allowedHosts: ['github.com'] 58 | description: This is ${{ parameters.name }} 59 | repoUrl: ${{ parameters.repoUrl }} 60 | defaultBranch: 'main' 61 | 62 | # The final step is to register our new component in the catalog. 63 | - id: register 64 | name: Register 65 | action: catalog:register 66 | input: 67 | repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} 68 | catalogInfoPath: '/catalog-info.yaml' 69 | 70 | # Outputs are displayed to the user after a successful execution of the template. 71 | output: 72 | links: 73 | - title: Repository 74 | url: ${{ steps['publish'].output.remoteUrl }} 75 | - title: Open in catalog 76 | icon: catalog 77 | entityRef: ${{ steps['register'].output.entityRef }} 78 | -------------------------------------------------------------------------------- /backstage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "version": "1.0.0", 4 | "private": true, 5 | "engines": { 6 | "node": "20 || 22" 7 | }, 8 | "scripts": { 9 | "start": "NODE_OPTIONS=--no-node-snapshot backstage-cli repo start", 10 | "build:backend": "yarn workspace backend build", 11 | "build:all": "backstage-cli repo build --all", 12 | "build-image": "yarn workspace backend build-image", 13 | "tsc": "tsc", 14 | "tsc:full": "tsc --skipLibCheck false --incremental false", 15 | "clean": "backstage-cli repo clean", 16 | "test": "backstage-cli repo test", 17 | "test:all": "backstage-cli repo test --coverage", 18 | "test:e2e": "playwright test", 19 | "fix": "backstage-cli repo fix", 20 | "lint": "backstage-cli repo lint --since origin/main", 21 | "lint:all": "backstage-cli repo lint", 22 | "prettier:check": "prettier --check .", 23 | "new": "backstage-cli new" 24 | }, 25 | "workspaces": { 26 | "packages": [ 27 | "packages/*", 28 | "plugins/*" 29 | ] 30 | }, 31 | "devDependencies": { 32 | "@backstage/cli": "^0.32.0", 33 | "@backstage/e2e-test-utils": "^0.1.1", 34 | "@playwright/test": "^1.32.3", 35 | "node-gyp": "^10.0.0", 36 | "prettier": "^2.3.2", 37 | "typescript": "~5.8.0" 38 | }, 39 | "resolutions": { 40 | "@types/react": "^18", 41 | "@types/react-dom": "^18" 42 | }, 43 | "prettier": "@backstage/cli/config/prettier", 44 | "lint-staged": { 45 | "*.{js,jsx,ts,tsx,mjs,cjs}": [ 46 | "eslint --fix", 47 | "prettier --write" 48 | ], 49 | "*.{json,md}": [ 50 | "prettier --write" 51 | ] 52 | }, 53 | "packageManager": "yarn@4.4.1" 54 | } 55 | -------------------------------------------------------------------------------- /backstage/packages/README.md: -------------------------------------------------------------------------------- 1 | # The Packages Folder 2 | 3 | This is where your own applications and centrally managed libraries live, each 4 | in a separate folder of its own. 5 | 6 | From the start there's an `app` folder (for the frontend) and a `backend` folder 7 | (for the Node backend), but you can also add more modules in here that house 8 | your core additions and adaptations, such as themes, common React component 9 | libraries, utilities, and similar. 10 | -------------------------------------------------------------------------------- /backstage/packages/app/.eslintignore: -------------------------------------------------------------------------------- 1 | public 2 | -------------------------------------------------------------------------------- /backstage/packages/app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); 2 | -------------------------------------------------------------------------------- /backstage/packages/app/e2e-tests/app.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 The Backstage Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { test, expect } from '@playwright/test'; 18 | 19 | test('App should render the welcome page', async ({ page }) => { 20 | await page.goto('/'); 21 | 22 | const enterButton = page.getByRole('button', { name: 'Enter' }); 23 | await expect(enterButton).toBeVisible(); 24 | await enterButton.click(); 25 | 26 | await expect(page.getByText('My Company Catalog')).toBeVisible(); 27 | }); 28 | -------------------------------------------------------------------------------- /backstage/packages/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "bundled": true, 6 | "backstage": { 7 | "role": "frontend" 8 | }, 9 | "scripts": { 10 | "start": "backstage-cli package start", 11 | "build": "backstage-cli package build", 12 | "clean": "backstage-cli package clean", 13 | "test": "backstage-cli package test", 14 | "lint": "backstage-cli package lint" 15 | }, 16 | "dependencies": { 17 | "@backstage/app-defaults": "^1.6.1", 18 | "@backstage/canon": "^0.3.0", 19 | "@backstage/catalog-model": "^1.7.3", 20 | "@backstage/cli": "^0.32.0", 21 | "@backstage/core-app-api": "^1.16.1", 22 | "@backstage/core-components": "^0.17.1", 23 | "@backstage/core-plugin-api": "^1.10.6", 24 | "@backstage/integration-react": "^1.2.6", 25 | "@backstage/plugin-api-docs": "^0.12.6", 26 | "@backstage/plugin-catalog": "^1.29.0", 27 | "@backstage/plugin-catalog-common": "^1.1.3", 28 | "@backstage/plugin-catalog-graph": "^0.4.18", 29 | "@backstage/plugin-catalog-import": "^0.12.13", 30 | "@backstage/plugin-catalog-react": "^1.17.0", 31 | "@backstage/plugin-kubernetes": "^0.12.6", 32 | "@backstage/plugin-org": "^0.6.38", 33 | "@backstage/plugin-permission-react": "^0.4.33", 34 | "@backstage/plugin-scaffolder": "^1.30.0", 35 | "@backstage/plugin-search": "^1.4.25", 36 | "@backstage/plugin-search-react": "^1.8.8", 37 | "@backstage/plugin-techdocs": "^1.12.5", 38 | "@backstage/plugin-techdocs-module-addons-contrib": "^1.1.23", 39 | "@backstage/plugin-techdocs-react": "^1.2.16", 40 | "@backstage/plugin-user-settings": "^0.8.21", 41 | "@backstage/theme": "^0.6.5", 42 | "@material-ui/core": "^4.12.2", 43 | "@material-ui/icons": "^4.9.1", 44 | "react": "^18.0.2", 45 | "react-dom": "^18.0.2", 46 | "react-router": "^6.3.0", 47 | "react-router-dom": "^6.3.0" 48 | }, 49 | "devDependencies": { 50 | "@backstage/test-utils": "^1.7.7", 51 | "@playwright/test": "^1.32.3", 52 | "@testing-library/dom": "^9.0.0", 53 | "@testing-library/jest-dom": "^6.0.0", 54 | "@testing-library/react": "^14.0.0", 55 | "@testing-library/user-event": "^14.0.0", 56 | "@types/react-dom": "*", 57 | "cross-env": "^7.0.0" 58 | }, 59 | "browserslist": { 60 | "production": [ 61 | ">0.2%", 62 | "not dead", 63 | "not op_mini all" 64 | ], 65 | "development": [ 66 | "last 1 chrome version", 67 | "last 1 firefox version", 68 | "last 1 safari version" 69 | ] 70 | }, 71 | "files": [ 72 | "dist" 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /backstage/packages/app/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/backstage/packages/app/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /backstage/packages/app/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/backstage/packages/app/public/apple-touch-icon.png -------------------------------------------------------------------------------- /backstage/packages/app/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/backstage/packages/app/public/favicon-16x16.png -------------------------------------------------------------------------------- /backstage/packages/app/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/backstage/packages/app/public/favicon-32x32.png -------------------------------------------------------------------------------- /backstage/packages/app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wnqueiroz/platform-engineering-backstack/c6c7e157431bb538237f26c409110d2a1cb815b3/backstage/packages/app/public/favicon.ico -------------------------------------------------------------------------------- /backstage/packages/app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 15 | 20 | 21 | 22 | 27 | 33 | 39 | 44 | <%= config.getOptionalString('app.title') ?? 'Backstage' %> 45 | 46 | 47 | 48 |
49 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /backstage/packages/app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Backstage", 3 | "name": "Backstage", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "48x48", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /backstage/packages/app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /backstage/packages/app/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | Created by potrace 1.11, written by Peter Selinger 2001-2013 -------------------------------------------------------------------------------- /backstage/packages/app/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, waitFor } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | describe('App', () => { 5 | it('should render', async () => { 6 | process.env = { 7 | NODE_ENV: 'test', 8 | APP_CONFIG: [ 9 | { 10 | data: { 11 | app: { title: 'Test' }, 12 | backend: { baseUrl: 'http://localhost:7007' }, 13 | techdocs: { 14 | storageUrl: 'http://localhost:7007/api/techdocs/static/docs', 15 | }, 16 | }, 17 | context: 'test', 18 | }, 19 | ] as any, 20 | }; 21 | 22 | const rendered = render(); 23 | 24 | await waitFor(() => { 25 | expect(rendered.baseElement).toBeInTheDocument(); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /backstage/packages/app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate, Route } from 'react-router-dom'; 2 | import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; 3 | import { 4 | CatalogEntityPage, 5 | CatalogIndexPage, 6 | catalogPlugin, 7 | } from '@backstage/plugin-catalog'; 8 | import { 9 | CatalogImportPage, 10 | catalogImportPlugin, 11 | } from '@backstage/plugin-catalog-import'; 12 | import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; 13 | import { orgPlugin } from '@backstage/plugin-org'; 14 | import { SearchPage } from '@backstage/plugin-search'; 15 | import { 16 | TechDocsIndexPage, 17 | techdocsPlugin, 18 | TechDocsReaderPage, 19 | } from '@backstage/plugin-techdocs'; 20 | import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; 21 | import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; 22 | import { UserSettingsPage } from '@backstage/plugin-user-settings'; 23 | import { apis } from './apis'; 24 | import { entityPage } from './components/catalog/EntityPage'; 25 | import { searchPage } from './components/search/SearchPage'; 26 | import { Root } from './components/Root'; 27 | 28 | import { 29 | AlertDisplay, 30 | OAuthRequestDialog, 31 | SignInPage, 32 | } from '@backstage/core-components'; 33 | import { createApp } from '@backstage/app-defaults'; 34 | import { AppRouter, FlatRoutes } from '@backstage/core-app-api'; 35 | import { CatalogGraphPage } from '@backstage/plugin-catalog-graph'; 36 | import { RequirePermission } from '@backstage/plugin-permission-react'; 37 | import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; 38 | 39 | const app = createApp({ 40 | apis, 41 | bindRoutes({ bind }) { 42 | bind(catalogPlugin.externalRoutes, { 43 | createComponent: scaffolderPlugin.routes.root, 44 | viewTechDoc: techdocsPlugin.routes.docRoot, 45 | createFromTemplate: scaffolderPlugin.routes.selectedTemplate, 46 | }); 47 | bind(apiDocsPlugin.externalRoutes, { 48 | registerApi: catalogImportPlugin.routes.importPage, 49 | }); 50 | bind(scaffolderPlugin.externalRoutes, { 51 | registerComponent: catalogImportPlugin.routes.importPage, 52 | viewTechDoc: techdocsPlugin.routes.docRoot, 53 | }); 54 | bind(orgPlugin.externalRoutes, { 55 | catalogIndex: catalogPlugin.routes.catalogIndex, 56 | }); 57 | }, 58 | components: { 59 | SignInPage: props => , 60 | }, 61 | }); 62 | 63 | const routes = ( 64 | 65 | } /> 66 | } /> 67 | } 70 | > 71 | {entityPage} 72 | 73 | } /> 74 | } 77 | > 78 | 79 | 80 | 81 | 82 | } /> 83 | } /> 84 | 88 | 89 | 90 | } 91 | /> 92 | }> 93 | {searchPage} 94 | 95 | } /> 96 | } /> 97 | 98 | ); 99 | 100 | export default app.createRoot( 101 | <> 102 | 103 | 104 | 105 | {routes} 106 | 107 | , 108 | ); 109 | -------------------------------------------------------------------------------- /backstage/packages/app/src/apis.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ScmIntegrationsApi, 3 | scmIntegrationsApiRef, 4 | ScmAuth, 5 | } from '@backstage/integration-react'; 6 | import { 7 | AnyApiFactory, 8 | configApiRef, 9 | createApiFactory, 10 | } from '@backstage/core-plugin-api'; 11 | 12 | export const apis: AnyApiFactory[] = [ 13 | createApiFactory({ 14 | api: scmIntegrationsApiRef, 15 | deps: { configApi: configApiRef }, 16 | factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), 17 | }), 18 | ScmAuth.createDefaultApiFactory(), 19 | ]; 20 | -------------------------------------------------------------------------------- /backstage/packages/app/src/components/Root/LogoFull.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core'; 2 | 3 | const useStyles = makeStyles({ 4 | svg: { 5 | width: 'auto', 6 | height: 30, 7 | }, 8 | path: { 9 | fill: '#7df3e1', 10 | }, 11 | }); 12 | const LogoFull = () => { 13 | const classes = useStyles(); 14 | 15 | return ( 16 | 21 | 25 | 26 | ); 27 | }; 28 | 29 | export default LogoFull; 30 | -------------------------------------------------------------------------------- /backstage/packages/app/src/components/Root/LogoIcon.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@material-ui/core'; 2 | 3 | const useStyles = makeStyles({ 4 | svg: { 5 | width: 'auto', 6 | height: 28, 7 | }, 8 | path: { 9 | fill: '#7df3e1', 10 | }, 11 | }); 12 | 13 | const LogoIcon = () => { 14 | const classes = useStyles(); 15 | 16 | return ( 17 | 22 | 26 | 27 | ); 28 | }; 29 | 30 | export default LogoIcon; 31 | -------------------------------------------------------------------------------- /backstage/packages/app/src/components/Root/Root.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { makeStyles } from '@material-ui/core'; 3 | import HomeIcon from '@material-ui/icons/Home'; 4 | import ExtensionIcon from '@material-ui/icons/Extension'; 5 | import LibraryBooks from '@material-ui/icons/LibraryBooks'; 6 | import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; 7 | import LogoFull from './LogoFull'; 8 | import LogoIcon from './LogoIcon'; 9 | import { 10 | Settings as SidebarSettings, 11 | UserSettingsSignInAvatar, 12 | } from '@backstage/plugin-user-settings'; 13 | import { SidebarSearchModal } from '@backstage/plugin-search'; 14 | import { 15 | Sidebar, 16 | sidebarConfig, 17 | SidebarDivider, 18 | SidebarGroup, 19 | SidebarItem, 20 | SidebarPage, 21 | SidebarScrollWrapper, 22 | SidebarSpace, 23 | useSidebarOpenState, 24 | Link, 25 | } from '@backstage/core-components'; 26 | import MenuIcon from '@material-ui/icons/Menu'; 27 | import SearchIcon from '@material-ui/icons/Search'; 28 | import { MyGroupsSidebarItem } from '@backstage/plugin-org'; 29 | import GroupIcon from '@material-ui/icons/People'; 30 | 31 | const useSidebarLogoStyles = makeStyles({ 32 | root: { 33 | width: sidebarConfig.drawerWidthClosed, 34 | height: 3 * sidebarConfig.logoHeight, 35 | display: 'flex', 36 | flexFlow: 'row nowrap', 37 | alignItems: 'center', 38 | marginBottom: -14, 39 | }, 40 | link: { 41 | width: sidebarConfig.drawerWidthClosed, 42 | marginLeft: 24, 43 | }, 44 | }); 45 | 46 | const SidebarLogo = () => { 47 | const classes = useSidebarLogoStyles(); 48 | const { isOpen } = useSidebarOpenState(); 49 | 50 | return ( 51 |
52 | 53 | {isOpen ? : } 54 | 55 |
56 | ); 57 | }; 58 | 59 | export const Root = ({ children }: PropsWithChildren<{}>) => ( 60 | 61 | 62 | 63 | } to="/search"> 64 | 65 | 66 | 67 | }> 68 | {/* Global nav, not org-specific */} 69 | 70 | 75 | 76 | 77 | 78 | {/* End global nav */} 79 | 80 | 81 | {/* Items in this group will be scrollable if they run out of space */} 82 | 83 | 84 | 85 | 86 | } 89 | to="/settings" 90 | > 91 | 92 | 93 | 94 | {children} 95 | 96 | ); 97 | -------------------------------------------------------------------------------- /backstage/packages/app/src/components/Root/index.ts: -------------------------------------------------------------------------------- 1 | export { Root } from './Root'; 2 | -------------------------------------------------------------------------------- /backstage/packages/app/src/components/catalog/EntityPage.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Grid } from '@material-ui/core'; 2 | import { 3 | EntityApiDefinitionCard, 4 | EntityConsumedApisCard, 5 | EntityConsumingComponentsCard, 6 | EntityHasApisCard, 7 | EntityProvidedApisCard, 8 | EntityProvidingComponentsCard, 9 | } from '@backstage/plugin-api-docs'; 10 | import { 11 | EntityAboutCard, 12 | EntityDependsOnComponentsCard, 13 | EntityDependsOnResourcesCard, 14 | EntityHasComponentsCard, 15 | EntityHasResourcesCard, 16 | EntityHasSubcomponentsCard, 17 | EntityHasSystemsCard, 18 | EntityLayout, 19 | EntityLinksCard, 20 | EntitySwitch, 21 | EntityOrphanWarning, 22 | EntityProcessingErrorsPanel, 23 | isComponentType, 24 | isKind, 25 | hasCatalogProcessingErrors, 26 | isOrphan, 27 | hasRelationWarnings, 28 | EntityRelationWarning, 29 | } from '@backstage/plugin-catalog'; 30 | import { 31 | EntityUserProfileCard, 32 | EntityGroupProfileCard, 33 | EntityMembersListCard, 34 | EntityOwnershipCard, 35 | } from '@backstage/plugin-org'; 36 | import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; 37 | import { EmptyState } from '@backstage/core-components'; 38 | import { 39 | Direction, 40 | EntityCatalogGraphCard, 41 | } from '@backstage/plugin-catalog-graph'; 42 | import { 43 | RELATION_API_CONSUMED_BY, 44 | RELATION_API_PROVIDED_BY, 45 | RELATION_CONSUMES_API, 46 | RELATION_DEPENDENCY_OF, 47 | RELATION_DEPENDS_ON, 48 | RELATION_HAS_PART, 49 | RELATION_PART_OF, 50 | RELATION_PROVIDES_API, 51 | } from '@backstage/catalog-model'; 52 | 53 | import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; 54 | import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; 55 | 56 | import { 57 | EntityKubernetesContent, 58 | isKubernetesAvailable, 59 | } from '@backstage/plugin-kubernetes'; 60 | 61 | const techdocsContent = ( 62 | 63 | 64 | 65 | 66 | 67 | ); 68 | 69 | const cicdContent = ( 70 | // This is an example of how you can implement your company's logic in entity page. 71 | // You can for example enforce that all components of type 'service' should use GitHubActions 72 | 73 | {/* 74 | Here you can add support for different CI/CD services, for example 75 | using @backstage-community/plugin-github-actions as follows: 76 | 77 | 78 | 79 | */} 80 | 81 | 91 | Read more 92 | 93 | } 94 | /> 95 | 96 | 97 | ); 98 | 99 | const entityWarningContent = ( 100 | <> 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | ); 126 | 127 | const overviewContent = ( 128 | 129 | {entityWarningContent} 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | ); 145 | 146 | const serviceEntityPage = ( 147 | 148 | 149 | {overviewContent} 150 | 151 | 152 | 153 | {cicdContent} 154 | 155 | 156 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | {techdocsContent} 188 | 189 | 190 | ); 191 | 192 | const websiteEntityPage = ( 193 | 194 | 195 | {overviewContent} 196 | 197 | 198 | 199 | {cicdContent} 200 | 201 | 202 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | {techdocsContent} 223 | 224 | 225 | ); 226 | 227 | /** 228 | * NOTE: This page is designed to work on small screens such as mobile devices. 229 | * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, 230 | * since this does not default. If no breakpoints are used, the items will equitably share the available space. 231 | * https://material-ui.com/components/grid/#basic-grid. 232 | */ 233 | 234 | const defaultEntityPage = ( 235 | 236 | 237 | {overviewContent} 238 | 239 | 240 | 241 | {techdocsContent} 242 | 243 | 244 | ); 245 | 246 | const componentPage = ( 247 | 248 | 249 | {serviceEntityPage} 250 | 251 | 252 | 253 | {websiteEntityPage} 254 | 255 | 256 | {defaultEntityPage} 257 | 258 | ); 259 | 260 | const apiPage = ( 261 | 262 | 263 | 264 | {entityWarningContent} 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | ); 294 | 295 | const userPage = ( 296 | 297 | 298 | 299 | {entityWarningContent} 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | ); 310 | 311 | const groupPage = ( 312 | 313 | 314 | 315 | {entityWarningContent} 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | ); 332 | 333 | const systemPage = ( 334 | 335 | 336 | 337 | {entityWarningContent} 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 376 | 377 | 378 | ); 379 | 380 | const domainPage = ( 381 | 382 | 383 | 384 | {entityWarningContent} 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | ); 398 | 399 | export const entityPage = ( 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | {defaultEntityPage} 409 | 410 | ); 411 | -------------------------------------------------------------------------------- /backstage/packages/app/src/components/search/SearchPage.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles, Theme, Grid, Paper } from '@material-ui/core'; 2 | 3 | import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; 4 | import { 5 | catalogApiRef, 6 | CATALOG_FILTER_EXISTS, 7 | } from '@backstage/plugin-catalog-react'; 8 | import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; 9 | 10 | import { SearchType } from '@backstage/plugin-search'; 11 | import { 12 | SearchBar, 13 | SearchFilter, 14 | SearchResult, 15 | SearchPagination, 16 | useSearch, 17 | } from '@backstage/plugin-search-react'; 18 | import { 19 | CatalogIcon, 20 | Content, 21 | DocsIcon, 22 | Header, 23 | Page, 24 | } from '@backstage/core-components'; 25 | import { useApi } from '@backstage/core-plugin-api'; 26 | 27 | const useStyles = makeStyles((theme: Theme) => ({ 28 | bar: { 29 | padding: theme.spacing(1, 0), 30 | }, 31 | filters: { 32 | padding: theme.spacing(2), 33 | marginTop: theme.spacing(2), 34 | }, 35 | filter: { 36 | '& + &': { 37 | marginTop: theme.spacing(2.5), 38 | }, 39 | }, 40 | })); 41 | 42 | const SearchPage = () => { 43 | const classes = useStyles(); 44 | const { types } = useSearch(); 45 | const catalogApi = useApi(catalogApiRef); 46 | 47 | return ( 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | , 66 | }, 67 | { 68 | value: 'techdocs', 69 | name: 'Documentation', 70 | icon: , 71 | }, 72 | ]} 73 | /> 74 | 75 | {types.includes('techdocs') && ( 76 | { 81 | // Return a list of entities which are documented. 82 | const { items } = await catalogApi.getEntities({ 83 | fields: ['metadata.name'], 84 | filter: { 85 | 'metadata.annotations.backstage.io/techdocs-ref': 86 | CATALOG_FILTER_EXISTS, 87 | }, 88 | }); 89 | 90 | const names = items.map(entity => entity.metadata.name); 91 | names.sort(); 92 | return names; 93 | }} 94 | /> 95 | )} 96 | 102 | 108 | 109 | 110 | 111 | 112 | 113 | } /> 114 | } /> 115 | 116 | 117 | 118 | 119 | 120 | ); 121 | }; 122 | 123 | export const searchPage = ; 124 | -------------------------------------------------------------------------------- /backstage/packages/app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import '@backstage/cli/asset-types'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import '@backstage/canon/css/styles.css'; 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render(); 7 | -------------------------------------------------------------------------------- /backstage/packages/app/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /backstage/packages/backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); 2 | -------------------------------------------------------------------------------- /backstage/packages/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # This dockerfile builds an image for the backend package. 2 | # It should be executed with the root of the repo as docker context. 3 | # 4 | # Before building this image, be sure to have run the following commands in the repo root: 5 | # 6 | # yarn install --immutable 7 | # yarn tsc 8 | # yarn build:backend 9 | # 10 | # Once the commands have been run, you can build the image using `yarn build-image` 11 | # 12 | # Alternatively, there is also a multi-stage Dockerfile documented here: 13 | # https://backstage.io/docs/deployment/docker#multi-stage-build 14 | 15 | FROM node:20-bookworm-slim 16 | 17 | # Set Python interpreter for `node-gyp` to use 18 | ENV PYTHON=/usr/bin/python3 19 | 20 | # Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. 21 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ 22 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 23 | apt-get update && \ 24 | apt-get install -y --no-install-recommends python3 g++ build-essential && \ 25 | rm -rf /var/lib/apt/lists/* 26 | 27 | # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, 28 | # in which case you should also move better-sqlite3 to "devDependencies" in package.json. 29 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ 30 | --mount=type=cache,target=/var/lib/apt,sharing=locked \ 31 | apt-get update && \ 32 | apt-get install -y --no-install-recommends libsqlite3-dev && \ 33 | rm -rf /var/lib/apt/lists/* 34 | 35 | # From here on we use the least-privileged `node` user to run the backend. 36 | USER node 37 | 38 | # This should create the app dir as `node`. 39 | # If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. 40 | # If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. 41 | WORKDIR /app 42 | 43 | # Copy files needed by Yarn 44 | COPY --chown=node:node .yarn ./.yarn 45 | COPY --chown=node:node .yarnrc.yml ./ 46 | COPY --chown=node:node backstage.json ./ 47 | 48 | # This switches many Node.js dependencies to production mode. 49 | ENV NODE_ENV=production 50 | 51 | # This disables node snapshot for Node 20 to work with the Scaffolder 52 | ENV NODE_OPTIONS="--no-node-snapshot" 53 | 54 | # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. 55 | # The skeleton contains the package.json of each package in the monorepo, 56 | # and along with yarn.lock and the root package.json, that's enough to run yarn install. 57 | COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ 58 | RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz 59 | 60 | RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ 61 | yarn workspaces focus --all --production && rm -rf "$(yarn cache clean)" 62 | 63 | # This will include the examples, if you don't need these simply remove this line 64 | COPY --chown=node:node examples ./examples 65 | COPY --chown=node:node catalog ./catalog 66 | 67 | # Then copy the rest of the backend bundle, along with any other files we might want. 68 | COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ 69 | RUN tar xzf bundle.tar.gz && rm bundle.tar.gz 70 | 71 | CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"] 72 | -------------------------------------------------------------------------------- /backstage/packages/backend/README.md: -------------------------------------------------------------------------------- 1 | # example-backend 2 | 3 | This package is an EXAMPLE of a Backstage backend. 4 | 5 | The main purpose of this package is to provide a test bed for Backstage plugins 6 | that have a backend part. Feel free to experiment locally or within your fork by 7 | adding dependencies and routes to this backend, to try things out. 8 | 9 | Our goal is to eventually amend the create-app flow of the CLI, such that a 10 | production ready version of a backend skeleton is made alongside the frontend 11 | app. Until then, feel free to experiment here! 12 | 13 | ## Development 14 | 15 | To run the example backend, first go to the project root and run 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | You should only need to do this once. 22 | 23 | After that, go to the `packages/backend` directory and run 24 | 25 | ```bash 26 | yarn start 27 | ``` 28 | 29 | If you want to override any configuration locally, for example adding any secrets, 30 | you can do so in `app-config.local.yaml`. 31 | 32 | The backend starts up on port 7007 per default. 33 | 34 | ## Populating The Catalog 35 | 36 | If you want to use the catalog functionality, you need to add so called 37 | locations to the backend. These are places where the backend can find some 38 | entity descriptor data to consume and serve. For more information, see 39 | [Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog). 40 | 41 | To get started quickly, this template already includes some statically configured example locations 42 | in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you 43 | like, and also override them for local development in `app-config.local.yaml`. 44 | 45 | ## Authentication 46 | 47 | We chose [Passport](http://www.passportjs.org/) as authentication platform due 48 | to its comprehensive set of supported authentication 49 | [strategies](http://www.passportjs.org/packages/). 50 | 51 | Read more about the 52 | [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md) 53 | and 54 | [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md) 55 | 56 | ## Documentation 57 | 58 | - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) 59 | - [Backstage Documentation](https://backstage.io/docs) 60 | -------------------------------------------------------------------------------- /backstage/packages/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "0.0.0", 4 | "main": "dist/index.cjs.js", 5 | "types": "src/index.ts", 6 | "private": true, 7 | "backstage": { 8 | "role": "backend" 9 | }, 10 | "scripts": { 11 | "start": "backstage-cli package start", 12 | "build": "backstage-cli package build", 13 | "lint": "backstage-cli package lint", 14 | "test": "backstage-cli package test", 15 | "clean": "backstage-cli package clean", 16 | "build-image": "docker build ../.. -f Dockerfile --tag backstage" 17 | }, 18 | "dependencies": { 19 | "@backstage/backend-defaults": "^0.9.0", 20 | "@backstage/config": "^1.3.2", 21 | "@backstage/plugin-app-backend": "^0.5.1", 22 | "@backstage/plugin-auth-backend": "^0.24.5", 23 | "@backstage/plugin-auth-backend-module-github-provider": "^0.3.2", 24 | "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.7", 25 | "@backstage/plugin-auth-node": "^0.6.2", 26 | "@backstage/plugin-catalog-backend": "^1.32.1", 27 | "@backstage/plugin-catalog-backend-module-logs": "^0.1.9", 28 | "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "^0.2.7", 29 | "@backstage/plugin-kubernetes-backend": "^0.19.5", 30 | "@backstage/plugin-permission-backend": "^0.6.0", 31 | "@backstage/plugin-permission-backend-module-allow-all-policy": "^0.2.7", 32 | "@backstage/plugin-permission-common": "^0.8.4", 33 | "@backstage/plugin-permission-node": "^0.9.1", 34 | "@backstage/plugin-proxy-backend": "^0.6.1", 35 | "@backstage/plugin-scaffolder-backend": "^1.32.0", 36 | "@backstage/plugin-scaffolder-backend-module-github": "^0.7.0", 37 | "@backstage/plugin-search-backend": "^2.0.1", 38 | "@backstage/plugin-search-backend-module-catalog": "^0.3.3", 39 | "@backstage/plugin-search-backend-module-pg": "^0.5.43", 40 | "@backstage/plugin-search-backend-module-techdocs": "^0.4.1", 41 | "@backstage/plugin-search-backend-node": "^1.3.10", 42 | "@backstage/plugin-techdocs-backend": "^2.0.1", 43 | "@kubernetes/client-node": "^1.1.2", 44 | "app": "link:../app", 45 | "better-sqlite3": "^9.0.0", 46 | "node-gyp": "^10.0.0", 47 | "pg": "^8.11.3" 48 | }, 49 | "devDependencies": { 50 | "@backstage/cli": "^0.32.0" 51 | }, 52 | "files": [ 53 | "dist" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /backstage/packages/backend/src/crossplane-entity-provider.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from '@backstage/catalog-model'; 2 | import { 3 | EntityProvider, 4 | EntityProviderConnection, 5 | } from '@backstage/plugin-catalog-node'; 6 | import { 7 | LoggerService, 8 | RootConfigService, 9 | SchedulerServiceTaskRunner, 10 | } from '@backstage/backend-plugin-api'; 11 | 12 | export class CrossplaneEntityProvider implements EntityProvider { 13 | private connection?: EntityProviderConnection; 14 | 15 | constructor( 16 | private readonly opts: { 17 | logger: LoggerService; 18 | config: RootConfigService; 19 | taskRunner: SchedulerServiceTaskRunner; 20 | }, 21 | ) {} 22 | 23 | getProviderName(): string { 24 | return 'crossplane-entity-provider'; 25 | } 26 | 27 | async connect(connection: EntityProviderConnection): Promise { 28 | this.connection = connection; 29 | 30 | await this.opts.taskRunner.run({ 31 | id: this.getProviderName(), 32 | fn: async () => { 33 | try { 34 | await this.run(); 35 | } catch (error: any) { 36 | this.opts.logger.error( 37 | `[${this.getProviderName()}] Failed to run`, 38 | error, 39 | ); 40 | } 41 | }, 42 | }); 43 | } 44 | 45 | private async run(): Promise { 46 | if (!this.connection) throw new Error('Provider not connected'); 47 | 48 | const clusterMethods = 49 | this.opts.config.getOptionalConfigArray( 50 | 'kubernetes.clusterLocatorMethods', 51 | ) ?? []; 52 | 53 | for (const method of clusterMethods) { 54 | if (method.getString('type') !== 'config') continue; 55 | 56 | const clusters = method.getOptionalConfigArray('clusters') ?? []; 57 | if (clusters.length === 0) { 58 | this.opts.logger.warn( 59 | `[${this.getProviderName()}] No clusters found in config`, 60 | ); 61 | continue; 62 | } 63 | 64 | for (const cluster of clusters) { 65 | await this.processCluster(cluster); 66 | } 67 | } 68 | } 69 | 70 | private async processCluster(cluster: any): Promise { 71 | const clusterName = cluster.getString('name'); 72 | const server = cluster.getString('url'); 73 | const token = cluster.getOptionalString('serviceAccountToken'); 74 | const namespace = 75 | cluster.getOptionalString('serviceAccountNamespace') ?? 'default'; 76 | const skipTLSVerify = cluster.getOptionalBoolean('skipTLSVerify') ?? false; 77 | 78 | const k8sClient = await import('@kubernetes/client-node'); 79 | 80 | const kc = new k8sClient.KubeConfig(); 81 | 82 | kc.loadFromOptions({ 83 | clusters: [{ name: clusterName, server, skipTLSVerify }], 84 | users: [{ name: 'backstage', token }], 85 | contexts: [ 86 | { 87 | name: 'backstage-context', 88 | user: 'backstage', 89 | cluster: clusterName, 90 | namespace, 91 | }, 92 | ], 93 | currentContext: 'backstage-context', 94 | }); 95 | 96 | const customObjectsApi = kc.makeApiClient(k8sClient.CustomObjectsApi); 97 | 98 | let claims; 99 | try { 100 | const res = await customObjectsApi.listNamespacedCustomObject({ 101 | group: 'platform.hooli.tech', 102 | version: 'v1alpha1', 103 | plural: 'xqueuesclaim', 104 | namespace: 'crossplane-system', 105 | }); 106 | 107 | claims = res.items; 108 | } catch (err: any) { 109 | this.opts.logger.error( 110 | `[${this.getProviderName()}] Failed to fetch claims from ${clusterName} cluster`, 111 | err, 112 | ); 113 | return; 114 | } 115 | 116 | if (!claims?.length) { 117 | this.opts.logger.warn( 118 | `[${this.getProviderName()}] No claims found in cluster ${clusterName}`, 119 | ); 120 | return; 121 | } 122 | 123 | const entities = this.toEntities(claims); 124 | const locationKey = `bootstrap:${this.getProviderName()}`; 125 | 126 | await this.connection?.applyMutation({ 127 | type: 'full', 128 | entities: entities.map(entity => ({ 129 | entity, 130 | locationKey, 131 | })), 132 | }); 133 | 134 | this.opts.logger.info( 135 | `[${this.getProviderName()}] Registered ${ 136 | entities.length 137 | } claims from ${clusterName} cluster`, 138 | ); 139 | } 140 | 141 | private toEntities(claims: any[]): Entity[] { 142 | return claims.map((claim: any) => { 143 | const statusConditions = (claim.status?.conditions ?? []).reduce( 144 | (acc: Record, cond: any) => { 145 | acc[cond.type] = cond.status; 146 | return acc; 147 | }, 148 | {}, 149 | ); 150 | 151 | const createdAt = claim.metadata?.creationTimestamp; 152 | const lastSynced = claim.status?.conditions?.find( 153 | (c: any) => c.type === 'Synced', 154 | )?.lastTransitionTime; 155 | 156 | return { 157 | apiVersion: 'backstage.io/v1alpha1', 158 | kind: 'Resource', 159 | metadata: { 160 | name: claim.metadata.name, 161 | namespace: 'default', 162 | description: `Crossplane XQueueClaim provisioned with provider ${claim.spec.providerName}`, 163 | annotations: { 164 | 'backstage.io/kubernetes-id': claim.metadata.name, 165 | 'backstage.io/managed-by-location': `bootstrap:${this.getProviderName()}`, 166 | 'backstage.io/managed-by-origin-location': `bootstrap:${this.getProviderName()}`, 167 | }, 168 | tags: Object.entries(claim.spec.tags ?? {}).map( 169 | ([k, v]) => `${k}:${v}`, 170 | ), 171 | }, 172 | spec: { 173 | type: 'xqueueclaim', 174 | system: 'infrastructure', 175 | owner: 'guests', 176 | lifecycle: 'production', 177 | provider: claim.spec.providerName, 178 | location: claim.spec.location, 179 | maxMessageSize: claim.spec.maxMessageSize, 180 | visibilityTimeoutSeconds: claim.spec.visibilityTimeoutSeconds, 181 | queueName: claim.spec.resourceRef?.name, 182 | status: statusConditions, 183 | createdAt, 184 | lastSynced, 185 | }, 186 | }; 187 | }); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /backstage/packages/backend/src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Hi! 3 | * 4 | * Note that this is an EXAMPLE Backstage backend. Please check the README. 5 | * 6 | * Happy hacking! 7 | */ 8 | 9 | import { createBackend } from '@backstage/backend-defaults'; 10 | import { 11 | coreServices, 12 | createBackendModule, 13 | } from '@backstage/backend-plugin-api'; 14 | import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; 15 | import { CrossplaneEntityProvider } from './crossplane-entity-provider'; 16 | 17 | const backend = createBackend(); 18 | 19 | backend.add(import('@backstage/plugin-app-backend')); 20 | backend.add(import('@backstage/plugin-proxy-backend')); 21 | backend.add(import('@backstage/plugin-scaffolder-backend')); 22 | backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); 23 | backend.add(import('@backstage/plugin-techdocs-backend')); 24 | 25 | // auth plugin 26 | backend.add(import('@backstage/plugin-auth-backend')); 27 | // See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin 28 | backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); 29 | // See https://backstage.io/docs/auth/guest/provider 30 | 31 | // catalog plugin 32 | backend.add(import('@backstage/plugin-catalog-backend')); 33 | backend.add( 34 | import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), 35 | ); 36 | 37 | // See https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors 38 | backend.add(import('@backstage/plugin-catalog-backend-module-logs')); 39 | 40 | // permission plugin 41 | backend.add(import('@backstage/plugin-permission-backend')); 42 | // See https://backstage.io/docs/permissions/getting-started for how to create your own permission policy 43 | backend.add( 44 | import('@backstage/plugin-permission-backend-module-allow-all-policy'), 45 | ); 46 | 47 | // search plugin 48 | backend.add(import('@backstage/plugin-search-backend')); 49 | 50 | // search engine 51 | // See https://backstage.io/docs/features/search/search-engines 52 | backend.add(import('@backstage/plugin-search-backend-module-pg')); 53 | 54 | // search collators 55 | backend.add(import('@backstage/plugin-search-backend-module-catalog')); 56 | backend.add(import('@backstage/plugin-search-backend-module-techdocs')); 57 | 58 | // kubernetes 59 | backend.add(import('@backstage/plugin-kubernetes-backend')); 60 | 61 | export const crossplaneIngestor = createBackendModule({ 62 | pluginId: 'catalog', 63 | moduleId: 'crossplane-ingestor', 64 | register(env) { 65 | env.registerInit({ 66 | deps: { 67 | config: coreServices.rootConfig, 68 | catalog: catalogProcessingExtensionPoint, 69 | scheduler: coreServices.scheduler, 70 | logger: coreServices.logger, 71 | }, 72 | async init({ catalog, scheduler, logger, config }) { 73 | logger.info('Starting crossplane ingestor'); 74 | 75 | const taskRunner = scheduler.createScheduledTaskRunner({ 76 | frequency: { seconds: 10 }, 77 | timeout: { minutes: 10 }, 78 | }); 79 | 80 | const entityProvider = new CrossplaneEntityProvider({ 81 | logger, 82 | config, 83 | taskRunner, 84 | }); 85 | 86 | catalog.addEntityProvider(entityProvider); 87 | }, 88 | }); 89 | }, 90 | }); 91 | 92 | backend.add(crossplaneIngestor); 93 | 94 | backend.start(); 95 | -------------------------------------------------------------------------------- /backstage/playwright.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Backstage Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import { defineConfig } from '@playwright/test'; 18 | import { generateProjects } from '@backstage/e2e-test-utils/playwright'; 19 | 20 | /** 21 | * See https://playwright.dev/docs/test-configuration. 22 | */ 23 | export default defineConfig({ 24 | timeout: 60_000, 25 | 26 | expect: { 27 | timeout: 5_000, 28 | }, 29 | 30 | // Run your local dev server before starting the tests 31 | webServer: process.env.CI 32 | ? [] 33 | : [ 34 | { 35 | command: 'yarn start', 36 | port: 3000, 37 | reuseExistingServer: true, 38 | timeout: 60_000, 39 | }, 40 | ], 41 | 42 | forbidOnly: !!process.env.CI, 43 | 44 | retries: process.env.CI ? 2 : 0, 45 | 46 | reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]], 47 | 48 | use: { 49 | actionTimeout: 0, 50 | baseURL: 51 | process.env.PLAYWRIGHT_URL ?? 52 | (process.env.CI ? 'http://localhost:7007' : 'http://localhost:3000'), 53 | screenshot: 'only-on-failure', 54 | trace: 'on-first-retry', 55 | }, 56 | 57 | outputDir: 'node_modules/.cache/e2e-test-results', 58 | 59 | projects: generateProjects(), // Find all packages with e2e-test folders 60 | }); 61 | -------------------------------------------------------------------------------- /backstage/plugins/README.md: -------------------------------------------------------------------------------- 1 | # The Plugins Folder 2 | 3 | This is where your own plugins and their associated modules live, each in a 4 | separate folder of its own. 5 | 6 | If you want to create a new plugin here, go to your project root directory, run 7 | the command `yarn new`, and follow the on-screen instructions. 8 | 9 | You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)! 10 | -------------------------------------------------------------------------------- /backstage/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@backstage/cli/config/tsconfig.json", 3 | "include": [ 4 | "packages/*/src", 5 | "packages/*/config.d.ts", 6 | "plugins/*/src", 7 | "plugins/*/config.d.ts", 8 | "plugins/*/dev", 9 | "plugins/*/migrations" 10 | ], 11 | "exclude": ["node_modules"], 12 | "compilerOptions": { 13 | "outDir": "dist-types", 14 | "rootDir": ".", 15 | "jsx": "react-jsx" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crossplane/claims/debug-pre-talk.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: debug-pre-talk 5 | spec: 6 | location: US 7 | providerName: default 8 | visibilityTimeoutSeconds: 30 9 | maxMessageSize: 262144 10 | tags: 11 | platform: rocks 12 | -------------------------------------------------------------------------------- /crossplane/claims/lorem-ipsum-dolor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: lorem-ipsum-dolor 5 | spec: 6 | location: US 7 | providerName: default 8 | visibilityTimeoutSeconds: 30 9 | maxMessageSize: 262144 10 | tags: 11 | foo: bar 12 | -------------------------------------------------------------------------------- /crossplane/claims/lorem-ipsum-foo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: lorem-ipsum-foo 5 | spec: 6 | location: US 7 | providerName: default 8 | visibilityTimeoutSeconds: 30 9 | maxMessageSize: 262144 10 | tags: 11 | foo: bar 12 | -------------------------------------------------------------------------------- /crossplane/claims/lorem-ipsum.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: lorem-ipsum 5 | spec: 6 | location: EU 7 | providerName: default 8 | visibilityTimeoutSeconds: 30 9 | maxMessageSize: 262144 10 | tags: 11 | foo: bar 12 | -------------------------------------------------------------------------------- /crossplane/claims/platform-rock-vai.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: platform-rock-vai 5 | spec: 6 | location: US 7 | providerName: default 8 | visibilityTimeoutSeconds: 30 9 | maxMessageSize: 262144 10 | tags: 11 | cost: teste 12 | -------------------------------------------------------------------------------- /crossplane/claims/platform-rocks.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: platform.hooli.tech/v1alpha1 2 | kind: XQueueClaim 3 | metadata: 4 | name: platform-rocks 5 | spec: 6 | location: US 7 | providerName: default 8 | visibilityTimeoutSeconds: 30 9 | maxMessageSize: 262144 10 | tags: 11 | cost: lorem-ipsum 12 | -------------------------------------------------------------------------------- /crossplane/compositions/aws/xqueue.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: Composition 3 | metadata: 4 | name: xqueues.aws.platform.hooli.tech 5 | spec: 6 | mode: Pipeline 7 | pipeline: 8 | - step: patch-and-transform 9 | functionRef: 10 | name: function-patch-and-transform 11 | input: 12 | apiVersion: pt.fn.crossplane.io/v1beta1 13 | kind: Resources 14 | resources: 15 | - name: MainQueue 16 | base: 17 | apiVersion: sqs.aws.upbound.io/v1beta1 18 | kind: Queue 19 | metadata: 20 | name: crossplane-quickstart-sqs-queue 21 | spec: 22 | forProvider: 23 | region: us-east-2 24 | tags: {} 25 | visibilityTimeoutSeconds: 30 26 | maxMessageSize: 262144 27 | providerConfigRef: 28 | name: default 29 | patches: 30 | - type: FromCompositeFieldPath 31 | fromFieldPath: "spec.location" 32 | toFieldPath: "spec.forProvider.region" 33 | transforms: 34 | - type: map 35 | map: 36 | EU: "eu-north-1" 37 | US: "us-east-2" 38 | - type: FromCompositeFieldPath 39 | fromFieldPath: "spec.tags" 40 | toFieldPath: "spec.forProvider.tags" 41 | policy: 42 | mergeOptions: 43 | strategy: MergeMap 44 | - type: FromCompositeFieldPath 45 | fromFieldPath: "spec.visibilityTimeoutSeconds" 46 | toFieldPath: "spec.forProvider.visibilityTimeoutSeconds" 47 | - type: FromCompositeFieldPath 48 | fromFieldPath: "spec.maxMessageSize" 49 | toFieldPath: "spec.forProvider.maxMessageSize" 50 | - type: FromCompositeFieldPath 51 | fromFieldPath: "spec.providerName" 52 | toFieldPath: "spec.providerConfigRef.name" 53 | compositeTypeRef: 54 | apiVersion: platform.hooli.tech/v1alpha1 55 | kind: XQueue 56 | -------------------------------------------------------------------------------- /crossplane/functions/function-patch-and-transform.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: pkg.crossplane.io/v1beta1 2 | kind: Function 3 | metadata: 4 | name: function-patch-and-transform 5 | spec: 6 | package: xpkg.upbound.io/crossplane-contrib/function-patch-and-transform:v0.2.1 7 | -------------------------------------------------------------------------------- /crossplane/providers-config/kubernetes-provider-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kubernetes.crossplane.io/v1alpha1 2 | kind: ProviderConfig 3 | metadata: 4 | name: kubernetes-provider 5 | spec: 6 | credentials: 7 | source: InjectedIdentity 8 | -------------------------------------------------------------------------------- /crossplane/providers-config/localstack-provider-config.yaml: -------------------------------------------------------------------------------- 1 | # @see https://marketplace.upbound.io/providers/upbound/provider-family-aws/v1.18.3/resources/aws.upbound.io/ProviderConfig/v1beta1 2 | # @see https://docs.localstack.cloud/user-guide/integrations/crossplane/ 3 | apiVersion: aws.upbound.io/v1beta1 4 | kind: ProviderConfig 5 | metadata: 6 | name: default 7 | namespace: crossplane-system 8 | spec: 9 | endpoint: 10 | url: 11 | type: Static 12 | static: http://localstack.localstack-system.svc.cluster.local:4566 13 | services: [s3, sqs, ec2, iam, lambda, dynamodb] # the same ones that are in bootstrap/localstack/manifests/deployment.yaml 14 | credentials: 15 | source: Secret 16 | secretRef: 17 | namespace: crossplane-system 18 | name: aws-secret 19 | key: creds 20 | skip_credentials_validation: true 21 | skip_metadata_api_check: true 22 | skip_requesting_account_id: true 23 | s3_use_path_style: true 24 | -------------------------------------------------------------------------------- /crossplane/providers/aws/sqs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: pkg.crossplane.io/v1 2 | kind: Provider 3 | metadata: 4 | name: provider-aws-sqs 5 | namespace: crossplane-system 6 | spec: 7 | package: xpkg.upbound.io/upbound/provider-aws-sqs:v1 8 | -------------------------------------------------------------------------------- /crossplane/providers/kubernetes.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: pkg.crossplane.io/v1 2 | kind: Provider 3 | metadata: 4 | name: provider-kubernetes 5 | spec: 6 | package: xpkg.upbound.io/upbound/provider-kubernetes:v0.17.1 7 | runtimeConfigRef: 8 | apiVersion: pkg.crossplane.io/v1beta1 9 | kind: DeploymentRuntimeConfig 10 | name: provider-kubernetes 11 | --- 12 | apiVersion: pkg.crossplane.io/v1beta1 13 | kind: DeploymentRuntimeConfig 14 | metadata: 15 | name: provider-kubernetes 16 | spec: 17 | deploymentTemplate: 18 | spec: 19 | replicas: 1 20 | selector: {} 21 | template: {} 22 | serviceAccountTemplate: 23 | metadata: 24 | name: provider-kubernetes 25 | --- 26 | apiVersion: rbac.authorization.k8s.io/v1 27 | kind: ClusterRoleBinding 28 | metadata: 29 | name: provider-kubernetes-cluster-admin 30 | subjects: 31 | - kind: ServiceAccount 32 | name: provider-kubernetes 33 | namespace: crossplane-system 34 | roleRef: 35 | kind: ClusterRole 36 | name: cluster-admin 37 | apiGroup: rbac.authorization.k8s.io 38 | -------------------------------------------------------------------------------- /crossplane/xrds/xqueue.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.crossplane.io/v1 2 | kind: CompositeResourceDefinition 3 | metadata: 4 | name: xqueues.platform.hooli.tech 5 | spec: 6 | group: platform.hooli.tech 7 | names: 8 | kind: XQueue 9 | plural: xqueues 10 | versions: 11 | - name: v1alpha1 12 | schema: 13 | openAPIV3Schema: 14 | type: object 15 | properties: 16 | spec: 17 | type: object 18 | properties: 19 | location: 20 | type: string 21 | oneOf: 22 | - pattern: "^EU$" 23 | - pattern: "^US$" 24 | tags: 25 | type: object 26 | additionalProperties: 27 | type: string 28 | visibilityTimeoutSeconds: 29 | type: integer 30 | minimum: 0 31 | maxMessageSize: 32 | type: integer 33 | minimum: 1024 34 | providerName: 35 | type: string 36 | required: 37 | - location 38 | - providerName 39 | served: true 40 | referenceable: true 41 | claimNames: 42 | kind: XQueueClaim 43 | plural: xqueuesclaim 44 | -------------------------------------------------------------------------------- /kyverno/validate-xqueue-fields.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kyverno.io/v1 2 | kind: ClusterPolicy 3 | metadata: 4 | name: validate-xqueue-fields 5 | spec: 6 | validationFailureAction: Enforce 7 | background: true 8 | rules: 9 | - name: deny-invalid-location 10 | match: 11 | resources: 12 | kinds: 13 | - platform.hooli.tech/v1alpha1/XQueueClaim 14 | validate: 15 | message: "Invalid location: only 'EU' or 'US' are allowed in spec.location" 16 | deny: 17 | conditions: 18 | all: 19 | - key: "{{ request.object.spec.location }}" 20 | operator: AllNotIn 21 | value: 22 | - "EU" 23 | - "US" 24 | 25 | - name: deny-invalid-max-message-size 26 | match: 27 | resources: 28 | kinds: 29 | - platform.hooli.tech/v1alpha1/XQueueClaim 30 | validate: 31 | message: "Invalid maxMessageSize: must be between 1024 and 262144 (bytes)" 32 | deny: 33 | conditions: 34 | any: 35 | - key: "{{ request.object.spec.maxMessageSize }}" 36 | operator: GreaterThan 37 | value: 262144 38 | - key: "{{ request.object.spec.maxMessageSize }}" 39 | operator: LessThan 40 | value: 1024 41 | 42 | - name: deny-invalid-visibility-timeout 43 | match: 44 | resources: 45 | kinds: 46 | - platform.hooli.tech/v1alpha1/XQueueClaim 47 | validate: 48 | message: "Invalid visibilityTimeoutSeconds: must be between 0 and 43200 (seconds)" 49 | deny: 50 | conditions: 51 | any: 52 | - key: "{{ request.object.spec.visibilityTimeoutSeconds }}" 53 | operator: GreaterThan 54 | value: 43200 55 | - key: "{{ request.object.spec.visibilityTimeoutSeconds }}" 56 | operator: LessThan 57 | value: 0 58 | --------------------------------------------------------------------------------