From d662710e090423379c3ef051d29a41d8c26d0e40 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 13 Jul 2026 07:51:36 +0200 Subject: [PATCH] ci: add PaC GitOps delivery --- .gitignore | 1 + .tekton/sub2rank-nc01-pac.yaml | 481 +++++++++++++++++++++++++++++++++ scripts/ci/build-image.sh | 48 ++++ scripts/ci/publish-gitops.mjs | 195 +++++++++++++ 4 files changed, 725 insertions(+) create mode 100644 .tekton/sub2rank-nc01-pac.yaml create mode 100755 scripts/ci/build-image.sh create mode 100755 scripts/ci/publish-gitops.mjs diff --git a/.gitignore b/.gitignore index a2a09a6..457dbcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +.worktree/ .state/ *.sqlite *.sqlite-shm diff --git a/.tekton/sub2rank-nc01-pac.yaml b/.tekton/sub2rank-nc01-pac.yaml new file mode 100644 index 0000000..60ed3aa --- /dev/null +++ b/.tekton/sub2rank-nc01-pac.yaml @@ -0,0 +1,481 @@ +apiVersion: tekton.dev/v1 +kind: PipelineRun +metadata: + name: "sub2rank-nc01-{{ revision }}" + namespace: devops-infra + annotations: + pipelinesascode.tekton.dev/on-event: "[push]" + pipelinesascode.tekton.dev/on-target-branch: "[master]" + pipelinesascode.tekton.dev/on-cel-expression: "event == 'push' && target_branch == 'master' && node == 'NC01'" + pipelinesascode.tekton.dev/max-keep-runs: "8" + unidesk.ai/pac-admission-provenance: admission-pac-v2:platform-infra-sub2rank-nc01 + unidesk.ai/owning-config-ref: "config/platform-infra/sub2rank.yaml#delivery" + unidesk.ai/effective-config-sha256: sha256:335c9e900a9b11998b8881e08ab36b89dce9c8c62397b5317ba3e9399f985336 + unidesk.ai/source-artifact-renderer: sub2rank-platform-service + unidesk.ai/source-artifact-mode: embedded-pipeline-spec + labels: + app.kubernetes.io/name: sub2rank + app.kubernetes.io/part-of: platform-infra + unidesk.ai/node: NC01 + unidesk.ai/source-commit: "{{ revision }}" + unidesk.ai/trigger: pipelines-as-code +spec: + pipelineSpec: + params: + - name: git-read-url + type: string + default: http://gitea-http.devops-infra.svc.cluster.local:3000/mirrors/pikasTech-sub2rank.git + - name: source-branch + type: string + default: master + - name: revision + type: string + - name: source-stage-ref + type: string + - name: config-path + type: string + default: config/sub2rank.yaml + - name: dockerfile + type: string + default: Dockerfile + - name: image-repository + type: string + default: 127.0.0.1:5000/sub2rank/sub2rank + - name: tools-image + type: string + default: 127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1 + - name: buildkit-image + type: string + default: 127.0.0.1:5000/hwlab/buildkit:rootless + - name: build-network + type: string + default: host + - name: build-http-proxy + type: string + default: http://127.0.0.1:10808 + - name: build-https-proxy + type: string + default: http://127.0.0.1:10808 + - name: build-all-proxy + type: string + default: http://127.0.0.1:10808 + - name: build-no-proxy + type: string + default: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,hyueapi.com,.hyueapi.com" + - name: gitops-read-url + type: string + default: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/unidesk.git + - name: gitops-write-url + type: string + default: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/unidesk.git + - name: gitops-branch + type: string + default: unidesk-host-gitops + - name: gitops-manifest-path + type: string + default: deploy/gitops/platform-infra/sub2rank-nc01/resources.yaml + - name: gitops-release-state-path + type: string + default: deploy/gitops-state/platform-infra/sub2rank-nc01.json + - name: gitops-max-push-attempts + type: string + default: "3" + - name: gitops-author-name + type: string + default: UniDesk Sub2Rank CI + - name: gitops-author-email + type: string + default: sub2rank-ci@unidesk.local + - name: manifest-template-b64 + type: string + default: YXBpVmVyc2lvbjogdjEKa2luZDogQ29uZmlnTWFwCm1ldGFkYXRhOgogIG5hbWU6IHN1YjJyYW5rLWNvbmZpZwogIG5hbWVzcGFjZTogcGxhdGZvcm0taW5mcmEKICBsYWJlbHM6CiAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuawogICAgYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZjogcGxhdGZvcm0taW5mcmEKICAgIGFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnk6IHVuaWRlc2sKYmluYXJ5RGF0YToKICBzdWIycmFuay55YW1sOiAiX19TVUIyUkFOS19DT05GSUdfQkFTRTY0X18iCi0tLQphcGlWZXJzaW9uOiB2MQpraW5kOiBQZXJzaXN0ZW50Vm9sdW1lQ2xhaW0KbWV0YWRhdGE6CiAgbmFtZTogc3ViMnJhbmstZGF0YQogIG5hbWVzcGFjZTogcGxhdGZvcm0taW5mcmEKICBsYWJlbHM6CiAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuawogICAgYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZjogcGxhdGZvcm0taW5mcmEKICAgIGFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnk6IHVuaWRlc2sKc3BlYzoKICBhY2Nlc3NNb2RlczoKICAgIC0gUmVhZFdyaXRlT25jZQogIHJlc291cmNlczoKICAgIHJlcXVlc3RzOgogICAgICBzdG9yYWdlOiAxR2kKLS0tCmFwaVZlcnNpb246IHYxCmtpbmQ6IFNlcnZpY2UKbWV0YWRhdGE6CiAgbmFtZTogc3ViMnJhbmsKICBuYW1lc3BhY2U6IHBsYXRmb3JtLWluZnJhCiAgbGFiZWxzOgogICAgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZTogc3ViMnJhbmsKICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICBhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5OiB1bmlkZXNrCnNwZWM6CiAgdHlwZTogQ2x1c3RlcklQCiAgc2VsZWN0b3I6CiAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuawogIHBvcnRzOgogICAgLSBuYW1lOiBodHRwCiAgICAgIHBvcnQ6IDgwODAKICAgICAgdGFyZ2V0UG9ydDogaHR0cAotLS0KYXBpVmVyc2lvbjogYXBwcy92MQpraW5kOiBEZXBsb3ltZW50Cm1ldGFkYXRhOgogIG5hbWU6IHN1YjJyYW5rCiAgbmFtZXNwYWNlOiBwbGF0Zm9ybS1pbmZyYQogIGxhYmVsczoKICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rCiAgICBhcHAua3ViZXJuZXRlcy5pby9wYXJ0LW9mOiBwbGF0Zm9ybS1pbmZyYQogICAgYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieTogdW5pZGVzawpzcGVjOgogIHJlcGxpY2FzOiAxCiAgc3RyYXRlZ3k6CiAgICB0eXBlOiBSZWNyZWF0ZQogIHNlbGVjdG9yOgogICAgbWF0Y2hMYWJlbHM6CiAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rCiAgdGVtcGxhdGU6CiAgICBtZXRhZGF0YToKICAgICAgbGFiZWxzOgogICAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rCiAgICAgICAgYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZjogcGxhdGZvcm0taW5mcmEKICAgICAgYW5ub3RhdGlvbnM6CiAgICAgICAgdW5pZGVzay5haS9zdWIycmFuay1jb25maWctc2hhMjU2OiAiX19TVUIyUkFOS19DT05GSUdfU0hBMjU2X18iCiAgICAgICAgdW5pZGVzay5haS9zdWIycmFuay1kZXBsb3ltZW50LWhhc2g6ICJmNmZhMWMwMjZjMjEwMzc2IgogICAgICAgIHVuaWRlc2suYWkvc291cmNlLWNvbW1pdDogIl9fU1VCMlJBTktfU09VUkNFX0NPTU1JVF9fIgogICAgICAgIHVuaWRlc2suYWkvcHVibGljLWJhc2UtdXJsOiAiaHR0cHM6Ly9yYW5rLnBpa2FweXRob24uY29tIgogICAgc3BlYzoKICAgICAgc2VjdXJpdHlDb250ZXh0OgogICAgICAgIGZzR3JvdXA6IDEwMDAKICAgICAgY29udGFpbmVyczoKICAgICAgICAtIG5hbWU6IHN1YjJyYW5rCiAgICAgICAgICBpbWFnZTogIl9fU1VCMlJBTktfSU1BR0VfUkVGX18iCiAgICAgICAgICBpbWFnZVB1bGxQb2xpY3k6IElmTm90UHJlc2VudAogICAgICAgICAgY29tbWFuZDoKICAgICAgICAgICAgLSAiYnVuIgogICAgICAgICAgICAtICJzcmMvc2VydmVyLnRzIgogICAgICAgICAgICAtICItLWNvbmZpZyIKICAgICAgICAgICAgLSAiL2V0Yy9zdWIycmFuay9zdWIycmFuay55YW1sIgogICAgICAgICAgICAtICItLXJ1bnRpbWUiCiAgICAgICAgICAgIC0gIms4cyIKICAgICAgICAgIHBvcnRzOgogICAgICAgICAgICAtIG5hbWU6IGh0dHAKICAgICAgICAgICAgICBjb250YWluZXJQb3J0OiA4MDgwCiAgICAgICAgICBlbnY6CiAgICAgICAgICAgIC0gbmFtZTogU1VCMlJBTktfQURNSU5fVE9LRU4KICAgICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgICBzZWNyZXRLZXlSZWY6CiAgICAgICAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLXNlY3JldHMKICAgICAgICAgICAgICAgICAga2V5OiBTVUIyUkFOS19BRE1JTl9UT0tFTgogICAgICAgICAgICAtIG5hbWU6IFNVQjJBUElfQURNSU5fRU1BSUwKICAgICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgICBzZWNyZXRLZXlSZWY6CiAgICAgICAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLXNlY3JldHMKICAgICAgICAgICAgICAgICAga2V5OiBTVUIyQVBJX0FETUlOX0VNQUlMCiAgICAgICAgICAgIC0gbmFtZTogU1VCMkFQSV9BRE1JTl9QQVNTV09SRAogICAgICAgICAgICAgIHZhbHVlRnJvbToKICAgICAgICAgICAgICAgIHNlY3JldEtleVJlZjoKICAgICAgICAgICAgICAgICAgbmFtZTogc3ViMnJhbmstc2VjcmV0cwogICAgICAgICAgICAgICAgICBrZXk6IFNVQjJBUElfQURNSU5fUEFTU1dPUkQKICAgICAgICAgIHJlYWRpbmVzc1Byb2JlOgogICAgICAgICAgICBodHRwR2V0OgogICAgICAgICAgICAgIHBhdGg6IC9oZWFsdGgKICAgICAgICAgICAgICBwb3J0OiBodHRwCiAgICAgICAgICAgIGluaXRpYWxEZWxheVNlY29uZHM6IDMKICAgICAgICAgICAgcGVyaW9kU2Vjb25kczogMTAKICAgICAgICAgICAgdGltZW91dFNlY29uZHM6IDMKICAgICAgICAgICAgZmFpbHVyZVRocmVzaG9sZDogNgogICAgICAgICAgbGl2ZW5lc3NQcm9iZToKICAgICAgICAgICAgaHR0cEdldDoKICAgICAgICAgICAgICBwYXRoOiAvaGVhbHRoCiAgICAgICAgICAgICAgcG9ydDogaHR0cAogICAgICAgICAgICBpbml0aWFsRGVsYXlTZWNvbmRzOiAzCiAgICAgICAgICAgIHBlcmlvZFNlY29uZHM6IDEwCiAgICAgICAgICAgIHRpbWVvdXRTZWNvbmRzOiAzCiAgICAgICAgICAgIGZhaWx1cmVUaHJlc2hvbGQ6IDYKICAgICAgICAgIHZvbHVtZU1vdW50czoKICAgICAgICAgICAgLSBuYW1lOiBhcHAtY29uZmlnCiAgICAgICAgICAgICAgbW91bnRQYXRoOiAvZXRjL3N1YjJyYW5rL3N1YjJyYW5rLnlhbWwKICAgICAgICAgICAgICBzdWJQYXRoOiBzdWIycmFuay55YW1sCiAgICAgICAgICAgICAgcmVhZE9ubHk6IHRydWUKICAgICAgICAgICAgLSBuYW1lOiBkYXRhCiAgICAgICAgICAgICAgbW91bnRQYXRoOiAvdmFyL2xpYi9zdWIycmFuawogICAgICB2b2x1bWVzOgogICAgICAgIC0gbmFtZTogYXBwLWNvbmZpZwogICAgICAgICAgY29uZmlnTWFwOgogICAgICAgICAgICBuYW1lOiBzdWIycmFuay1jb25maWcKICAgICAgICAtIG5hbWU6IGRhdGEKICAgICAgICAgIHBlcnNpc3RlbnRWb2x1bWVDbGFpbToKICAgICAgICAgICAgY2xhaW1OYW1lOiBzdWIycmFuay1kYXRhCi0tLQphcGlWZXJzaW9uOiB2MQpraW5kOiBDb25maWdNYXAKbWV0YWRhdGE6CiAgbmFtZTogc3ViMnJhbmstZnJwYy1jb25maWcKICBuYW1lc3BhY2U6IHBsYXRmb3JtLWluZnJhCiAgbGFiZWxzOgogICAgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZTogc3ViMnJhbmstZnJwYwogICAgYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50OiB0dW5uZWwKICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICBhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5OiB1bmlkZXNrCmRhdGE6CiAgZnJwYy50b21sOiB8CiAgICBzZXJ2ZXJBZGRyID0gIjgyLjE1Ni4yMy4yMjAiCiAgICBzZXJ2ZXJQb3J0ID0gMjIwMDAKICAgIGxvZ2luRmFpbEV4aXQgPSB0cnVlCiAgICBhdXRoLnRva2VuID0gInt7IC5FbnZzLkZSUF9UT0tFTiB9fSIKICAgIAogICAgW1twcm94aWVzXV0KICAgIG5hbWUgPSAicGxhdGZvcm0taW5mcmEtc3ViMnJhbmstbmMwMS13ZWIiCiAgICB0eXBlID0gInRjcCIKICAgIGxvY2FsSVAgPSAic3ViMnJhbmsucGxhdGZvcm0taW5mcmEuc3ZjLmNsdXN0ZXIubG9jYWwiCiAgICBsb2NhbFBvcnQgPSA4MDgwCiAgICByZW1vdGVQb3J0ID0gMjIwOTUKICAgIAotLS0KYXBpVmVyc2lvbjogYXBwcy92MQpraW5kOiBEZXBsb3ltZW50Cm1ldGFkYXRhOgogIG5hbWU6IHN1YjJyYW5rLWZycGMKICBuYW1lc3BhY2U6IHBsYXRmb3JtLWluZnJhCiAgbGFiZWxzOgogICAgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZTogc3ViMnJhbmstZnJwYwogICAgYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50OiB0dW5uZWwKICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICBhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5OiB1bmlkZXNrCiAgICB1bmlkZXNrLmFpL3J1bnRpbWUtbm9kZTogTkMwMQogICAgdW5pZGVzay5haS9wdWJsaWMtaG9zdG5hbWU6IHJhbmsucGlrYXB5dGhvbi5jb20Kc3BlYzoKICByZXBsaWNhczogMQogIHN0cmF0ZWd5OgogICAgdHlwZTogUmVjcmVhdGUKICBzZWxlY3RvcjoKICAgIG1hdGNoTGFiZWxzOgogICAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuay1mcnBjCiAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudDogdHVubmVsCiAgdGVtcGxhdGU6CiAgICBtZXRhZGF0YToKICAgICAgbGFiZWxzOgogICAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rLWZycGMKICAgICAgICBhcHAua3ViZXJuZXRlcy5pby9jb21wb25lbnQ6IHR1bm5lbAogICAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICAgIGFubm90YXRpb25zOgogICAgICAgIHVuaWRlc2suYWkvcHVibGljLWJhc2UtdXJsOiAiaHR0cHM6Ly9yYW5rLnBpa2FweXRob24uY29tIgogICAgICAgIHVuaWRlc2suYWkvZnJwLXNlcnZlcjogIjgyLjE1Ni4yMy4yMjA6MjIwMDAiCiAgICAgICAgdW5pZGVzay5haS9mcnAtcmVtb3RlLXBvcnQ6ICIyMjA5NSIKICAgICAgICB1bmlkZXNrLmFpL2ZycGMtY29uZmlnLXNoYTI1NjogIjFkOGY1OGE2ZjU4MjQ3ZGEzMzJkNDBkZWE1YzFkN2I3YzY4NDU1YTU5OTVhYTNiMWQ5NjE1NzA2ZWMxYzk0ZTYiCiAgICBzcGVjOgogICAgICBjb250YWluZXJzOgogICAgICAgIC0gbmFtZTogZnJwYwogICAgICAgICAgaW1hZ2U6IDEyNy4wLjAuMTo1MDAwL2h3bGFiL2ZycGM6djAuNjguMQogICAgICAgICAgaW1hZ2VQdWxsUG9saWN5OiBJZk5vdFByZXNlbnQKICAgICAgICAgIGFyZ3M6CiAgICAgICAgICAgIC0gLWMKICAgICAgICAgICAgLSAvZXRjL2ZycC9mcnBjLnRvbWwKICAgICAgICAgIGVudjoKICAgICAgICAgICAgLSBuYW1lOiBGUlBfVE9LRU4KICAgICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgICBzZWNyZXRLZXlSZWY6CiAgICAgICAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLXNlY3JldHMKICAgICAgICAgICAgICAgICAga2V5OiBGUlBfVE9LRU4KICAgICAgICAgIHZvbHVtZU1vdW50czoKICAgICAgICAgICAgLSBuYW1lOiBmcnBjLWNvbmZpZwogICAgICAgICAgICAgIG1vdW50UGF0aDogL2V0Yy9mcnAvZnJwYy50b21sCiAgICAgICAgICAgICAgc3ViUGF0aDogZnJwYy50b21sCiAgICAgICAgICAgICAgcmVhZE9ubHk6IHRydWUKICAgICAgdm9sdW1lczoKICAgICAgICAtIG5hbWU6IGZycGMtY29uZmlnCiAgICAgICAgICBjb25maWdNYXA6CiAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLWZycGMtY29uZmlnCg== + workspaces: + - name: source + tasks: + - name: source-validate + workspaces: + - name: source + workspace: source + taskSpec: + params: + - name: git-read-url + - name: source-branch + - name: revision + - name: source-stage-ref + - name: config-path + - name: dockerfile + - name: image-repository + - name: tools-image + - name: buildkit-image + - name: build-network + - name: build-http-proxy + - name: build-https-proxy + - name: build-all-proxy + - name: build-no-proxy + - name: gitops-read-url + - name: gitops-write-url + - name: gitops-branch + - name: gitops-manifest-path + - name: gitops-release-state-path + - name: gitops-max-push-attempts + - name: gitops-author-name + - name: gitops-author-email + - name: manifest-template-b64 + workspaces: + - name: source + steps: + - name: checkout-and-cli-validate + image: $(params.tools-image) + imagePullPolicy: IfNotPresent + env: + - name: HTTP_PROXY + value: $(params.build-http-proxy) + - name: http_proxy + value: $(params.build-http-proxy) + - name: HTTPS_PROXY + value: $(params.build-https-proxy) + - name: https_proxy + value: $(params.build-https-proxy) + - name: ALL_PROXY + value: $(params.build-all-proxy) + - name: all_proxy + value: $(params.build-all-proxy) + - name: NO_PROXY + value: $(params.build-no-proxy) + - name: no_proxy + value: $(params.build-no-proxy) + script: "#!/bin/sh\nset -eu\nroot=\"$(workspaces.source.path)\"\nrm -rf \"$root/repo\"\ngit clone --filter=blob:none --no-checkout \"$(params.git-read-url)\" \"$root/repo\"\ncd \"$root/repo\"\ngit fetch --depth=1 --filter=blob:none origin \"+$(params.source-stage-ref):refs/remotes/origin/sub2rank-source-snapshot\"\ngit checkout --detach \"$(params.revision)\"\ntest \"$(git rev-parse HEAD)\" = \"$(params.revision)\"\nbun install --frozen-lockfile\nbun scripts/sub2rank-cli.ts --config \"$(params.config-path)\" config validate\nchmod -R a+rX,g+rwX \"$root/repo\"\nprintf '{\"ok\":true,\"phase\":\"source-validate\",\"sourceCommit\":\"%s\",\"configPath\":\"%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$(params.config-path)\"" + params: + - name: git-read-url + value: $(params.git-read-url) + - name: source-branch + value: $(params.source-branch) + - name: revision + value: $(params.revision) + - name: source-stage-ref + value: $(params.source-stage-ref) + - name: config-path + value: $(params.config-path) + - name: dockerfile + value: $(params.dockerfile) + - name: image-repository + value: $(params.image-repository) + - name: tools-image + value: $(params.tools-image) + - name: buildkit-image + value: $(params.buildkit-image) + - name: build-network + value: $(params.build-network) + - name: build-http-proxy + value: $(params.build-http-proxy) + - name: build-https-proxy + value: $(params.build-https-proxy) + - name: build-all-proxy + value: $(params.build-all-proxy) + - name: build-no-proxy + value: $(params.build-no-proxy) + - name: gitops-read-url + value: $(params.gitops-read-url) + - name: gitops-write-url + value: $(params.gitops-write-url) + - name: gitops-branch + value: $(params.gitops-branch) + - name: gitops-manifest-path + value: $(params.gitops-manifest-path) + - name: gitops-release-state-path + value: $(params.gitops-release-state-path) + - name: gitops-max-push-attempts + value: $(params.gitops-max-push-attempts) + - name: gitops-author-name + value: $(params.gitops-author-name) + - name: gitops-author-email + value: $(params.gitops-author-email) + - name: manifest-template-b64 + value: $(params.manifest-template-b64) + - name: image-build + runAfter: + - source-validate + workspaces: + - name: source + workspace: source + taskSpec: + params: + - name: git-read-url + - name: source-branch + - name: revision + - name: source-stage-ref + - name: config-path + - name: dockerfile + - name: image-repository + - name: tools-image + - name: buildkit-image + - name: build-network + - name: build-http-proxy + - name: build-https-proxy + - name: build-all-proxy + - name: build-no-proxy + - name: gitops-read-url + - name: gitops-write-url + - name: gitops-branch + - name: gitops-manifest-path + - name: gitops-release-state-path + - name: gitops-max-push-attempts + - name: gitops-author-name + - name: gitops-author-email + - name: manifest-template-b64 + workspaces: + - name: source + steps: + - name: build-and-push + image: $(params.buildkit-image) + imagePullPolicy: IfNotPresent + env: + - name: HTTP_PROXY + value: $(params.build-http-proxy) + - name: http_proxy + value: $(params.build-http-proxy) + - name: HTTPS_PROXY + value: $(params.build-https-proxy) + - name: https_proxy + value: $(params.build-https-proxy) + - name: ALL_PROXY + value: $(params.build-all-proxy) + - name: all_proxy + value: $(params.build-all-proxy) + - name: NO_PROXY + value: $(params.build-no-proxy) + - name: no_proxy + value: $(params.build-no-proxy) + - name: BUILDKITD_FLAGS + value: "--oci-worker-no-process-sandbox --oci-worker-net=host --allow-insecure-entitlement network.host" + - name: SOURCE_ROOT + value: $(workspaces.source.path)/repo + - name: SOURCE_COMMIT + value: $(params.revision) + - name: IMAGE_REPOSITORY + value: $(params.image-repository) + - name: DOCKERFILE + value: $(params.dockerfile) + - name: BUILD_NETWORK + value: $(params.build-network) + - name: BUILD_METADATA_FILE + value: $(workspaces.source.path)/build-metadata.json + - name: BUILD_RESULT_FILE + value: $(workspaces.source.path)/build-result.json + securityContext: + privileged: true + runAsUser: 1000 + runAsGroup: 1000 + script: "#!/bin/sh\nset -eu\nexec \"$(workspaces.source.path)/repo/scripts/ci/build-image.sh\"" + params: + - name: git-read-url + value: $(params.git-read-url) + - name: source-branch + value: $(params.source-branch) + - name: revision + value: $(params.revision) + - name: source-stage-ref + value: $(params.source-stage-ref) + - name: config-path + value: $(params.config-path) + - name: dockerfile + value: $(params.dockerfile) + - name: image-repository + value: $(params.image-repository) + - name: tools-image + value: $(params.tools-image) + - name: buildkit-image + value: $(params.buildkit-image) + - name: build-network + value: $(params.build-network) + - name: build-http-proxy + value: $(params.build-http-proxy) + - name: build-https-proxy + value: $(params.build-https-proxy) + - name: build-all-proxy + value: $(params.build-all-proxy) + - name: build-no-proxy + value: $(params.build-no-proxy) + - name: gitops-read-url + value: $(params.gitops-read-url) + - name: gitops-write-url + value: $(params.gitops-write-url) + - name: gitops-branch + value: $(params.gitops-branch) + - name: gitops-manifest-path + value: $(params.gitops-manifest-path) + - name: gitops-release-state-path + value: $(params.gitops-release-state-path) + - name: gitops-max-push-attempts + value: $(params.gitops-max-push-attempts) + - name: gitops-author-name + value: $(params.gitops-author-name) + - name: gitops-author-email + value: $(params.gitops-author-email) + - name: manifest-template-b64 + value: $(params.manifest-template-b64) + - name: gitops-publish + runAfter: + - image-build + workspaces: + - name: source + workspace: source + taskSpec: + params: + - name: git-read-url + - name: source-branch + - name: revision + - name: source-stage-ref + - name: config-path + - name: dockerfile + - name: image-repository + - name: tools-image + - name: buildkit-image + - name: build-network + - name: build-http-proxy + - name: build-https-proxy + - name: build-all-proxy + - name: build-no-proxy + - name: gitops-read-url + - name: gitops-write-url + - name: gitops-branch + - name: gitops-manifest-path + - name: gitops-release-state-path + - name: gitops-max-push-attempts + - name: gitops-author-name + - name: gitops-author-email + - name: manifest-template-b64 + workspaces: + - name: source + steps: + - name: publish-digest-manifest + image: $(params.tools-image) + imagePullPolicy: IfNotPresent + env: + - name: HTTP_PROXY + value: $(params.build-http-proxy) + - name: http_proxy + value: $(params.build-http-proxy) + - name: HTTPS_PROXY + value: $(params.build-https-proxy) + - name: https_proxy + value: $(params.build-https-proxy) + - name: ALL_PROXY + value: $(params.build-all-proxy) + - name: all_proxy + value: $(params.build-all-proxy) + - name: NO_PROXY + value: $(params.build-no-proxy) + - name: no_proxy + value: $(params.build-no-proxy) + script: "#!/bin/sh\nset -eu\nroot=\"$(workspaces.source.path)\"\nexec bun \"$root/repo/scripts/ci/publish-gitops.mjs\" \\\n --source-root \"$root/repo\" \\\n --source-commit \"$(params.revision)\" \\\n --config-path \"$(params.config-path)\" \\\n --metadata \"$root/build-metadata.json\" \\\n --manifest-template-b64 \"$(params.manifest-template-b64)\" \\\n --image-repository \"$(params.image-repository)\" \\\n --gitops-read-url \"$(params.gitops-read-url)\" \\\n --gitops-write-url \"$(params.gitops-write-url)\" \\\n --gitops-branch \"$(params.gitops-branch)\" \\\n --manifest-path \"$(params.gitops-manifest-path)\" \\\n --release-state-path \"$(params.gitops-release-state-path)\" \\\n --max-push-attempts \"$(params.gitops-max-push-attempts)\" \\\n --author-name \"$(params.gitops-author-name)\" \\\n --author-email \"$(params.gitops-author-email)\" \\\n --worktree \"$root/gitops\"" + params: + - name: git-read-url + value: $(params.git-read-url) + - name: source-branch + value: $(params.source-branch) + - name: revision + value: $(params.revision) + - name: source-stage-ref + value: $(params.source-stage-ref) + - name: config-path + value: $(params.config-path) + - name: dockerfile + value: $(params.dockerfile) + - name: image-repository + value: $(params.image-repository) + - name: tools-image + value: $(params.tools-image) + - name: buildkit-image + value: $(params.buildkit-image) + - name: build-network + value: $(params.build-network) + - name: build-http-proxy + value: $(params.build-http-proxy) + - name: build-https-proxy + value: $(params.build-https-proxy) + - name: build-all-proxy + value: $(params.build-all-proxy) + - name: build-no-proxy + value: $(params.build-no-proxy) + - name: gitops-read-url + value: $(params.gitops-read-url) + - name: gitops-write-url + value: $(params.gitops-write-url) + - name: gitops-branch + value: $(params.gitops-branch) + - name: gitops-manifest-path + value: $(params.gitops-manifest-path) + - name: gitops-release-state-path + value: $(params.gitops-release-state-path) + - name: gitops-max-push-attempts + value: $(params.gitops-max-push-attempts) + - name: gitops-author-name + value: $(params.gitops-author-name) + - name: gitops-author-email + value: $(params.gitops-author-email) + - name: manifest-template-b64 + value: $(params.manifest-template-b64) + taskRunTemplate: + serviceAccountName: default + podTemplate: + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + securityContext: + fsGroup: 1000 + params: + - name: git-read-url + value: "{{ git_read_url }}" + - name: source-branch + value: "{{ source_branch }}" + - name: revision + value: "{{ revision }}" + - name: source-stage-ref + value: "{{ source_snapshot_prefix }}/{{ revision }}" + - name: config-path + value: config/sub2rank.yaml + - name: dockerfile + value: Dockerfile + - name: image-repository + value: "{{ image_repository }}" + - name: tools-image + value: 127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-bun-v1 + - name: buildkit-image + value: 127.0.0.1:5000/hwlab/buildkit:rootless + - name: build-network + value: host + - name: build-http-proxy + value: http://127.0.0.1:10808 + - name: build-https-proxy + value: http://127.0.0.1:10808 + - name: build-all-proxy + value: http://127.0.0.1:10808 + - name: build-no-proxy + value: "localhost,127.0.0.1,::1,127.0.0.1:5000,localhost:5000,.svc,.svc.cluster.local,.cluster.local,hyueapi.com,.hyueapi.com" + - name: gitops-read-url + value: http://git-mirror-http.devops-infra.svc.cluster.local:8080/pikasTech/unidesk.git + - name: gitops-write-url + value: http://git-mirror-write.devops-infra.svc.cluster.local:8080/pikasTech/unidesk.git + - name: gitops-branch + value: "{{ gitops_branch }}" + - name: gitops-manifest-path + value: "{{ gitops_manifest_path }}" + - name: gitops-release-state-path + value: deploy/gitops-state/platform-infra/sub2rank-nc01.json + - name: gitops-max-push-attempts + value: "3" + - name: gitops-author-name + value: UniDesk Sub2Rank CI + - name: gitops-author-email + value: sub2rank-ci@unidesk.local + - name: manifest-template-b64 + value: YXBpVmVyc2lvbjogdjEKa2luZDogQ29uZmlnTWFwCm1ldGFkYXRhOgogIG5hbWU6IHN1YjJyYW5rLWNvbmZpZwogIG5hbWVzcGFjZTogcGxhdGZvcm0taW5mcmEKICBsYWJlbHM6CiAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuawogICAgYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZjogcGxhdGZvcm0taW5mcmEKICAgIGFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnk6IHVuaWRlc2sKYmluYXJ5RGF0YToKICBzdWIycmFuay55YW1sOiAiX19TVUIyUkFOS19DT05GSUdfQkFTRTY0X18iCi0tLQphcGlWZXJzaW9uOiB2MQpraW5kOiBQZXJzaXN0ZW50Vm9sdW1lQ2xhaW0KbWV0YWRhdGE6CiAgbmFtZTogc3ViMnJhbmstZGF0YQogIG5hbWVzcGFjZTogcGxhdGZvcm0taW5mcmEKICBsYWJlbHM6CiAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuawogICAgYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZjogcGxhdGZvcm0taW5mcmEKICAgIGFwcC5rdWJlcm5ldGVzLmlvL21hbmFnZWQtYnk6IHVuaWRlc2sKc3BlYzoKICBhY2Nlc3NNb2RlczoKICAgIC0gUmVhZFdyaXRlT25jZQogIHJlc291cmNlczoKICAgIHJlcXVlc3RzOgogICAgICBzdG9yYWdlOiAxR2kKLS0tCmFwaVZlcnNpb246IHYxCmtpbmQ6IFNlcnZpY2UKbWV0YWRhdGE6CiAgbmFtZTogc3ViMnJhbmsKICBuYW1lc3BhY2U6IHBsYXRmb3JtLWluZnJhCiAgbGFiZWxzOgogICAgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZTogc3ViMnJhbmsKICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICBhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5OiB1bmlkZXNrCnNwZWM6CiAgdHlwZTogQ2x1c3RlcklQCiAgc2VsZWN0b3I6CiAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuawogIHBvcnRzOgogICAgLSBuYW1lOiBodHRwCiAgICAgIHBvcnQ6IDgwODAKICAgICAgdGFyZ2V0UG9ydDogaHR0cAotLS0KYXBpVmVyc2lvbjogYXBwcy92MQpraW5kOiBEZXBsb3ltZW50Cm1ldGFkYXRhOgogIG5hbWU6IHN1YjJyYW5rCiAgbmFtZXNwYWNlOiBwbGF0Zm9ybS1pbmZyYQogIGxhYmVsczoKICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rCiAgICBhcHAua3ViZXJuZXRlcy5pby9wYXJ0LW9mOiBwbGF0Zm9ybS1pbmZyYQogICAgYXBwLmt1YmVybmV0ZXMuaW8vbWFuYWdlZC1ieTogdW5pZGVzawpzcGVjOgogIHJlcGxpY2FzOiAxCiAgc3RyYXRlZ3k6CiAgICB0eXBlOiBSZWNyZWF0ZQogIHNlbGVjdG9yOgogICAgbWF0Y2hMYWJlbHM6CiAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rCiAgdGVtcGxhdGU6CiAgICBtZXRhZGF0YToKICAgICAgbGFiZWxzOgogICAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rCiAgICAgICAgYXBwLmt1YmVybmV0ZXMuaW8vcGFydC1vZjogcGxhdGZvcm0taW5mcmEKICAgICAgYW5ub3RhdGlvbnM6CiAgICAgICAgdW5pZGVzay5haS9zdWIycmFuay1jb25maWctc2hhMjU2OiAiX19TVUIyUkFOS19DT05GSUdfU0hBMjU2X18iCiAgICAgICAgdW5pZGVzay5haS9zdWIycmFuay1kZXBsb3ltZW50LWhhc2g6ICJmNmZhMWMwMjZjMjEwMzc2IgogICAgICAgIHVuaWRlc2suYWkvc291cmNlLWNvbW1pdDogIl9fU1VCMlJBTktfU09VUkNFX0NPTU1JVF9fIgogICAgICAgIHVuaWRlc2suYWkvcHVibGljLWJhc2UtdXJsOiAiaHR0cHM6Ly9yYW5rLnBpa2FweXRob24uY29tIgogICAgc3BlYzoKICAgICAgc2VjdXJpdHlDb250ZXh0OgogICAgICAgIGZzR3JvdXA6IDEwMDAKICAgICAgY29udGFpbmVyczoKICAgICAgICAtIG5hbWU6IHN1YjJyYW5rCiAgICAgICAgICBpbWFnZTogIl9fU1VCMlJBTktfSU1BR0VfUkVGX18iCiAgICAgICAgICBpbWFnZVB1bGxQb2xpY3k6IElmTm90UHJlc2VudAogICAgICAgICAgY29tbWFuZDoKICAgICAgICAgICAgLSAiYnVuIgogICAgICAgICAgICAtICJzcmMvc2VydmVyLnRzIgogICAgICAgICAgICAtICItLWNvbmZpZyIKICAgICAgICAgICAgLSAiL2V0Yy9zdWIycmFuay9zdWIycmFuay55YW1sIgogICAgICAgICAgICAtICItLXJ1bnRpbWUiCiAgICAgICAgICAgIC0gIms4cyIKICAgICAgICAgIHBvcnRzOgogICAgICAgICAgICAtIG5hbWU6IGh0dHAKICAgICAgICAgICAgICBjb250YWluZXJQb3J0OiA4MDgwCiAgICAgICAgICBlbnY6CiAgICAgICAgICAgIC0gbmFtZTogU1VCMlJBTktfQURNSU5fVE9LRU4KICAgICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgICBzZWNyZXRLZXlSZWY6CiAgICAgICAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLXNlY3JldHMKICAgICAgICAgICAgICAgICAga2V5OiBTVUIyUkFOS19BRE1JTl9UT0tFTgogICAgICAgICAgICAtIG5hbWU6IFNVQjJBUElfQURNSU5fRU1BSUwKICAgICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgICBzZWNyZXRLZXlSZWY6CiAgICAgICAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLXNlY3JldHMKICAgICAgICAgICAgICAgICAga2V5OiBTVUIyQVBJX0FETUlOX0VNQUlMCiAgICAgICAgICAgIC0gbmFtZTogU1VCMkFQSV9BRE1JTl9QQVNTV09SRAogICAgICAgICAgICAgIHZhbHVlRnJvbToKICAgICAgICAgICAgICAgIHNlY3JldEtleVJlZjoKICAgICAgICAgICAgICAgICAgbmFtZTogc3ViMnJhbmstc2VjcmV0cwogICAgICAgICAgICAgICAgICBrZXk6IFNVQjJBUElfQURNSU5fUEFTU1dPUkQKICAgICAgICAgIHJlYWRpbmVzc1Byb2JlOgogICAgICAgICAgICBodHRwR2V0OgogICAgICAgICAgICAgIHBhdGg6IC9oZWFsdGgKICAgICAgICAgICAgICBwb3J0OiBodHRwCiAgICAgICAgICAgIGluaXRpYWxEZWxheVNlY29uZHM6IDMKICAgICAgICAgICAgcGVyaW9kU2Vjb25kczogMTAKICAgICAgICAgICAgdGltZW91dFNlY29uZHM6IDMKICAgICAgICAgICAgZmFpbHVyZVRocmVzaG9sZDogNgogICAgICAgICAgbGl2ZW5lc3NQcm9iZToKICAgICAgICAgICAgaHR0cEdldDoKICAgICAgICAgICAgICBwYXRoOiAvaGVhbHRoCiAgICAgICAgICAgICAgcG9ydDogaHR0cAogICAgICAgICAgICBpbml0aWFsRGVsYXlTZWNvbmRzOiAzCiAgICAgICAgICAgIHBlcmlvZFNlY29uZHM6IDEwCiAgICAgICAgICAgIHRpbWVvdXRTZWNvbmRzOiAzCiAgICAgICAgICAgIGZhaWx1cmVUaHJlc2hvbGQ6IDYKICAgICAgICAgIHZvbHVtZU1vdW50czoKICAgICAgICAgICAgLSBuYW1lOiBhcHAtY29uZmlnCiAgICAgICAgICAgICAgbW91bnRQYXRoOiAvZXRjL3N1YjJyYW5rL3N1YjJyYW5rLnlhbWwKICAgICAgICAgICAgICBzdWJQYXRoOiBzdWIycmFuay55YW1sCiAgICAgICAgICAgICAgcmVhZE9ubHk6IHRydWUKICAgICAgICAgICAgLSBuYW1lOiBkYXRhCiAgICAgICAgICAgICAgbW91bnRQYXRoOiAvdmFyL2xpYi9zdWIycmFuawogICAgICB2b2x1bWVzOgogICAgICAgIC0gbmFtZTogYXBwLWNvbmZpZwogICAgICAgICAgY29uZmlnTWFwOgogICAgICAgICAgICBuYW1lOiBzdWIycmFuay1jb25maWcKICAgICAgICAtIG5hbWU6IGRhdGEKICAgICAgICAgIHBlcnNpc3RlbnRWb2x1bWVDbGFpbToKICAgICAgICAgICAgY2xhaW1OYW1lOiBzdWIycmFuay1kYXRhCi0tLQphcGlWZXJzaW9uOiB2MQpraW5kOiBDb25maWdNYXAKbWV0YWRhdGE6CiAgbmFtZTogc3ViMnJhbmstZnJwYy1jb25maWcKICBuYW1lc3BhY2U6IHBsYXRmb3JtLWluZnJhCiAgbGFiZWxzOgogICAgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZTogc3ViMnJhbmstZnJwYwogICAgYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50OiB0dW5uZWwKICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICBhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5OiB1bmlkZXNrCmRhdGE6CiAgZnJwYy50b21sOiB8CiAgICBzZXJ2ZXJBZGRyID0gIjgyLjE1Ni4yMy4yMjAiCiAgICBzZXJ2ZXJQb3J0ID0gMjIwMDAKICAgIGxvZ2luRmFpbEV4aXQgPSB0cnVlCiAgICBhdXRoLnRva2VuID0gInt7IC5FbnZzLkZSUF9UT0tFTiB9fSIKICAgIAogICAgW1twcm94aWVzXV0KICAgIG5hbWUgPSAicGxhdGZvcm0taW5mcmEtc3ViMnJhbmstbmMwMS13ZWIiCiAgICB0eXBlID0gInRjcCIKICAgIGxvY2FsSVAgPSAic3ViMnJhbmsucGxhdGZvcm0taW5mcmEuc3ZjLmNsdXN0ZXIubG9jYWwiCiAgICBsb2NhbFBvcnQgPSA4MDgwCiAgICByZW1vdGVQb3J0ID0gMjIwOTUKICAgIAotLS0KYXBpVmVyc2lvbjogYXBwcy92MQpraW5kOiBEZXBsb3ltZW50Cm1ldGFkYXRhOgogIG5hbWU6IHN1YjJyYW5rLWZycGMKICBuYW1lc3BhY2U6IHBsYXRmb3JtLWluZnJhCiAgbGFiZWxzOgogICAgYXBwLmt1YmVybmV0ZXMuaW8vbmFtZTogc3ViMnJhbmstZnJwYwogICAgYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50OiB0dW5uZWwKICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICBhcHAua3ViZXJuZXRlcy5pby9tYW5hZ2VkLWJ5OiB1bmlkZXNrCiAgICB1bmlkZXNrLmFpL3J1bnRpbWUtbm9kZTogTkMwMQogICAgdW5pZGVzay5haS9wdWJsaWMtaG9zdG5hbWU6IHJhbmsucGlrYXB5dGhvbi5jb20Kc3BlYzoKICByZXBsaWNhczogMQogIHN0cmF0ZWd5OgogICAgdHlwZTogUmVjcmVhdGUKICBzZWxlY3RvcjoKICAgIG1hdGNoTGFiZWxzOgogICAgICBhcHAua3ViZXJuZXRlcy5pby9uYW1lOiBzdWIycmFuay1mcnBjCiAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudDogdHVubmVsCiAgdGVtcGxhdGU6CiAgICBtZXRhZGF0YToKICAgICAgbGFiZWxzOgogICAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL25hbWU6IHN1YjJyYW5rLWZycGMKICAgICAgICBhcHAua3ViZXJuZXRlcy5pby9jb21wb25lbnQ6IHR1bm5lbAogICAgICAgIGFwcC5rdWJlcm5ldGVzLmlvL3BhcnQtb2Y6IHBsYXRmb3JtLWluZnJhCiAgICAgIGFubm90YXRpb25zOgogICAgICAgIHVuaWRlc2suYWkvcHVibGljLWJhc2UtdXJsOiAiaHR0cHM6Ly9yYW5rLnBpa2FweXRob24uY29tIgogICAgICAgIHVuaWRlc2suYWkvZnJwLXNlcnZlcjogIjgyLjE1Ni4yMy4yMjA6MjIwMDAiCiAgICAgICAgdW5pZGVzay5haS9mcnAtcmVtb3RlLXBvcnQ6ICIyMjA5NSIKICAgICAgICB1bmlkZXNrLmFpL2ZycGMtY29uZmlnLXNoYTI1NjogIjFkOGY1OGE2ZjU4MjQ3ZGEzMzJkNDBkZWE1YzFkN2I3YzY4NDU1YTU5OTVhYTNiMWQ5NjE1NzA2ZWMxYzk0ZTYiCiAgICBzcGVjOgogICAgICBjb250YWluZXJzOgogICAgICAgIC0gbmFtZTogZnJwYwogICAgICAgICAgaW1hZ2U6IDEyNy4wLjAuMTo1MDAwL2h3bGFiL2ZycGM6djAuNjguMQogICAgICAgICAgaW1hZ2VQdWxsUG9saWN5OiBJZk5vdFByZXNlbnQKICAgICAgICAgIGFyZ3M6CiAgICAgICAgICAgIC0gLWMKICAgICAgICAgICAgLSAvZXRjL2ZycC9mcnBjLnRvbWwKICAgICAgICAgIGVudjoKICAgICAgICAgICAgLSBuYW1lOiBGUlBfVE9LRU4KICAgICAgICAgICAgICB2YWx1ZUZyb206CiAgICAgICAgICAgICAgICBzZWNyZXRLZXlSZWY6CiAgICAgICAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLXNlY3JldHMKICAgICAgICAgICAgICAgICAga2V5OiBGUlBfVE9LRU4KICAgICAgICAgIHZvbHVtZU1vdW50czoKICAgICAgICAgICAgLSBuYW1lOiBmcnBjLWNvbmZpZwogICAgICAgICAgICAgIG1vdW50UGF0aDogL2V0Yy9mcnAvZnJwYy50b21sCiAgICAgICAgICAgICAgc3ViUGF0aDogZnJwYy50b21sCiAgICAgICAgICAgICAgcmVhZE9ubHk6IHRydWUKICAgICAgdm9sdW1lczoKICAgICAgICAtIG5hbWU6IGZycGMtY29uZmlnCiAgICAgICAgICBjb25maWdNYXA6CiAgICAgICAgICAgIG5hbWU6IHN1YjJyYW5rLWZycGMtY29uZmlnCg== + workspaces: + - name: source + volumeClaimTemplate: + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 4Gi diff --git a/scripts/ci/build-image.sh b/scripts/ci/build-image.sh new file mode 100755 index 0000000..bb33890 --- /dev/null +++ b/scripts/ci/build-image.sh @@ -0,0 +1,48 @@ +#!/bin/sh +set -eu + +source_root=${SOURCE_ROOT:?SOURCE_ROOT is required} +source_commit=${SOURCE_COMMIT:?SOURCE_COMMIT is required} +image_repository=${IMAGE_REPOSITORY:?IMAGE_REPOSITORY is required} +dockerfile=${DOCKERFILE:?DOCKERFILE is required} +build_network=${BUILD_NETWORK:?BUILD_NETWORK is required} +metadata_file=${BUILD_METADATA_FILE:?BUILD_METADATA_FILE is required} +result_file=${BUILD_RESULT_FILE:?BUILD_RESULT_FILE is required} + +case "$source_commit" in + *[!0-9a-f]*|'') printf '%s\n' 'SOURCE_COMMIT must be a full lowercase Git SHA' >&2; exit 2 ;; +esac +[ "${#source_commit}" -eq 40 ] || { printf '%s\n' 'SOURCE_COMMIT must be 40 characters' >&2; exit 2; } +[ "$build_network" = host ] || { printf '%s\n' 'BUILD_NETWORK must be host' >&2; exit 2; } +case "$dockerfile" in + /*|*../*|*/..|..) printf '%s\n' 'DOCKERFILE must be a safe relative path' >&2; exit 2 ;; +esac +[ -f "$source_root/$dockerfile" ] || { printf '%s\n' 'Dockerfile is missing' >&2; exit 2; } + +image_ref="$image_repository:$(printf '%s' "$source_commit" | cut -c1-12)" +rm -f "$metadata_file" "$result_file" + +buildctl-daemonless.sh build \ + --allow network.host \ + --frontend dockerfile.v0 \ + --local context="$source_root" \ + --local dockerfile="$source_root" \ + --opt "filename=$dockerfile" \ + --opt "network=$build_network" \ + --opt "build-arg:HTTP_PROXY=${HTTP_PROXY:-}" \ + --opt "build-arg:HTTPS_PROXY=${HTTPS_PROXY:-}" \ + --opt "build-arg:ALL_PROXY=${ALL_PROXY:-}" \ + --opt "build-arg:NO_PROXY=${NO_PROXY:-}" \ + --metadata-file "$metadata_file" \ + --output "type=image,name=$image_ref,push=true,registry.insecure=true" + +[ -s "$metadata_file" ] || { printf '%s\n' 'BuildKit metadata is missing' >&2; exit 2; } +digest=$(tr -d '\n' < "$metadata_file" | sed -n 's/.*"containerimage.digest"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1) +case "$digest" in + sha256:????????????????????????????????????????????????????????????????) ;; + *) printf '%s\n' 'BuildKit metadata digest is invalid' >&2; exit 2 ;; +esac + +printf '{"ok":true,"phase":"image-build","status":"built","imageStatus":"built","sourceCommit":"%s","imageRef":"%s","digest":"%s","digestRef":"%s@%s","valuesPrinted":false}\n' \ + "$source_commit" "$image_ref" "$digest" "$image_repository" "$digest" > "$result_file" +cat "$result_file" diff --git a/scripts/ci/publish-gitops.mjs b/scripts/ci/publish-gitops.mjs new file mode 100755 index 0000000..09908f3 --- /dev/null +++ b/scripts/ci/publish-gitops.mjs @@ -0,0 +1,195 @@ +#!/usr/bin/env bun +import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { parseAllDocuments } from "yaml"; + +const placeholders = { + image: "__SUB2RANK_IMAGE_REF__", + configBase64: "__SUB2RANK_CONFIG_BASE64__", + configSha256: "__SUB2RANK_CONFIG_SHA256__", + sourceCommit: "__SUB2RANK_SOURCE_COMMIT__", +}; + +function option(name) { + const index = process.argv.indexOf(name); + if (index === -1) return null; + const value = process.argv[index + 1]; + if (value === undefined || value.length === 0 || value.startsWith("--")) throw new Error(`${name} requires a value`); + return value; +} + +function required(value, path) { + if (typeof value !== "string" || value.length === 0 || value.includes("\n")) throw new Error(`${path} must be a non-empty single-line string`); + return value; +} + +function record(value, path) { + if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`); + return value; +} + +function safePath(value, path) { + const result = required(value, path); + if (result.startsWith("/") || result.split(/[\\/]/u).includes("..") || !/^[A-Za-z0-9._/-]+$/u.test(result)) throw new Error(`${path} must be a safe relative path`); + return result; +} + +function run(command, args, cwd, allowFailure = false) { + const result = spawnSync(command, args, { cwd, encoding: "utf8", maxBuffer: 4 * 1024 * 1024 }); + if (result.error !== undefined) throw result.error; + if (result.status !== 0 && !allowFailure) { + const detail = `${result.stderr || result.stdout || "command failed"}`.trim().slice(-2000); + throw new Error(`${command} failed (${result.status}): ${detail}`); + } + return result; +} + +function sha256(value) { + return createHash("sha256").update(value).digest("hex"); +} + +function replaceAllRequired(template, replacements) { + let output = template; + for (const [from, to] of replacements) { + if (!output.includes(from)) throw new Error(`manifest template is missing ${from}`); + output = output.split(from).join(to); + } + for (const placeholder of Object.values(placeholders)) { + if (output.includes(placeholder)) throw new Error(`manifest template retained ${placeholder}`); + } + return output; +} + +function validateManifest(manifest, expected) { + const documents = parseAllDocuments(manifest); + const errors = documents.flatMap((document) => document.errors); + if (errors.length > 0) throw new Error(`rendered manifest is invalid YAML: ${errors[0].message}`); + const objects = documents.map((document) => document.toJSON()).filter((value) => value !== null).map((value, index) => record(value, `manifest[${index}]`)); + const deployment = objects.find((item) => item.kind === "Deployment" && item.metadata?.name === "sub2rank"); + const service = objects.find((item) => item.kind === "Service" && item.metadata?.name === "sub2rank"); + const configMap = objects.find((item) => item.kind === "ConfigMap" && item.metadata?.name === "sub2rank-config"); + if (deployment === undefined || service === undefined || configMap === undefined) throw new Error("rendered manifest must contain the Sub2Rank Deployment, Service and ConfigMap"); + if (deployment.spec?.template?.spec?.containers?.[0]?.image !== expected.digestRef) throw new Error("rendered Deployment image is not the expected digest reference"); + if (deployment.spec?.template?.metadata?.annotations?.["unidesk.ai/source-commit"] !== expected.sourceCommit) throw new Error("rendered Deployment source commit annotation is incorrect"); + if (deployment.spec?.template?.metadata?.annotations?.["unidesk.ai/sub2rank-config-sha256"] !== expected.configSha256) throw new Error("rendered Deployment config SHA annotation is incorrect"); + if (configMap.binaryData?.["sub2rank.yaml"] !== expected.configBase64) throw new Error("rendered ConfigMap does not contain the exact source config"); +} + +function main() { + const sourceRoot = resolve(required(option("--source-root"), "--source-root")); + const sourceCommit = required(option("--source-commit"), "--source-commit"); + const configPath = safePath(required(option("--config-path"), "--config-path"), "--config-path"); + const metadataPath = resolve(required(option("--metadata"), "--metadata")); + const templateB64 = required(option("--manifest-template-b64"), "--manifest-template-b64"); + const imageRepository = required(option("--image-repository"), "--image-repository"); + const readUrl = required(option("--gitops-read-url"), "--gitops-read-url"); + const writeUrl = required(option("--gitops-write-url"), "--gitops-write-url"); + const branch = required(option("--gitops-branch"), "--gitops-branch"); + const manifestPath = safePath(required(option("--manifest-path"), "--manifest-path"), "--manifest-path"); + const releaseStatePath = safePath(required(option("--release-state-path"), "--release-state-path"), "--release-state-path"); + const maxPushAttempts = Number(required(option("--max-push-attempts"), "--max-push-attempts")); + const authorName = required(option("--author-name"), "--author-name"); + const authorEmail = required(option("--author-email"), "--author-email"); + const worktree = resolve(required(option("--worktree"), "--worktree")); + if (!/^[0-9a-f]{40}$/u.test(sourceCommit)) throw new Error("--source-commit must be a full lowercase Git SHA"); + if (!Number.isInteger(maxPushAttempts) || maxPushAttempts < 1 || maxPushAttempts > 5) throw new Error("--max-push-attempts must be an integer from 1 to 5"); + if (run("git", ["rev-parse", "HEAD"], sourceRoot).stdout.trim() !== sourceCommit) throw new Error("source checkout does not match --source-commit"); + + const configText = readFileSync(resolve(sourceRoot, configPath), "utf8"); + const appConfig = record(Bun.YAML.parse(configText), configPath); + if (appConfig.kind !== "Sub2Rank") throw new Error(`${configPath}.kind must be Sub2Rank`); + const lottery = record(appConfig.lottery, `${configPath}.lottery`); + const automaticCredit = record(lottery.automaticCredit, `${configPath}.lottery.automaticCredit`); + if (typeof automaticCredit.enabled !== "boolean" || !new Set(["dry-run", "live"]).has(automaticCredit.mode)) throw new Error(`${configPath}.lottery.automaticCredit must declare enabled and mode`); + const configBase64 = Buffer.from(configText, "utf8").toString("base64"); + const configSha256 = sha256(configText); + + const metadata = record(JSON.parse(readFileSync(metadataPath, "utf8")), metadataPath); + const digest = required(metadata["containerimage.digest"], `${metadataPath}.containerimage.digest`); + if (!/^sha256:[0-9a-f]{64}$/u.test(digest)) throw new Error("BuildKit metadata digest is invalid"); + const digestRef = `${imageRepository}@${digest}`; + const template = Buffer.from(templateB64, "base64").toString("utf8"); + const manifest = replaceAllRequired(template, [ + [placeholders.image, digestRef], + [placeholders.configBase64, configBase64], + [placeholders.configSha256, configSha256], + [placeholders.sourceCommit, sourceCommit], + ]); + validateManifest(manifest, { digestRef, sourceCommit, configSha256, configBase64 }); + + rmSync(worktree, { recursive: true, force: true }); + run("git", ["clone", "--no-checkout", readUrl, worktree], sourceRoot); + const fetched = run("git", ["fetch", "origin", branch], worktree, true).status === 0; + if (fetched) run("git", ["checkout", "-B", branch, `origin/${branch}`], worktree); + else { + run("git", ["checkout", "--orphan", branch], worktree); + run("git", ["rm", "-rf", "."], worktree, true); + } + + const targetPath = resolve(worktree, manifestPath); + const statePath = resolve(worktree, releaseStatePath); + if (!targetPath.startsWith(`${worktree}/`) || !statePath.startsWith(`${worktree}/`)) throw new Error("GitOps output path escaped the worktree"); + mkdirSync(dirname(targetPath), { recursive: true }); + writeFileSync(targetPath, manifest, "utf8"); + mkdirSync(dirname(statePath), { recursive: true }); + writeFileSync(statePath, `${JSON.stringify({ + version: 1, + kind: "Sub2RankReleaseState", + service: "sub2rank", + sourceCommit, + configSha256, + digest, + digestRef, + manifestPath, + automaticCredit: { enabled: automaticCredit.enabled, mode: automaticCredit.mode }, + }, null, 2)}\n`, "utf8"); + + run("git", ["config", "user.name", authorName], worktree); + run("git", ["config", "user.email", authorEmail], worktree); + run("git", ["add", "--", manifestPath, releaseStatePath], worktree); + const changed = run("git", ["diff", "--cached", "--quiet"], worktree, true).status !== 0; + if (changed) run("git", ["commit", "-m", `sub2rank: deploy ${sourceCommit.slice(0, 12)}`], worktree); + run("git", ["remote", "set-url", "origin", writeUrl], worktree); + + let pushAttempts = 0; + if (changed) { + let pushed = false; + for (let attempt = 1; attempt <= maxPushAttempts; attempt += 1) { + pushAttempts = attempt; + if (run("git", ["push", "origin", `HEAD:refs/heads/${branch}`], worktree, true).status === 0) { + pushed = true; + break; + } + run("git", ["fetch", "origin", branch], worktree); + const rebase = run("git", ["rebase", `origin/${branch}`], worktree, true); + if (rebase.status !== 0) { + run("git", ["rebase", "--abort"], worktree, true); + throw new Error(`GitOps rebase failed after push attempt ${attempt}`); + } + } + if (!pushed) throw new Error(`GitOps push failed after ${maxPushAttempts} attempts`); + } + const gitopsCommit = run("git", ["rev-parse", "HEAD"], worktree).stdout.trim(); + const remoteHead = run("git", ["ls-remote", writeUrl, `refs/heads/${branch}`], worktree).stdout.trim().split(/\s+/u)[0] ?? ""; + if (remoteHead !== gitopsCommit) throw new Error("GitOps remote branch did not converge to the published commit"); + process.stdout.write(`${JSON.stringify({ + ok: true, + phase: "gitops-publish", + status: "built", + imageStatus: "built", + sourceCommit, + runtimeSourceCommit: sourceCommit, + configSha256, + automaticCredit: { enabled: automaticCredit.enabled, mode: automaticCredit.mode }, + digest, + digestRef, + gitopsCommit, + changed, + pushAttempts, + valuesPrinted: false, + })}\n`); +} + +if (!process.execArgv.includes("--check")) main();