import { argoApplicationName, buildkitRunnerImage, ciToolsRunnerImage, defaultDevBaseImage, defaultRegistryPrefix, defaultServiceIds, defaultSourceRepo, gitopsPathFor, gitopsPathForProfile, gitopsTargetForProfile, isRuntimeLane, namespaceNameForProfile, primitiveValidationTasks, proxyEnv, serviceIdsForLane, servicesParamForLane } from "./core.mjs"; import { ciTimingShellFunction, collectArtifactsScript, controlPlaneReconcileScript, gitopsPromoteScript, perServicePublishScript, planArtifactsScript, pollerScript, prepareSourceScript, primitiveValidationTask, primitiveValidationTaskNames } from "./tekton-scripts.mjs"; function ciLaneSettings(argsOrLane = "node", deploy = null) { const lane = typeof argsOrLane === "string" ? argsOrLane : argsOrLane.lane; if (isRuntimeLane(lane)) { const namespace = namespaceNameForProfile(lane, deploy); return { lane, profile: lane, gitopsTarget: lane, pipelineName: `${namespace}-ci-image-publish`, pipelineRunPrefix: `${namespace}-ci-poll`, serviceAccountName: `${namespace}-tekton-runner`, pollerName: `${namespace}-branch-poller`, controlPlaneReconcilerName: `${namespace}-control-plane-reconciler`, tektonDir: `tekton-${lane}`, catalogPath: typeof argsOrLane === "object" ? argsOrLane.catalogPath : `deploy/artifact-catalog.${lane}.json`, imageTagMode: typeof argsOrLane === "object" ? argsOrLane.imageTagMode : "full", runtimeNamespace: namespace }; } return { lane: "node", profile: "dev", gitopsTarget: "node", pipelineName: "hwlab-node-ci-image-publish", pipelineRunPrefix: "hwlab-node-ci-poll", serviceAccountName: "hwlab-tekton-runner", pollerName: "hwlab-node-branch-poller", controlPlaneReconcilerName: "hwlab-node-control-plane-reconciler", tektonDir: "tekton", catalogPath: "deploy/artifact-catalog.dev.json", imageTagMode: "short", runtimeNamespace: "hwlab-dev" }; } function tektonRbac(args = { lane: "node" }, deploy = null) { const settings = ciLaneSettings(args, deploy); if (isRuntimeLane(settings.lane)) { const runtimeObserverName = `${settings.runtimeNamespace}-runtime-observer`; const pipelineRunWriterName = `${settings.runtimeNamespace}-pipelinerun-writer`; const argoReconcilerName = `${settings.runtimeNamespace}-argocd-reconciler`; return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } }, { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: settings.serviceAccountName, namespace: "hwlab-ci" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: runtimeObserverName, namespace: settings.runtimeNamespace }, rules: [ { apiGroups: [""], resources: ["pods", "services", "configmaps"], verbs: ["get", "list", "watch"] }, { apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] }, { apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: runtimeObserverName, namespace: settings.runtimeNamespace }, subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: runtimeObserverName } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: pipelineRunWriterName, namespace: "hwlab-ci" }, rules: [ { apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "create"] }, { apiGroups: ["tekton.dev"], resources: ["pipelines", "tasks"], verbs: ["get", "patch", "create"] }, { apiGroups: ["batch"], resources: ["cronjobs"], verbs: ["get", "patch", "create"] }, { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }, { apiGroups: [""], resources: ["serviceaccounts"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: pipelineRunWriterName, namespace: "hwlab-ci" }, subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: pipelineRunWriterName } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: argoReconcilerName, namespace: "argocd" }, rules: [ { apiGroups: ["argoproj.io"], resources: ["applications", "appprojects"], verbs: ["get", "patch", "create"] }, { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: argoReconcilerName, namespace: "argocd" }, subjects: [{ kind: "ServiceAccount", name: settings.serviceAccountName, namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: argoReconcilerName } } ] }; } return { apiVersion: "v1", kind: "List", items: [ { apiVersion: "v1", kind: "Namespace", metadata: { name: "hwlab-ci" } }, { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: "hwlab-tekton-runner", namespace: "hwlab-ci" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-node-control-plane-reconciler", namespace: "hwlab-dev" }, rules: [ { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-node-control-plane-reconciler", namespace: "hwlab-dev" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-node-control-plane-reconciler" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-tekton-runtime-observer", namespace: "hwlab-dev" }, rules: [ { apiGroups: [""], resources: ["pods", "services", "configmaps"], verbs: ["get", "list", "watch"] }, { apiGroups: ["apps"], resources: ["deployments", "statefulsets"], verbs: ["get", "list", "watch"] }, { apiGroups: ["batch"], resources: ["jobs"], verbs: ["get", "list", "watch"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-tekton-runtime-observer", namespace: "hwlab-dev" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-tekton-runtime-observer" } }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "Role", metadata: { name: "hwlab-tekton-pipelinerun-writer", namespace: "hwlab-ci" }, rules: [ { apiGroups: ["tekton.dev"], resources: ["pipelineruns"], verbs: ["get", "list", "create"] }, { apiGroups: ["tekton.dev"], resources: ["pipelines", "tasks"], verbs: ["get", "patch", "create"] }, { apiGroups: ["batch"], resources: ["cronjobs"], verbs: ["get", "patch", "create"] }, { apiGroups: ["rbac.authorization.k8s.io"], resources: ["roles", "rolebindings"], verbs: ["get", "patch", "create"] }, { apiGroups: [""], resources: ["serviceaccounts"], verbs: ["get", "patch", "create"] } ] }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: "hwlab-tekton-pipelinerun-writer", namespace: "hwlab-ci" }, subjects: [{ kind: "ServiceAccount", name: "hwlab-tekton-runner", namespace: "hwlab-ci" }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "hwlab-tekton-pipelinerun-writer" } } ] }; } function buildTaskName(serviceId) { return `build-${serviceId}`; } function affectedResultName(serviceId) { return `affected-${serviceId}`; } function buildResultName(serviceId) { return `build-${serviceId}`; } const serviceResultFields = Object.freeze([ "status", "service-id", "image", "image-tag", "digest", "repository-digest", "source-commit-id", "component-input-hash", "environment-input-hash", "code-input-hash", "runtime-mode", "boot-repo", "boot-commit", "boot-sh", "build-created-at", "build-backend", "reused-from", "go-base-image", "go-base-image-status", "go-base-digest", "go-base-build-duration-ms", "go-base-buildkit-cache-ref" ]); function serviceResultParamName(serviceId, field) { return `${serviceId}-${field}`; } function serviceResultEnvName(serviceId, field) { return `HWLAB_SERVICE_RESULT_${serviceId.toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}_${field.toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}`; } function serviceResultParams(serviceIds = defaultServiceIds) { return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultParamName(serviceId, field), default: "" }))); } function serviceResultParamValues(serviceIds = defaultServiceIds) { return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultParamName(serviceId, field), value: `$(tasks.${buildTaskName(serviceId)}.results.${field})` }))); } function serviceResultEnv(serviceIds = defaultServiceIds) { return serviceIds.flatMap((serviceId) => serviceResultFields.map((field) => ({ name: serviceResultEnvName(serviceId, field), value: `$(params.${serviceResultParamName(serviceId, field)})` }))); } function serviceWorkVolume() { return { name: "service-work", emptyDir: {} }; } function buildkitBinVolume() { return { name: "buildkit-bin", emptyDir: {} }; } function buildkitRunVolume() { return { name: "buildkit-run", emptyDir: {} }; } function prepareBuildkitClientStep() { return { name: "prepare-buildkit-client", image: buildkitRunnerImage, securityContext: { runAsUser: 0, runAsGroup: 0 }, volumeMounts: [{ name: "buildkit-bin", mountPath: "/workspace/buildkit-bin" }], script: `#!/bin/sh set -eu mkdir -p /workspace/buildkit-bin cp /usr/bin/buildctl /workspace/buildkit-bin/ chmod +x /workspace/buildkit-bin/* ` }; } function buildkitSidecar() { return { name: "buildkitd", image: buildkitRunnerImage, args: ["--addr", "unix:///workspace/buildkit-run/buildkitd.sock", "--oci-worker-no-process-sandbox"], env: proxyEnv(), volumeMounts: [{ name: "buildkit-run", mountPath: "/workspace/buildkit-run" }], securityContext: { runAsUser: 1000, runAsGroup: 1000, allowPrivilegeEscalation: true, appArmorProfile: { type: "Unconfined" }, seccompProfile: { type: "Unconfined" } } }; } function planArtifactsTask({ serviceIds = defaultServiceIds } = {}) { return { name: "plan-artifacts", runAfter: primitiveValidationTaskNames(), workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [ { name: "git-url" }, { name: "gitops-read-url" }, { name: "gitops-branch" }, { name: "lane" }, { name: "revision" }, { name: "catalog-path" }, { name: "registry-prefix" }, { name: "services" }, { name: "release-base-commit" }, { name: "release-target" }, { name: "release-consumer" }, { name: "reviewed-plan" } ], results: serviceIds.flatMap((serviceId) => [ { name: affectedResultName(serviceId), description: `${serviceId} rollout affected according to ci-plan` }, { name: buildResultName(serviceId), description: `${serviceId} image build required according to ci-plan` } ]).concat([{ name: "build-required", description: "true when at least one planned service image must be built" }]), workspaces: [{ name: "source" }], steps: [{ name: "plan", image: ciToolsRunnerImage, env: [ { name: "HWLAB_SELECTED_SERVICES", value: "$(params.services)" }, { name: "HWLAB_DEV_REGISTRY_K8S_NATIVE", value: "1" }, { name: "HWLAB_TEKTON_PIPELINERUN", value: "$(context.pipelineRun.name)" }, ...proxyEnv() ], script: planArtifactsScript() }] }, params: [ { name: "git-url", value: "$(params.git-url)" }, { name: "gitops-read-url", value: "$(params.gitops-read-url)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "lane", value: "$(params.lane)" }, { name: "revision", value: "$(params.revision)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "services", value: "$(params.services)" }, { name: "release-base-commit", value: "$(params.release-base-commit)" }, { name: "release-target", value: "$(params.release-target)" }, { name: "release-consumer", value: "$(params.release-consumer)" }, { name: "reviewed-plan", value: "$(params.reviewed-plan)" } ] }; } function perServiceBuildTask(serviceId, { runAfter = ["plan-artifacts"] } = {}) { return { name: buildTaskName(serviceId), runAfter, workspaces: [{ name: "source", workspace: "source" }], when: [{ input: `$(tasks.plan-artifacts.results.${buildResultName(serviceId)})`, operator: "in", values: ["true"] }], taskSpec: { params: [ { name: "revision" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "service-id" }, { name: "base-image" }, { name: "build-cache-mode" } ], results: serviceResultFields.map((field) => ({ name: field, description: `${serviceId} ${field}` })), workspaces: [{ name: "source" }], volumes: [serviceWorkVolume(), buildkitBinVolume(), buildkitRunVolume()], sidecars: [buildkitSidecar()], steps: [ prepareBuildkitClientStep(), { name: "publish", image: ciToolsRunnerImage, securityContext: { runAsUser: 1000, runAsGroup: 1000 }, env: proxyEnv(), volumeMounts: [{ name: "service-work", mountPath: "/workspace/service-work" }, { name: "buildkit-bin", mountPath: "/workspace/buildkit-bin" }, { name: "buildkit-run", mountPath: "/workspace/buildkit-run" }], script: perServicePublishScript() } ] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "service-id", value: serviceId }, { name: "base-image", value: "$(params.base-image)" }, { name: "build-cache-mode", value: "$(params.build-cache-mode)" } ] }; } function perServiceBuildMatrixTask(serviceIds, { runAfter = ["plan-artifacts"] } = {}) { const task = perServiceBuildTask(serviceIds[0], { runAfter }); task.name = "build-services"; task.when = [{ input: "$(tasks.plan-artifacts.results.build-required)", operator: "in", values: ["true"] }]; task.params = task.params.filter((param) => param.name !== "service-id"); task.matrix = { params: [{ name: "service-id", value: serviceIds }] }; return task; } function mergeNamedEntries(...groups) { const entries = new Map(); for (const group of groups) { for (const entry of group || []) { if (!entries.has(entry.name)) entries.set(entry.name, entry); } } return [...entries.values()]; } function releasePlanTask({ serviceIds = defaultServiceIds } = {}) { const plan = planArtifactsTask({ serviceIds }); const prepareParams = [ { name: "git-url" }, { name: "git-read-url" }, { name: "gitops-read-url" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "services" }, { name: "revision" } ]; const prepareBindings = prepareParams.map(({ name }) => ({ name, value: `$(params.${name})` })); return { name: "plan-artifacts", workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: mergeNamedEntries(prepareParams, plan.taskSpec.params), results: plan.taskSpec.results, workspaces: [{ name: "source" }], steps: [ { name: "prepare-source", image: ciToolsRunnerImage, env: proxyEnv(), script: prepareSourceScript() }, plan.taskSpec.steps[0] ] }, params: mergeNamedEntries(prepareBindings, plan.params) }; } function collectArtifactsTask({ serviceIds = defaultServiceIds } = {}) { return { name: "collect-artifacts", runAfter: ["build-services"], workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [ { name: "revision" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "services" }, { name: "base-image" } ], workspaces: [{ name: "source" }], steps: [{ name: "collect", image: ciToolsRunnerImage, env: proxyEnv(), script: collectArtifactsScript() }] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" }, { name: "services", value: "$(params.services)" }, { name: "base-image", value: "$(params.base-image)" } ] }; } function gitopsPromoteTask() { return { name: "gitops-promote", workspaces: [ { name: "source", workspace: "source" }, { name: "gitea-auth", workspace: "gitea-auth" } ], taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, { name: "gitops-read-url" }, { name: "git-write-url" }, { name: "gitops-username" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "runtime-path" }, { name: "revision" }, { name: "registry-prefix" } ], results: [{ name: "runtime-ready-required", description: "true when GitOps promotion changed runtime desired state and runtime readiness must be observed" }], workspaces: [{ name: "source" }, { name: "gitea-auth" }], steps: [{ name: "promote", image: ciToolsRunnerImage, env: proxyEnv(), script: gitopsPromoteScript() }] }, params: [ { name: "git-url", value: "$(params.git-url)" }, { name: "git-read-url", value: "$(params.git-read-url)" }, { name: "gitops-read-url", value: "$(params.gitops-read-url)" }, { name: "git-write-url", value: "$(params.git-write-url)" }, { name: "gitops-username", value: "$(params.gitops-username)" }, { name: "source-branch", value: "$(params.source-branch)" }, { name: "gitops-branch", value: "$(params.gitops-branch)" }, { name: "lane", value: "$(params.lane)" }, { name: "catalog-path", value: "$(params.catalog-path)" }, { name: "image-tag-mode", value: "$(params.image-tag-mode)" }, { name: "runtime-path", value: "$(params.runtime-path)" }, { name: "revision", value: "$(params.revision)" }, { name: "registry-prefix", value: "$(params.registry-prefix)" } ] }; } function releaseArtifactsTask({ serviceIds = defaultServiceIds } = {}) { const collect = collectArtifactsTask({ serviceIds }); const promote = gitopsPromoteTask(); return { name: "release-artifacts", runAfter: ["build-services"], workspaces: promote.workspaces, taskSpec: { params: mergeNamedEntries(collect.taskSpec.params, promote.taskSpec.params), results: promote.taskSpec.results, workspaces: promote.taskSpec.workspaces, steps: [collect.taskSpec.steps[0], promote.taskSpec.steps[0]] }, params: mergeNamedEntries(collect.params, promote.params) }; } function runtimeReadyScript() { return `#!/bin/sh set -eu ${ciTimingShellFunction()} export HWLAB_TEKTON_PIPELINERUN="$(context.pipelineRun.name)" export HWLAB_TEKTON_TASKRUN="$(context.taskRun.name)" export HWLAB_TEKTON_TASK="runtime-ready" export HWLAB_SOURCE_REVISION="$(params.revision)" export HWLAB_RUNTIME_READY_TIMEOUT_MS="$(params.timeout-ms)" cd /workspace/source/repo node scripts/ci/runtime-ready.mjs `; } function runtimeReadyTask({ profile = "dev", deploy = null } = {}) { return { name: "runtime-ready", runAfter: ["gitops-promote"], workspaces: [{ name: "source", workspace: "source" }], taskSpec: { params: [{ name: "revision" }, { name: "runtime-namespace" }, { name: "gitops-target" }, { name: "timeout-ms" }], workspaces: [{ name: "source" }], steps: [{ name: "runtime-ready", image: ciToolsRunnerImage, env: [ ...proxyEnv(), { name: "HWLAB_RUNTIME_NAMESPACE", value: "$(params.runtime-namespace)" }, { name: "HWLAB_GITOPS_TARGET", value: "$(params.gitops-target)" } ], script: runtimeReadyScript() }] }, params: [ { name: "revision", value: "$(params.revision)" }, { name: "runtime-namespace", value: namespaceNameForProfile(profile, deploy) }, { name: "gitops-target", value: gitopsTargetForProfile(profile) }, { name: "timeout-ms", value: "$(params.runtime-ready-timeout-ms)" } ] }; } function imagePublishTaskSet(args = { lane: "node" }, deploy = null) { const serviceIds = serviceIdsForLane(args.lane, deploy); const buildTask = perServiceBuildMatrixTask(serviceIds, { runAfter: ["plan-artifacts"] }); return [ planArtifactsTask({ serviceIds }), buildTask, collectArtifactsTask({ serviceIds }) ]; } function taskResourceName(pipelineName, taskName) { return `${pipelineName}-${taskName}`.replace(/[^a-z0-9-]/gu, "-").slice(0, 63).replace(/-+$/u, ""); } function reusableTaskResource(pipelineName, task) { return { apiVersion: "tekton.dev/v1", kind: "Task", metadata: { name: taskResourceName(pipelineName, task.name), namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/tekton-pipeline": pipelineName } }, spec: task.taskSpec }; } function tektonTasks(args = { lane: "node" }, deploy = null) { const settings = ciLaneSettings(args, deploy); const prepareSource = { name: "prepare-source", taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, { name: "gitops-read-url" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "registry-prefix" }, { name: "services" }, { name: "revision" } ], workspaces: [{ name: "source" }], steps: [{ name: "prepare-source", image: ciToolsRunnerImage, env: proxyEnv(), script: prepareSourceScript() }] } }; const pipelineTasks = [ prepareSource, ...primitiveValidationTasks.map(primitiveValidationTask), ...imagePublishTaskSet(args, deploy), { name: "gitops-promote", taskSpec: { params: [ { name: "git-url" }, { name: "git-read-url" }, { name: "gitops-read-url" }, { name: "git-write-url" }, { name: "gitops-username" }, { name: "source-branch" }, { name: "gitops-branch" }, { name: "lane" }, { name: "catalog-path" }, { name: "image-tag-mode" }, { name: "runtime-path" }, { name: "revision" }, { name: "registry-prefix" } ], results: [{ name: "runtime-ready-required", description: "true when GitOps promotion changed runtime desired state and runtime readiness must be observed" }], workspaces: [{ name: "source" }, { name: "gitea-auth" }], steps: [{ name: "promote", image: ciToolsRunnerImage, env: proxyEnv(), script: gitopsPromoteScript() }] } }, ...(isRuntimeLane(settings.lane) ? [] : [{ name: "runtime-ready", taskSpec: runtimeReadyTask({ profile: settings.profile }).taskSpec }]) ]; return pipelineTasks.map((task) => reusableTaskResource(settings.pipelineName, task)); } function tektonPipeline(args = { lane: "node" }, deploy = null) { const settings = ciLaneSettings(args, deploy); const serviceIds = serviceIdsForLane(args.lane, deploy); const runtimePath = gitopsPathForProfile(args, settings.profile, deploy); const preFlushRuntimeReadyTasks = isRuntimeLane(settings.lane) ? [] : [{ ...runtimeReadyTask({ profile: settings.profile, deploy }), when: [{ input: "$(tasks.release-artifacts.results.runtime-ready-required)", operator: "in", values: ["true"] }] }]; return { apiVersion: "tekton.dev/v1", kind: "Pipeline", metadata: { name: settings.pipelineName, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { "hwlab.pikastech.local/source-config": "scripts/gitops-render.mjs#bounded-release-taskruns", "hwlab.pikastech.local/ci-contract": "plan-build-release-taskruns", "hwlab.pikastech.local/policy": "reviewed-plan-exact-scope" } }, spec: { params: [ { name: "git-url", type: "string", default: defaultSourceRepo }, { name: "git-read-url", type: "string", default: args.gitReadUrl }, { name: "gitops-read-url", type: "string", default: args.gitReadUrl }, { name: "git-write-url", type: "string", default: args.gitWriteUrl }, { name: "gitops-username", type: "string", default: "unidesk-admin" }, { name: "source-branch", type: "string", default: args.sourceBranch }, { name: "gitops-branch", type: "string", default: args.gitopsBranch }, { name: "lane", type: "string", default: settings.lane }, { name: "catalog-path", type: "string", default: args.catalogPath || settings.catalogPath }, { name: "image-tag-mode", type: "string", default: args.imageTagMode || settings.imageTagMode }, { name: "runtime-path", type: "string", default: runtimePath }, { name: "revision", type: "string", description: "Full git commit SHA; the only source truth for image tags." }, { name: "registry-prefix", type: "string", default: defaultRegistryPrefix }, { name: "services", type: "string", default: servicesParamForLane(args.lane, deploy) }, { name: "base-image", type: "string", default: defaultDevBaseImage }, { name: "build-cache-mode", type: "string", default: "registry" }, { name: "runtime-ready-timeout-ms", type: "string", default: "60000" }, { name: "release-base-commit", type: "string", description: "Reviewed release plan base commit." }, { name: "release-target", type: "string", description: "Reviewed release target." }, { name: "release-consumer", type: "string", description: "Reviewed release consumer." }, { name: "reviewed-plan", type: "string", description: "Reviewed release plan envelope supplied by the manual webhook." } ], workspaces: [ { name: "source" }, { name: "gitea-auth" } ], tasks: [ releasePlanTask({ serviceIds }), perServiceBuildMatrixTask(serviceIds, { runAfter: ["plan-artifacts"] }), releaseArtifactsTask({ serviceIds }), ...preFlushRuntimeReadyTasks ] } }; } function tektonPipelineRunTemplate({ source, args, deploy = null }) { const settings = ciLaneSettings(args, deploy); const runtimePath = gitopsPathForProfile(args, settings.profile, deploy); return { apiVersion: "tekton.dev/v1", kind: "PipelineRun", metadata: { generateName: `${settings.pipelineRunPrefix}-manual-`, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget, "hwlab.pikastech.local/source-commit": source.full } }, spec: { pipelineRef: { name: settings.pipelineName }, taskRunTemplate: { serviceAccountName: settings.serviceAccountName, podTemplate: { hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", securityContext: { fsGroup: 1000 } } }, params: [ { name: "git-url", value: args.sourceRepo }, { name: "git-read-url", value: args.gitReadUrl }, { name: "gitops-read-url", value: args.gitReadUrl }, { name: "git-write-url", value: args.gitWriteUrl }, { name: "gitops-username", value: "unidesk-admin" }, { name: "source-branch", value: args.sourceBranch }, { name: "gitops-branch", value: args.gitopsBranch }, { name: "lane", value: settings.lane }, { name: "catalog-path", value: args.catalogPath || settings.catalogPath }, { name: "image-tag-mode", value: args.imageTagMode || settings.imageTagMode }, { name: "runtime-path", value: runtimePath }, { name: "revision", value: source.full }, { name: "registry-prefix", value: args.registryPrefix }, { name: "base-image", value: defaultDevBaseImage }, { name: "build-cache-mode", value: "registry" } ], workspaces: [ { name: "source", volumeClaimTemplate: { spec: { accessModes: ["ReadWriteOnce"], resources: { requests: { storage: "8Gi" } } } } }, { name: "gitea-auth", secret: { secretName: "pac-gitea-hwlab", items: [{ key: "token", path: "token" }] } } ] } }; } function tektonPollerCronJob(args, deploy) { const settings = ciLaneSettings(args, deploy); if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a branch poller CronJob"); return { apiVersion: "batch/v1", kind: "CronJob", metadata: { name: settings.pollerName, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { "hwlab.pikastech.local/source-branch": args.sourceBranch, "hwlab.pikastech.local/gitops-branch": args.gitopsBranch } }, spec: { schedule: settings.lane === "v02" ? "*/10 * * * *" : "* * * * *", concurrencyPolicy: "Forbid", successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 3, jobTemplate: { spec: { backoffLimit: 1, template: { metadata: { labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget } }, spec: { serviceAccountName: settings.serviceAccountName, restartPolicy: "Never", hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "poll", image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", env: [ ...proxyEnv(), { name: "POD_NAMESPACE", valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } }, { name: "PIPELINE_NAME", value: settings.pipelineName }, { name: "PIPELINERUN_PREFIX", value: settings.pipelineRunPrefix }, { name: "SERVICE_ACCOUNT_NAME", value: settings.serviceAccountName }, { name: "GITOPS_TARGET", value: settings.gitopsTarget }, { name: "LANE", value: settings.lane }, { name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath }, { name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode }, { name: "RUNTIME_PATH", value: gitopsPathForProfile(args, settings.profile, deploy) }, { name: "GIT_URL", value: args.sourceRepo }, { name: "GIT_READ_URL", value: args.gitReadUrl }, { name: "SOURCE_BRANCH", value: args.sourceBranch }, { name: "GITOPS_BRANCH", value: args.gitopsBranch }, { name: "REGISTRY_PREFIX", value: args.registryPrefix }, { name: "SERVICES", value: servicesParamForLane(args.lane, deploy) }, { name: "BASE_IMAGE", value: defaultDevBaseImage } ], volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }], command: ["/bin/sh", "-c"], args: [pollerScript()] }], volumes: [{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }] } } } } } }; } function tektonControlPlaneReconcilerCronJob(args) { const settings = ciLaneSettings(args); if (settings.lane === "v02") throw new Error("v02 CI/CD is manually triggered by UniDesk CLI and must not render a control-plane reconciler CronJob"); return { apiVersion: "batch/v1", kind: "CronJob", metadata: { name: settings.controlPlaneReconcilerName, namespace: "hwlab-ci", labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget }, annotations: { "hwlab.pikastech.local/source-branch": args.sourceBranch, "hwlab.pikastech.local/gitops-branch": args.gitopsBranch, "hwlab.pikastech.local/purpose": "render-and-apply-node-ci-control-plane" } }, spec: { schedule: "*/10 * * * *", concurrencyPolicy: "Forbid", successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 3, jobTemplate: { spec: { backoffLimit: 1, template: { metadata: { labels: { "app.kubernetes.io/part-of": "hwlab", "hwlab.pikastech.local/gitops-target": settings.gitopsTarget } }, spec: { serviceAccountName: settings.serviceAccountName, restartPolicy: "Never", hostNetwork: true, dnsPolicy: "ClusterFirstWithHostNet", containers: [{ name: "reconcile", image: ciToolsRunnerImage, imagePullPolicy: "IfNotPresent", env: [ ...proxyEnv(), { name: "POD_NAMESPACE", valueFrom: { fieldRef: { fieldPath: "metadata.namespace" } } }, { name: "LANE", value: settings.lane }, { name: "CATALOG_PATH", value: args.catalogPath || settings.catalogPath }, { name: "IMAGE_TAG_MODE", value: args.imageTagMode || settings.imageTagMode }, { name: "TEKTON_DIR", value: settings.tektonDir }, { name: "ARGO_FILES", value: settings.lane === "v02" ? `${gitopsPathFor(args, "argocd/project.yaml")},${gitopsPathFor(args, "argocd/application-v02.yaml")}` : "" }, { name: "FIELD_MANAGER", value: settings.controlPlaneReconcilerName }, { name: "RECONCILE_EVENT", value: `${settings.gitopsTarget}-control-plane-reconciled` }, { name: "GIT_URL", value: args.sourceRepo }, { name: "GIT_READ_URL", value: args.gitReadUrl }, { name: "SOURCE_BRANCH", value: args.sourceBranch }, { name: "GITOPS_BRANCH", value: args.gitopsBranch }, { name: "REGISTRY_PREFIX", value: args.registryPrefix } ], volumeMounts: [{ name: "git-ssh", mountPath: "/workspace/git-ssh", readOnly: true }], command: ["/bin/sh", "-c"], args: [controlPlaneReconcileScript()] }], volumes: [{ name: "git-ssh", secret: { secretName: "hwlab-git-ssh" } }] } } } } } }; } export { affectedResultName, buildResultName, buildTaskName, buildkitBinVolume, buildkitRunVolume, buildkitSidecar, ciLaneSettings, collectArtifactsTask, imagePublishTaskSet, perServiceBuildTask, planArtifactsTask, prepareBuildkitClientStep, runtimeReadyScript, runtimeReadyTask, serviceResultEnv, serviceResultEnvName, serviceResultParamName, serviceResultParams, serviceResultParamValues, serviceWorkVolume, tektonControlPlaneReconcilerCronJob, tektonPipeline, tektonTasks, tektonPipelineRunTemplate, tektonPollerCronJob, tektonRbac };