diff --git a/apps/codeai/README.md b/apps/codeai/README.md index 594080a..37ee18d 100644 --- a/apps/codeai/README.md +++ b/apps/codeai/README.md @@ -1,3 +1,4 @@ -This app's deployment definitions live under `deployments/`. +`main` keeps deployment metadata, env policy, and the Kargo temp-wrapper templates. -Docker image tag writeback is done by the GitHub Actions workflow [`k8s-commit-image-ref-to-argocd.yml`](https://github.com/code-dot-org/code-dot-org/blob/staging/.github/workflows/k8s-commit-image-ref-to-argocd.yml). +Rendered manifests live on `stage/` branches at `apps/codeai/deployments//deploy/`. +Argo CD deploys those rendered paths directly; Kargo promotion is responsible for rehydrating the OCI release capsule and writing them. diff --git a/apps/codeai/applicationset.yaml b/apps/codeai/applicationset.yaml index e367ef9..cc4d82c 100644 --- a/apps/codeai/applicationset.yaml +++ b/apps/codeai/applicationset.yaml @@ -9,7 +9,10 @@ spec: repoURL: https://github.com/code-dot-org/k8s-gitops.git revision: main files: - - path: apps/codeai/deployments/*/deployment.yaml + - path: apps/codeai/deployments/staging/deployment.yaml + - path: apps/codeai/deployments/test/deployment.yaml + - path: apps/codeai/deployments/levelbuilder/deployment.yaml + - path: apps/codeai/deployments/production/deployment.yaml template: metadata: name: codeai-{{path.basename}} @@ -19,17 +22,9 @@ spec: spec: project: default sources: - - repoURL: https://github.com/code-dot-org/code-dot-org.git - targetRevision: '{{sourceRevision}}' - path: k8s/helm - helm: - releaseName: '{{path.basename}}' - valueFiles: - - $values/apps/codeai/envTypes/{{envType}}.values.yaml - - $values/apps/codeai/deployments/{{path.basename}}/values.yaml - repoURL: https://github.com/code-dot-org/k8s-gitops.git - targetRevision: main - ref: values + targetRevision: stage/{{path.basename}} + path: apps/codeai/deployments/{{path.basename}}/deploy destination: server: https://kubernetes.default.svc namespace: '{{namespace}}' diff --git a/apps/codeai/deployments/levelbuilder/deploy/README.md b/apps/codeai/deployments/levelbuilder/deploy/README.md new file mode 100644 index 0000000..57b9909 --- /dev/null +++ b/apps/codeai/deployments/levelbuilder/deploy/README.md @@ -0,0 +1,2 @@ +Rendered output is committed on `stage/levelbuilder`. +`main` keeps only this placeholder so the branch-local path exists before first render. diff --git a/apps/codeai/deployments/levelbuilder/deployment.yaml b/apps/codeai/deployments/levelbuilder/deployment.yaml new file mode 100644 index 0000000..c776c6d --- /dev/null +++ b/apps/codeai/deployments/levelbuilder/deployment.yaml @@ -0,0 +1,3 @@ +envType: levelbuilder +namespace: levelbuilder +branch: levelbuilder diff --git a/apps/codeai/deployments/production/deploy/README.md b/apps/codeai/deployments/production/deploy/README.md new file mode 100644 index 0000000..647e568 --- /dev/null +++ b/apps/codeai/deployments/production/deploy/README.md @@ -0,0 +1,2 @@ +Rendered output is committed on `stage/production`. +`main` keeps only this placeholder so the branch-local path exists before first render. diff --git a/apps/codeai/deployments/production/deployment.yaml b/apps/codeai/deployments/production/deployment.yaml new file mode 100644 index 0000000..17544b0 --- /dev/null +++ b/apps/codeai/deployments/production/deployment.yaml @@ -0,0 +1,3 @@ +envType: production +namespace: production +branch: production diff --git a/apps/codeai/deployments/staging/deploy/README.md b/apps/codeai/deployments/staging/deploy/README.md new file mode 100644 index 0000000..4aec27f --- /dev/null +++ b/apps/codeai/deployments/staging/deploy/README.md @@ -0,0 +1,2 @@ +Rendered output is committed on `stage/staging`. +`main` keeps only this placeholder so the branch-local path exists before first render. diff --git a/apps/codeai/deployments/test/deploy/README.md b/apps/codeai/deployments/test/deploy/README.md new file mode 100644 index 0000000..96e5432 --- /dev/null +++ b/apps/codeai/deployments/test/deploy/README.md @@ -0,0 +1,2 @@ +Rendered output is committed on `stage/test`. +`main` keeps only this placeholder so the branch-local path exists before first render. diff --git a/apps/codeai/kargo/templates/deploy/kustomization.yaml b/apps/codeai/kargo/templates/deploy/kustomization.yaml new file mode 100644 index 0000000..7a77c4b --- /dev/null +++ b/apps/codeai/kargo/templates/deploy/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: replace-me +resources: [] +components: [] +images: + - name: code-dot-org + newName: code-dot-org + newTag: latest diff --git a/apps/codeai/kargo/templates/release-metadata.yaml b/apps/codeai/kargo/templates/release-metadata.yaml new file mode 100644 index 0000000..a108950 --- /dev/null +++ b/apps/codeai/kargo/templates/release-metadata.yaml @@ -0,0 +1,12 @@ +schemaVersion: codeai/v1alpha1 +release: + gitCommit: replace-me + image: + repoURL: replace-me + tag: replace-me + digest: replace-me + capsule: + repoURL: replace-me + tag: replace-me + packageKind: replace-me + packagePath: replace-me diff --git a/apps/kargo-project-codeai/analysis-template-codeai-test-smoke.yaml b/apps/kargo-project-codeai/analysis-template-codeai-test-smoke.yaml new file mode 100644 index 0000000..7940359 --- /dev/null +++ b/apps/kargo-project-codeai/analysis-template-codeai-test-smoke.yaml @@ -0,0 +1,32 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AnalysisTemplate +metadata: + name: codeai-test-smoke + namespace: kargo-project-codeai +spec: + args: + - name: healthURL + metrics: + - name: wait-for-test-health + provider: + job: + spec: + backoffLimit: 0 + template: + spec: + restartPolicy: Never + containers: + - name: curl + image: curlimages/curl:8.12.1 + command: + - sh + - -ceu + args: + - | + for attempt in $(seq 1 60); do + if curl -fsS "{{args.healthURL}}" >/dev/null; then + exit 0 + fi + sleep 10 + done + exit 1 diff --git a/apps/kargo-project-codeai/codeai-release-verify-configmap.yaml b/apps/kargo-project-codeai/codeai-release-verify-configmap.yaml new file mode 100644 index 0000000..58d470c --- /dev/null +++ b/apps/kargo-project-codeai/codeai-release-verify-configmap.yaml @@ -0,0 +1,101 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: codeai-release-verify + namespace: kargo-project-codeai +data: + server.py: | + import json + import re + from http.server import BaseHTTPRequestHandler, HTTPServer + + TAG_PATTERN = re.compile(r"^git-([0-9a-f]{40})$") + + + def failure(reason): + return { + "ok": False, + "reason": reason, + "release": { + "gitCommit": "", + "packageKind": "", + "packagePath": "", + }, + } + + + def validate(payload): + image = payload.get("image") or {} + capsule = payload.get("capsule") or {} + release = payload.get("release") or {} + + image_tag = image.get("tag", "") + release_tag = release.get("imageTag", "") + release_digest = release.get("imageDigest", "") + release_git_commit = release.get("gitCommit", "") + package_kind = release.get("packageKind", "") + package_path = release.get("packagePath", "") + + match = TAG_PATTERN.match(image_tag) + if not match: + return failure(f"image tag is not a canonical git tag: {image_tag}") + + expected_commit = match.group(1) + if release_tag != image_tag: + return failure("image tag mismatch between Freight and capsule release.yaml") + if release_git_commit != expected_commit: + return failure("git commit mismatch between Freight tag and capsule release.yaml") + if release_digest != image.get("digest"): + return failure("image digest mismatch between Freight and capsule release.yaml") + if package_kind != "kustomize": + return failure("package kind must be kustomize") + if not package_path.startswith("package/"): + return failure("package path must stay under package/") + if capsule.get("tag") != image_tag: + return failure("capsule tag mismatch") + + return { + "ok": True, + "reason": "", + "release": { + "gitCommit": release_git_commit, + "packageKind": package_kind, + "packagePath": package_path, + }, + } + + + class Handler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/healthz": + self.respond(200, {"ok": True}) + return + self.respond(404, {"ok": False, "reason": "not found"}) + + def do_POST(self): + if self.path != "/verify": + self.respond(404, {"ok": False, "reason": "not found"}) + return + + try: + content_length = int(self.headers.get("Content-Length", "0")) + payload = json.loads(self.rfile.read(content_length)) + result = validate(payload) + self.respond(200 if result["ok"] else 422, result) + except Exception as exc: + self.respond(500, {"ok": False, "reason": f"verifier error: {exc}"}) + + def log_message(self, _format, *_args): + return + + def respond(self, status_code, body): + data = json.dumps(body).encode("utf-8") + self.send_response(status_code) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(data))) + self.end_headers() + self.wfile.write(data) + + + server = HTTPServer(("0.0.0.0", 8080), Handler) + server.serve_forever() diff --git a/apps/kargo-project-codeai/codeai-release-verify-deployment.yaml b/apps/kargo-project-codeai/codeai-release-verify-deployment.yaml new file mode 100644 index 0000000..5ada02d --- /dev/null +++ b/apps/kargo-project-codeai/codeai-release-verify-deployment.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: codeai-release-verify + namespace: kargo-project-codeai +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: codeai-release-verify + template: + metadata: + labels: + app.kubernetes.io/name: codeai-release-verify + spec: + containers: + - name: server + image: python:3.12-alpine + command: + - python + - /app/server.py + ports: + - name: http + containerPort: 8080 + readinessProbe: + httpGet: + path: /healthz + port: http + livenessProbe: + httpGet: + path: /healthz + port: http + volumeMounts: + - name: app + mountPath: /app + volumes: + - name: app + configMap: + name: codeai-release-verify diff --git a/apps/kargo-project-codeai/codeai-release-verify-service.yaml b/apps/kargo-project-codeai/codeai-release-verify-service.yaml new file mode 100644 index 0000000..1a43670 --- /dev/null +++ b/apps/kargo-project-codeai/codeai-release-verify-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: codeai-release-verify + namespace: kargo-project-codeai +spec: + selector: + app.kubernetes.io/name: codeai-release-verify + ports: + - name: http + port: 80 + targetPort: http diff --git a/apps/kargo-project-codeai/project-config.yaml b/apps/kargo-project-codeai/project-config.yaml index a303851..de77915 100644 --- a/apps/kargo-project-codeai/project-config.yaml +++ b/apps/kargo-project-codeai/project-config.yaml @@ -9,7 +9,9 @@ spec: autoPromotionEnabled: true - stage: test autoPromotionEnabled: false - - stage: production - autoPromotionEnabled: false - stage: levelbuilder autoPromotionEnabled: false + - stage: review-infra-changes + autoPromotionEnabled: false + - stage: production + autoPromotionEnabled: false diff --git a/apps/kargo-project-codeai/stages/levelbuilder.yaml b/apps/kargo-project-codeai/stages/levelbuilder.yaml index 305d668..c08e1a0 100644 --- a/apps/kargo-project-codeai/stages/levelbuilder.yaml +++ b/apps/kargo-project-codeai/stages/levelbuilder.yaml @@ -7,7 +7,7 @@ spec: requestedFreight: - origin: kind: Warehouse - name: kargo-project-codeai + name: codeai-image sources: stages: - test @@ -16,24 +16,192 @@ spec: vars: - name: gitopsRepo value: https://github.com/code-dot-org/k8s-gitops.git + - name: imageRepo + value: ghcr.io/code-dot-org/code-dot-org + - name: capsuleRepo + value: ghcr.io/code-dot-org/codeai-release-capsule + - name: targetBranch + value: stage/${{ ctx.stage }} steps: - uses: git-clone config: repoURL: ${{ vars.gitopsRepo }} checkout: - branch: main - path: ./gitops + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ./out + - uses: yaml-parse + as: deployment-meta + config: + path: ./src/apps/codeai/deployments/${{ ctx.stage }}/deployment.yaml + outputs: + - name: envType + fromExpression: envType + - name: namespace + fromExpression: namespace + - uses: yaml-parse + as: legacy-gate + config: + path: ./src/warehouses/codeai/legacy-gitflow/levelbuilder/merged/${{ imageFrom(vars.imageRepo).Tag }}.yaml + outputs: + - name: mergedTag + fromExpression: tag + - uses: git-clear + config: + path: ./out/apps/codeai/deployments/${{ ctx.stage }}/deploy + - uses: oci-download + config: + imageRef: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + outPath: ./capsule.tgz + - uses: untar + config: + inPath: ./capsule.tgz + outPath: ./capsule + - uses: yaml-parse + as: capsule-release + config: + path: ./capsule/release.yaml + outputs: + - name: gitCommit + fromExpression: gitCommit + - name: imageRepo + fromExpression: image.repoURL + - name: imageTag + fromExpression: image.tag + - name: imageDigest + fromExpression: image.digest + - name: packageKind + fromExpression: package.kind + - name: packagePath + fromExpression: package.path + - uses: http + as: verify-release + config: + method: POST + url: http://codeai-release-verify.kargo-project-codeai.svc.cluster.local/verify + headers: + - name: Content-Type + value: application/json + body: | + ${{ quote({ + "image": { + "repoURL": imageFrom(vars.imageRepo).RepoURL, + "tag": imageFrom(vars.imageRepo).Tag, + "digest": imageFrom(vars.imageRepo).Digest + }, + "capsule": { + "repoURL": vars.capsuleRepo, + "tag": imageFrom(vars.imageRepo).Tag + }, + "release": { + "gitCommit": outputs['capsule-release'].gitCommit, + "imageRepo": outputs['capsule-release'].imageRepo, + "imageTag": outputs['capsule-release'].imageTag, + "imageDigest": outputs['capsule-release'].imageDigest, + "packageKind": outputs['capsule-release'].packageKind, + "packagePath": outputs['capsule-release'].packagePath + } + }) }} + successExpression: response.status == 200 && response.body.ok == true + failureExpression: response.status >= 400 || response.body.ok == false + outputs: + - name: verifiedGitCommit + fromExpression: response.body.release.gitCommit + - name: verifiedPackageKind + fromExpression: response.body.release.packageKind + - name: verifiedPackagePath + fromExpression: response.body.release.packagePath + - uses: set-metadata + config: + updates: + - kind: Stage + name: levelbuilder + values: + promotedImage: + repoURL: ${{ imageFrom(vars.imageRepo).RepoURL }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: + repoURL: ${{ vars.capsuleRepo }} + tag: ${{ outputs['capsule-release'].imageTag }} + packageKind: ${{ outputs['verify-release'].verifiedPackageKind }} + packagePath: ${{ outputs['verify-release'].verifiedPackagePath }} + gitCommit: ${{ outputs['verify-release'].verifiedGitCommit }} + - uses: copy + config: + inPath: ./capsule/package/kustomize + outPath: ./work/deployments/source + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/${{ outputs['deployment-meta'].envType }} + outPath: ./work/deployments/envTypes/${{ outputs['deployment-meta'].envType }} + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/components + outPath: ./work/deployments/envTypes/components + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/deploy + outPath: ./work/deployments/${{ ctx.stage }}/deploy + - uses: yaml-update + config: + path: ./work/deployments/${{ ctx.stage }}/deploy/kustomization.yaml + updates: + - key: namespace + value: ${{ outputs['deployment-meta'].namespace }} + - key: resources + value: + - ../../source/base + - key: components + value: + - ../../envTypes/${{ outputs['deployment-meta'].envType }} + - uses: kustomize-set-image + config: + path: ./work/deployments/${{ ctx.stage }}/deploy + images: + - image: code-dot-org + newName: ${{ vars.imageRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: kustomize-build + config: + path: ./work/deployments/${{ ctx.stage }}/deploy + outPath: ./out/apps/codeai/deployments/${{ ctx.stage }}/deploy + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/release-metadata.yaml + outPath: ./out/apps/codeai/deployments/${{ ctx.stage }}/release-metadata.yaml - uses: yaml-update config: - path: ./gitops/apps/codeai/deployments/levelbuilder/values.yaml + path: ./out/apps/codeai/deployments/${{ ctx.stage }}/release-metadata.yaml updates: - - key: image - value: ghcr.io/code-dot-org/code-dot-org:${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} + - key: release.gitCommit + value: ${{ outputs['verify-release'].verifiedGitCommit }} + - key: release.image.repoURL + value: ${{ imageFrom(vars.imageRepo).RepoURL }} + - key: release.image.tag + value: ${{ imageFrom(vars.imageRepo).Tag }} + - key: release.image.digest + value: ${{ imageFrom(vars.imageRepo).Digest }} + - key: release.capsule.repoURL + value: ${{ vars.capsuleRepo }} + - key: release.capsule.tag + value: ${{ outputs['capsule-release'].imageTag }} + - key: release.capsule.packageKind + value: ${{ outputs['verify-release'].verifiedPackageKind }} + - key: release.capsule.packagePath + value: ${{ outputs['verify-release'].verifiedPackagePath }} - uses: git-commit config: - path: ./gitops + path: ./out message: | - Promote levelbuilder to ${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} [skip ci] + Render levelbuilder from OCI release capsule + + image: ${{ imageFrom(vars.imageRepo).RepoURL }}:${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} - uses: git-push config: - path: ./gitops + path: ./out + branch: ${{ vars.targetBranch }} diff --git a/apps/kargo-project-codeai/stages/production.yaml b/apps/kargo-project-codeai/stages/production.yaml index eac25f5..a671927 100644 --- a/apps/kargo-project-codeai/stages/production.yaml +++ b/apps/kargo-project-codeai/stages/production.yaml @@ -7,33 +7,43 @@ spec: requestedFreight: - origin: kind: Warehouse - name: kargo-project-codeai + name: codeai-image sources: stages: - - test + - review-infra-changes promotionTemplate: spec: vars: - name: gitopsRepo value: https://github.com/code-dot-org/k8s-gitops.git + - name: imageRepo + value: ghcr.io/code-dot-org/code-dot-org + - name: capsuleRepo + value: ghcr.io/code-dot-org/codeai-release-capsule steps: - uses: git-clone config: repoURL: ${{ vars.gitopsRepo }} checkout: - branch: main - path: ./gitops - - uses: yaml-update + path: ./src + - uses: yaml-parse + as: legacy-gate config: - path: ./gitops/apps/codeai/deployments/production/values.yaml - updates: - - key: image - value: ghcr.io/code-dot-org/code-dot-org:${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} - - uses: git-commit - config: - path: ./gitops - message: | - Promote production to ${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} [skip ci] - - uses: git-push + path: ./src/warehouses/codeai/legacy-gitflow/production/merged/${{ imageFrom(vars.imageRepo).Tag }}.yaml + outputs: + - name: mergedTag + fromExpression: tag + - uses: set-metadata config: - path: ./gitops + updates: + - kind: Stage + name: production + values: + promotedImage: + repoURL: ${{ imageFrom(vars.imageRepo).RepoURL }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: + repoURL: ${{ vars.capsuleRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} diff --git a/apps/kargo-project-codeai/stages/review-infra-changes.yaml b/apps/kargo-project-codeai/stages/review-infra-changes.yaml new file mode 100644 index 0000000..30e7cd0 --- /dev/null +++ b/apps/kargo-project-codeai/stages/review-infra-changes.yaml @@ -0,0 +1,219 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: review-infra-changes + namespace: kargo-project-codeai +spec: + requestedFreight: + - origin: + kind: Warehouse + name: codeai-image + sources: + stages: + - test + promotionTemplate: + spec: + vars: + - name: gitopsRepo + value: https://github.com/code-dot-org/k8s-gitops.git + - name: imageRepo + value: ghcr.io/code-dot-org/code-dot-org + - name: capsuleRepo + value: ghcr.io/code-dot-org/codeai-release-capsule + - name: targetBranch + value: stage/production + steps: + - uses: git-clone + config: + repoURL: ${{ vars.gitopsRepo }} + checkout: + - branch: main + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ./out + - uses: yaml-parse + as: deployment-meta + config: + path: ./src/apps/codeai/deployments/production/deployment.yaml + outputs: + - name: envType + fromExpression: envType + - name: namespace + fromExpression: namespace + - uses: yaml-parse + as: legacy-gate + config: + path: ./src/warehouses/codeai/legacy-gitflow/production/merged/${{ imageFrom(vars.imageRepo).Tag }}.yaml + outputs: + - name: mergedTag + fromExpression: tag + - uses: git-clear + config: + path: ./out/apps/codeai/deployments/production/deploy + - uses: oci-download + config: + imageRef: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + outPath: ./capsule.tgz + - uses: untar + config: + inPath: ./capsule.tgz + outPath: ./capsule + - uses: yaml-parse + as: capsule-release + config: + path: ./capsule/release.yaml + outputs: + - name: gitCommit + fromExpression: gitCommit + - name: imageRepo + fromExpression: image.repoURL + - name: imageTag + fromExpression: image.tag + - name: imageDigest + fromExpression: image.digest + - name: packageKind + fromExpression: package.kind + - name: packagePath + fromExpression: package.path + - uses: http + as: verify-release + config: + method: POST + url: http://codeai-release-verify.kargo-project-codeai.svc.cluster.local/verify + headers: + - name: Content-Type + value: application/json + body: | + ${{ quote({ + "image": { + "repoURL": imageFrom(vars.imageRepo).RepoURL, + "tag": imageFrom(vars.imageRepo).Tag, + "digest": imageFrom(vars.imageRepo).Digest + }, + "capsule": { + "repoURL": vars.capsuleRepo, + "tag": imageFrom(vars.imageRepo).Tag + }, + "release": { + "gitCommit": outputs['capsule-release'].gitCommit, + "imageRepo": outputs['capsule-release'].imageRepo, + "imageTag": outputs['capsule-release'].imageTag, + "imageDigest": outputs['capsule-release'].imageDigest, + "packageKind": outputs['capsule-release'].packageKind, + "packagePath": outputs['capsule-release'].packagePath + } + }) }} + successExpression: response.status == 200 && response.body.ok == true + failureExpression: response.status >= 400 || response.body.ok == false + outputs: + - name: verifiedGitCommit + fromExpression: response.body.release.gitCommit + - name: verifiedPackageKind + fromExpression: response.body.release.packageKind + - name: verifiedPackagePath + fromExpression: response.body.release.packagePath + - uses: set-metadata + config: + updates: + - kind: Stage + name: review-infra-changes + values: + promotedImage: + repoURL: ${{ imageFrom(vars.imageRepo).RepoURL }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: + repoURL: ${{ vars.capsuleRepo }} + tag: ${{ outputs['capsule-release'].imageTag }} + packageKind: ${{ outputs['verify-release'].verifiedPackageKind }} + packagePath: ${{ outputs['verify-release'].verifiedPackagePath }} + gitCommit: ${{ outputs['verify-release'].verifiedGitCommit }} + - uses: copy + config: + inPath: ./capsule/package/kustomize + outPath: ./work/deployments/source + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/${{ outputs['deployment-meta'].envType }} + outPath: ./work/deployments/envTypes/${{ outputs['deployment-meta'].envType }} + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/components + outPath: ./work/deployments/envTypes/components + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/deploy + outPath: ./work/deployments/production/deploy + - uses: yaml-update + config: + path: ./work/deployments/production/deploy/kustomization.yaml + updates: + - key: namespace + value: ${{ outputs['deployment-meta'].namespace }} + - key: resources + value: + - ../../source/base + - key: components + value: + - ../../envTypes/${{ outputs['deployment-meta'].envType }} + - uses: kustomize-set-image + config: + path: ./work/deployments/production/deploy + images: + - image: code-dot-org + newName: ${{ vars.imageRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: kustomize-build + config: + path: ./work/deployments/production/deploy + outPath: ./out/apps/codeai/deployments/production/deploy + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/release-metadata.yaml + outPath: ./out/apps/codeai/deployments/production/release-metadata.yaml + - uses: yaml-update + config: + path: ./out/apps/codeai/deployments/production/release-metadata.yaml + updates: + - key: release.gitCommit + value: ${{ outputs['verify-release'].verifiedGitCommit }} + - key: release.image.repoURL + value: ${{ imageFrom(vars.imageRepo).RepoURL }} + - key: release.image.tag + value: ${{ imageFrom(vars.imageRepo).Tag }} + - key: release.image.digest + value: ${{ imageFrom(vars.imageRepo).Digest }} + - key: release.capsule.repoURL + value: ${{ vars.capsuleRepo }} + - key: release.capsule.tag + value: ${{ outputs['capsule-release'].imageTag }} + - key: release.capsule.packageKind + value: ${{ outputs['verify-release'].verifiedPackageKind }} + - key: release.capsule.packagePath + value: ${{ outputs['verify-release'].verifiedPackagePath }} + - uses: git-commit + config: + path: ./out + message: | + Review production render from OCI release capsule + + image: ${{ imageFrom(vars.imageRepo).RepoURL }}:${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + - uses: git-push + as: push + config: + path: ./out + generateTargetBranch: true + - uses: git-open-pr + as: open-pr + config: + repoURL: ${{ vars.gitopsRepo }} + sourceBranch: ${{ outputs.push.branch }} + targetBranch: ${{ vars.targetBranch }} + title: Review CodeAI production render for ${{ imageFrom(vars.imageRepo).Tag }} + - uses: git-wait-for-pr + config: + repoURL: ${{ vars.gitopsRepo }} + prNumber: ${{ outputs['open-pr'].pr.id }} diff --git a/apps/kargo-project-codeai/stages/staging.yaml b/apps/kargo-project-codeai/stages/staging.yaml index c986f62..ebf7253 100644 --- a/apps/kargo-project-codeai/stages/staging.yaml +++ b/apps/kargo-project-codeai/stages/staging.yaml @@ -7,7 +7,7 @@ spec: requestedFreight: - origin: kind: Warehouse - name: kargo-project-codeai + name: codeai-image sources: direct: true promotionTemplate: @@ -15,24 +15,185 @@ spec: vars: - name: gitopsRepo value: https://github.com/code-dot-org/k8s-gitops.git + - name: imageRepo + value: ghcr.io/code-dot-org/code-dot-org + - name: capsuleRepo + value: ghcr.io/code-dot-org/codeai-release-capsule + - name: targetBranch + value: stage/${{ ctx.stage }} steps: - uses: git-clone config: repoURL: ${{ vars.gitopsRepo }} checkout: - branch: main - path: ./gitops + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ./out + - uses: yaml-parse + as: deployment-meta + config: + path: ./src/apps/codeai/deployments/${{ ctx.stage }}/deployment.yaml + outputs: + - name: envType + fromExpression: envType + - name: namespace + fromExpression: namespace + - uses: git-clear + config: + path: ./out/apps/codeai/deployments/${{ ctx.stage }}/deploy + - uses: oci-download + config: + imageRef: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + outPath: ./capsule.tgz + - uses: untar + config: + inPath: ./capsule.tgz + outPath: ./capsule + - uses: yaml-parse + as: capsule-release + config: + path: ./capsule/release.yaml + outputs: + - name: gitCommit + fromExpression: gitCommit + - name: imageRepo + fromExpression: image.repoURL + - name: imageTag + fromExpression: image.tag + - name: imageDigest + fromExpression: image.digest + - name: packageKind + fromExpression: package.kind + - name: packagePath + fromExpression: package.path + - uses: http + as: verify-release + config: + method: POST + url: http://codeai-release-verify.kargo-project-codeai.svc.cluster.local/verify + headers: + - name: Content-Type + value: application/json + body: | + ${{ quote({ + "image": { + "repoURL": imageFrom(vars.imageRepo).RepoURL, + "tag": imageFrom(vars.imageRepo).Tag, + "digest": imageFrom(vars.imageRepo).Digest + }, + "capsule": { + "repoURL": vars.capsuleRepo, + "tag": imageFrom(vars.imageRepo).Tag + }, + "release": { + "gitCommit": outputs['capsule-release'].gitCommit, + "imageRepo": outputs['capsule-release'].imageRepo, + "imageTag": outputs['capsule-release'].imageTag, + "imageDigest": outputs['capsule-release'].imageDigest, + "packageKind": outputs['capsule-release'].packageKind, + "packagePath": outputs['capsule-release'].packagePath + } + }) }} + successExpression: response.status == 200 && response.body.ok == true + failureExpression: response.status >= 400 || response.body.ok == false + outputs: + - name: verifiedGitCommit + fromExpression: response.body.release.gitCommit + - name: verifiedPackageKind + fromExpression: response.body.release.packageKind + - name: verifiedPackagePath + fromExpression: response.body.release.packagePath + - uses: set-metadata + config: + updates: + - kind: Stage + name: staging + values: + promotedImage: + repoURL: ${{ imageFrom(vars.imageRepo).RepoURL }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: + repoURL: ${{ vars.capsuleRepo }} + tag: ${{ outputs['capsule-release'].imageTag }} + packageKind: ${{ outputs['verify-release'].verifiedPackageKind }} + packagePath: ${{ outputs['verify-release'].verifiedPackagePath }} + gitCommit: ${{ outputs['verify-release'].verifiedGitCommit }} + - uses: copy + config: + inPath: ./capsule/package/kustomize + outPath: ./work/deployments/source + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/${{ outputs['deployment-meta'].envType }} + outPath: ./work/deployments/envTypes/${{ outputs['deployment-meta'].envType }} + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/components + outPath: ./work/deployments/envTypes/components + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/deploy + outPath: ./work/deployments/${{ ctx.stage }}/deploy + - uses: yaml-update + config: + path: ./work/deployments/${{ ctx.stage }}/deploy/kustomization.yaml + updates: + - key: namespace + value: ${{ outputs['deployment-meta'].namespace }} + - key: resources + value: + - ../../source/base + - key: components + value: + - ../../envTypes/${{ outputs['deployment-meta'].envType }} + - uses: kustomize-set-image + config: + path: ./work/deployments/${{ ctx.stage }}/deploy + images: + - image: code-dot-org + newName: ${{ vars.imageRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: kustomize-build + config: + path: ./work/deployments/${{ ctx.stage }}/deploy + outPath: ./out/apps/codeai/deployments/${{ ctx.stage }}/deploy + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/release-metadata.yaml + outPath: ./out/apps/codeai/deployments/${{ ctx.stage }}/release-metadata.yaml - uses: yaml-update config: - path: ./gitops/apps/codeai/deployments/staging/values.yaml + path: ./out/apps/codeai/deployments/${{ ctx.stage }}/release-metadata.yaml updates: - - key: image - value: ghcr.io/code-dot-org/code-dot-org:${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} + - key: release.gitCommit + value: ${{ outputs['verify-release'].verifiedGitCommit }} + - key: release.image.repoURL + value: ${{ imageFrom(vars.imageRepo).RepoURL }} + - key: release.image.tag + value: ${{ imageFrom(vars.imageRepo).Tag }} + - key: release.image.digest + value: ${{ imageFrom(vars.imageRepo).Digest }} + - key: release.capsule.repoURL + value: ${{ vars.capsuleRepo }} + - key: release.capsule.tag + value: ${{ outputs['capsule-release'].imageTag }} + - key: release.capsule.packageKind + value: ${{ outputs['verify-release'].verifiedPackageKind }} + - key: release.capsule.packagePath + value: ${{ outputs['verify-release'].verifiedPackagePath }} - uses: git-commit config: - path: ./gitops + path: ./out message: | - Promote staging to ${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} [skip ci] + Render staging from OCI release capsule + + image: ${{ imageFrom(vars.imageRepo).RepoURL }}:${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} - uses: git-push config: - path: ./gitops + path: ./out + branch: ${{ vars.targetBranch }} diff --git a/apps/kargo-project-codeai/stages/test.yaml b/apps/kargo-project-codeai/stages/test.yaml index c029450..104b2db 100644 --- a/apps/kargo-project-codeai/stages/test.yaml +++ b/apps/kargo-project-codeai/stages/test.yaml @@ -7,33 +7,208 @@ spec: requestedFreight: - origin: kind: Warehouse - name: kargo-project-codeai + name: codeai-image sources: stages: - staging + requiredSoakTime: 30m + verification: + analysisTemplates: + - name: codeai-test-smoke + args: + - name: healthURL + value: http://test-cdo-dashboard.test.svc.cluster.local:3000/health_check promotionTemplate: spec: vars: - name: gitopsRepo value: https://github.com/code-dot-org/k8s-gitops.git + - name: imageRepo + value: ghcr.io/code-dot-org/code-dot-org + - name: capsuleRepo + value: ghcr.io/code-dot-org/codeai-release-capsule + - name: targetBranch + value: stage/${{ ctx.stage }} steps: - uses: git-clone config: repoURL: ${{ vars.gitopsRepo }} checkout: - branch: main - path: ./gitops + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ./out + - uses: yaml-parse + as: deployment-meta + config: + path: ./src/apps/codeai/deployments/${{ ctx.stage }}/deployment.yaml + outputs: + - name: envType + fromExpression: envType + - name: namespace + fromExpression: namespace + - uses: yaml-parse + as: legacy-gate + config: + path: ./src/warehouses/codeai/legacy-gitflow/test/merged/${{ imageFrom(vars.imageRepo).Tag }}.yaml + outputs: + - name: mergedTag + fromExpression: tag + - uses: git-clear + config: + path: ./out/apps/codeai/deployments/${{ ctx.stage }}/deploy + - uses: oci-download + config: + imageRef: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + outPath: ./capsule.tgz + - uses: untar + config: + inPath: ./capsule.tgz + outPath: ./capsule + - uses: yaml-parse + as: capsule-release + config: + path: ./capsule/release.yaml + outputs: + - name: gitCommit + fromExpression: gitCommit + - name: imageRepo + fromExpression: image.repoURL + - name: imageTag + fromExpression: image.tag + - name: imageDigest + fromExpression: image.digest + - name: packageKind + fromExpression: package.kind + - name: packagePath + fromExpression: package.path + - uses: http + as: verify-release + config: + method: POST + url: http://codeai-release-verify.kargo-project-codeai.svc.cluster.local/verify + headers: + - name: Content-Type + value: application/json + body: | + ${{ quote({ + "image": { + "repoURL": imageFrom(vars.imageRepo).RepoURL, + "tag": imageFrom(vars.imageRepo).Tag, + "digest": imageFrom(vars.imageRepo).Digest + }, + "capsule": { + "repoURL": vars.capsuleRepo, + "tag": imageFrom(vars.imageRepo).Tag + }, + "release": { + "gitCommit": outputs['capsule-release'].gitCommit, + "imageRepo": outputs['capsule-release'].imageRepo, + "imageTag": outputs['capsule-release'].imageTag, + "imageDigest": outputs['capsule-release'].imageDigest, + "packageKind": outputs['capsule-release'].packageKind, + "packagePath": outputs['capsule-release'].packagePath + } + }) }} + successExpression: response.status == 200 && response.body.ok == true + failureExpression: response.status >= 400 || response.body.ok == false + outputs: + - name: verifiedGitCommit + fromExpression: response.body.release.gitCommit + - name: verifiedPackageKind + fromExpression: response.body.release.packageKind + - name: verifiedPackagePath + fromExpression: response.body.release.packagePath + - uses: set-metadata + config: + updates: + - kind: Stage + name: test + values: + promotedImage: + repoURL: ${{ imageFrom(vars.imageRepo).RepoURL }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: + repoURL: ${{ vars.capsuleRepo }} + tag: ${{ outputs['capsule-release'].imageTag }} + packageKind: ${{ outputs['verify-release'].verifiedPackageKind }} + packagePath: ${{ outputs['verify-release'].verifiedPackagePath }} + gitCommit: ${{ outputs['verify-release'].verifiedGitCommit }} + - uses: copy + config: + inPath: ./capsule/package/kustomize + outPath: ./work/deployments/source + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/${{ outputs['deployment-meta'].envType }} + outPath: ./work/deployments/envTypes/${{ outputs['deployment-meta'].envType }} + - uses: copy + config: + inPath: ./src/apps/codeai/envTypes/components + outPath: ./work/deployments/envTypes/components + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/deploy + outPath: ./work/deployments/${{ ctx.stage }}/deploy + - uses: yaml-update + config: + path: ./work/deployments/${{ ctx.stage }}/deploy/kustomization.yaml + updates: + - key: namespace + value: ${{ outputs['deployment-meta'].namespace }} + - key: resources + value: + - ../../source/base + - key: components + value: + - ../../envTypes/${{ outputs['deployment-meta'].envType }} + - uses: kustomize-set-image + config: + path: ./work/deployments/${{ ctx.stage }}/deploy + images: + - image: code-dot-org + newName: ${{ vars.imageRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: kustomize-build + config: + path: ./work/deployments/${{ ctx.stage }}/deploy + outPath: ./out/apps/codeai/deployments/${{ ctx.stage }}/deploy + - uses: copy + config: + inPath: ./src/apps/codeai/kargo/templates/release-metadata.yaml + outPath: ./out/apps/codeai/deployments/${{ ctx.stage }}/release-metadata.yaml - uses: yaml-update config: - path: ./gitops/apps/codeai/deployments/test/values.yaml + path: ./out/apps/codeai/deployments/${{ ctx.stage }}/release-metadata.yaml updates: - - key: image - value: ghcr.io/code-dot-org/code-dot-org:${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} + - key: release.gitCommit + value: ${{ outputs['verify-release'].verifiedGitCommit }} + - key: release.image.repoURL + value: ${{ imageFrom(vars.imageRepo).RepoURL }} + - key: release.image.tag + value: ${{ imageFrom(vars.imageRepo).Tag }} + - key: release.image.digest + value: ${{ imageFrom(vars.imageRepo).Digest }} + - key: release.capsule.repoURL + value: ${{ vars.capsuleRepo }} + - key: release.capsule.tag + value: ${{ outputs['capsule-release'].imageTag }} + - key: release.capsule.packageKind + value: ${{ outputs['verify-release'].verifiedPackageKind }} + - key: release.capsule.packagePath + value: ${{ outputs['verify-release'].verifiedPackagePath }} - uses: git-commit config: - path: ./gitops + path: ./out message: | - Promote test to ${{ imageFrom("ghcr.io/code-dot-org/code-dot-org").Tag }} [skip ci] + Render test from OCI release capsule + + image: ${{ imageFrom(vars.imageRepo).RepoURL }}:${{ imageFrom(vars.imageRepo).Tag }} + digest: ${{ imageFrom(vars.imageRepo).Digest }} + capsule: ${{ vars.capsuleRepo }}:${{ imageFrom(vars.imageRepo).Tag }} - uses: git-push config: - path: ./gitops + path: ./out + branch: ${{ vars.targetBranch }} diff --git a/apps/kargo-project-codeai/warehouse.yaml b/apps/kargo-project-codeai/warehouse.yaml index ff9ebe0..8f0e8c8 100644 --- a/apps/kargo-project-codeai/warehouse.yaml +++ b/apps/kargo-project-codeai/warehouse.yaml @@ -1,12 +1,16 @@ apiVersion: kargo.akuity.io/v1alpha1 kind: Warehouse metadata: - name: kargo-project-codeai + name: codeai-image namespace: kargo-project-codeai spec: subscriptions: - image: repoURL: ghcr.io/code-dot-org/code-dot-org + imageSelectionStrategy: NewestBuild + cacheByTag: true + allowTagsRegexes: + - '^git-[0-9a-f]{40}$' ignoreTagsRegexes: # Ignore single-platform images when we have a multiplatform option - '.*-amd64$' diff --git a/warehouses/codeai/legacy-gitflow/README.md b/warehouses/codeai/legacy-gitflow/README.md new file mode 100644 index 0000000..a62c8cd --- /dev/null +++ b/warehouses/codeai/legacy-gitflow/README.md @@ -0,0 +1,3 @@ +These files are legacy gitflow coexistence gates, not Freight inputs. +GitHub Actions in `code-dot-org` update `current.yaml` and `merged/git-.yaml` +when the legacy `staging`, `test`, `levelbuilder`, and `production` branches advance. diff --git a/warehouses/codeai/legacy-gitflow/levelbuilder/merged/.gitkeep b/warehouses/codeai/legacy-gitflow/levelbuilder/merged/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/warehouses/codeai/legacy-gitflow/levelbuilder/merged/.gitkeep @@ -0,0 +1 @@ + diff --git a/warehouses/codeai/legacy-gitflow/production/merged/.gitkeep b/warehouses/codeai/legacy-gitflow/production/merged/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/warehouses/codeai/legacy-gitflow/production/merged/.gitkeep @@ -0,0 +1 @@ + diff --git a/warehouses/codeai/legacy-gitflow/staging/merged/.gitkeep b/warehouses/codeai/legacy-gitflow/staging/merged/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/warehouses/codeai/legacy-gitflow/staging/merged/.gitkeep @@ -0,0 +1 @@ + diff --git a/warehouses/codeai/legacy-gitflow/test/merged/.gitkeep b/warehouses/codeai/legacy-gitflow/test/merged/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/warehouses/codeai/legacy-gitflow/test/merged/.gitkeep @@ -0,0 +1 @@ +