226 lines
9.8 KiB
TypeScript
226 lines
9.8 KiB
TypeScript
import { stableJsonSha256 } from "./stable-json";
|
|
import {
|
|
readSub2RankConfig,
|
|
resolveSub2RankTarget,
|
|
sub2RankConfigLabel,
|
|
type Sub2RankConfig,
|
|
type Sub2RankTarget,
|
|
} from "./platform-infra-sub2rank-config";
|
|
import {
|
|
renderSub2RankManifest,
|
|
sub2RankConfigBase64Placeholder,
|
|
sub2RankConfigSha256Placeholder,
|
|
sub2RankImagePlaceholder,
|
|
sub2RankSourceCommitPlaceholder,
|
|
} from "./platform-infra-sub2rank";
|
|
import type { PacSourceArtifactBinding, PacSourceArtifactMode, PacSourceArtifactRenderer } from "./platform-infra-pipelines-as-code-source-artifact";
|
|
|
|
export interface Sub2RankPipelineProvenance {
|
|
readonly configRef: string;
|
|
readonly effectiveConfigSha256: string;
|
|
readonly renderer: PacSourceArtifactRenderer;
|
|
readonly mode: PacSourceArtifactMode;
|
|
}
|
|
|
|
export function sub2RankOwningSourceRemote(): string {
|
|
return readSub2RankConfig().application.remote;
|
|
}
|
|
|
|
export function renderSub2RankDesiredPipeline(binding: PacSourceArtifactBinding): {
|
|
pipeline: Record<string, unknown>;
|
|
provenance: Sub2RankPipelineProvenance;
|
|
} {
|
|
const config = readSub2RankConfig();
|
|
const target = resolveSub2RankTarget(config, binding.consumer.node);
|
|
const expectedConfigRef = `${sub2RankConfigLabel}#delivery`;
|
|
if (binding.consumer.sourceArtifact.configRef !== expectedConfigRef) throw new Error(`Sub2Rank sourceArtifact.configRef must equal ${expectedConfigRef}`);
|
|
const manifestTemplate = renderSub2RankManifest(config, target, {
|
|
imageRef: sub2RankImagePlaceholder,
|
|
appConfigBase64: sub2RankConfigBase64Placeholder,
|
|
appConfigSha256: sub2RankConfigSha256Placeholder,
|
|
sourceCommit: sub2RankSourceCommitPlaceholder,
|
|
});
|
|
return {
|
|
pipeline: renderPipeline(config, target, binding, manifestTemplate),
|
|
provenance: {
|
|
configRef: expectedConfigRef,
|
|
effectiveConfigSha256: stableJsonSha256(pipelineOwner(config, target)),
|
|
renderer: "sub2rank-platform-service",
|
|
mode: "embedded-pipeline-spec",
|
|
},
|
|
};
|
|
}
|
|
|
|
function renderPipeline(
|
|
config: Sub2RankConfig,
|
|
target: Sub2RankTarget,
|
|
binding: PacSourceArtifactBinding,
|
|
manifestTemplate: string,
|
|
): Record<string, unknown> {
|
|
const delivery = config.delivery;
|
|
const params = [
|
|
{ name: "git-read-url", type: "string", default: binding.repository.cloneUrl },
|
|
{ name: "source-branch", type: "string", default: config.application.branch },
|
|
{ name: "revision", type: "string" },
|
|
{ name: "source-stage-ref", type: "string" },
|
|
{ name: "config-path", type: "string", default: config.application.sourceConfigPath },
|
|
{ name: "dockerfile", type: "string", default: config.application.dockerfile },
|
|
{ name: "image-repository", type: "string", default: config.image.repository },
|
|
{ name: "tools-image", type: "string", default: delivery.pipeline.toolsImage },
|
|
{ name: "buildkit-image", type: "string", default: delivery.pipeline.buildkitImage },
|
|
{ name: "build-network", type: "string", default: delivery.build.networkMode },
|
|
{ name: "build-http-proxy", type: "string", default: delivery.build.proxy.http },
|
|
{ name: "build-https-proxy", type: "string", default: delivery.build.proxy.https },
|
|
{ name: "build-all-proxy", type: "string", default: delivery.build.proxy.all },
|
|
{ name: "build-no-proxy", type: "string", default: delivery.build.proxy.noProxy.join(",") },
|
|
{ name: "gitops-read-url", type: "string", default: delivery.gitops.readUrl },
|
|
{ name: "gitops-write-url", type: "string", default: delivery.gitops.writeUrl },
|
|
{ name: "gitops-branch", type: "string", default: delivery.gitops.branch },
|
|
{ name: "gitops-manifest-path", type: "string", default: delivery.gitops.manifestPath },
|
|
{ name: "gitops-release-state-path", type: "string", default: delivery.gitops.releaseStatePath },
|
|
{ name: "gitops-max-push-attempts", type: "string", default: String(delivery.gitops.maxPushAttempts) },
|
|
{ name: "gitops-author-name", type: "string", default: delivery.gitops.author.name },
|
|
{ name: "gitops-author-email", type: "string", default: delivery.gitops.author.email },
|
|
{ name: "manifest-template-b64", type: "string", default: Buffer.from(manifestTemplate, "utf8").toString("base64") },
|
|
];
|
|
const task = (name: string, steps: readonly Record<string, unknown>[], runAfter: readonly string[] = []): Record<string, unknown> => ({
|
|
name,
|
|
...(runAfter.length === 0 ? {} : { runAfter }),
|
|
workspaces: [{ name: "source", workspace: "source" }],
|
|
taskSpec: { params: params.map(({ name }) => ({ name })), workspaces: [{ name: "source" }], steps },
|
|
params: params.map(({ name: paramName }) => ({ name: paramName, value: `$(params.${paramName})` })),
|
|
});
|
|
return {
|
|
apiVersion: "tekton.dev/v1",
|
|
kind: "Pipeline",
|
|
metadata: {
|
|
name: binding.consumer.pipeline,
|
|
namespace: binding.consumer.namespace,
|
|
labels: {
|
|
"app.kubernetes.io/name": "sub2rank",
|
|
"app.kubernetes.io/part-of": "platform-infra",
|
|
"app.kubernetes.io/managed-by": "unidesk",
|
|
"unidesk.ai/node": target.id,
|
|
},
|
|
},
|
|
spec: {
|
|
params,
|
|
workspaces: [{ name: "source" }],
|
|
tasks: [
|
|
task("source-validate", [{
|
|
name: "checkout-and-cli-validate",
|
|
image: "$(params.tools-image)",
|
|
imagePullPolicy: "IfNotPresent",
|
|
env: proxyEnv(),
|
|
script: sourceValidateScript(),
|
|
}]),
|
|
task("image-build", [{
|
|
name: "build-and-push",
|
|
image: "$(params.buildkit-image)",
|
|
imagePullPolicy: "IfNotPresent",
|
|
env: [
|
|
...proxyEnv(),
|
|
{ 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: buildScript(),
|
|
}], ["source-validate"]),
|
|
task("gitops-publish", [{
|
|
name: "publish-digest-manifest",
|
|
image: "$(params.tools-image)",
|
|
imagePullPolicy: "IfNotPresent",
|
|
env: proxyEnv(),
|
|
script: gitopsPublishScript(),
|
|
}], ["image-build"]),
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
function sourceValidateScript(): string {
|
|
return [
|
|
"#!/bin/sh",
|
|
"set -eu",
|
|
"root=\"$(workspaces.source.path)\"",
|
|
"rm -rf \"$root/repo\"",
|
|
"git clone --filter=blob:none --no-checkout \"$(params.git-read-url)\" \"$root/repo\"",
|
|
"cd \"$root/repo\"",
|
|
"git fetch --depth=1 --filter=blob:none origin \"+$(params.source-stage-ref):refs/remotes/origin/sub2rank-source-snapshot\"",
|
|
"git checkout --detach \"$(params.revision)\"",
|
|
"test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"",
|
|
"bun install --frozen-lockfile",
|
|
"bun scripts/sub2rank-cli.ts --config \"$(params.config-path)\" config validate",
|
|
"chmod -R a+rX,g+rwX \"$root/repo\"",
|
|
"printf '{\"ok\":true,\"phase\":\"source-validate\",\"sourceCommit\":\"%s\",\"configPath\":\"%s\",\"valuesPrinted\":false}\\n' \"$(params.revision)\" \"$(params.config-path)\"",
|
|
].join("\n");
|
|
}
|
|
|
|
function buildScript(): string {
|
|
return [
|
|
"#!/bin/sh",
|
|
"set -eu",
|
|
"exec \"$(workspaces.source.path)/repo/scripts/ci/build-image.sh\"",
|
|
].join("\n");
|
|
}
|
|
|
|
function gitopsPublishScript(): string {
|
|
return [
|
|
"#!/bin/sh",
|
|
"set -eu",
|
|
"root=\"$(workspaces.source.path)\"",
|
|
"exec bun \"$root/repo/scripts/ci/publish-gitops.mjs\" \\",
|
|
" --source-root \"$root/repo\" \\",
|
|
" --source-commit \"$(params.revision)\" \\",
|
|
" --config-path \"$(params.config-path)\" \\",
|
|
" --metadata \"$root/build-metadata.json\" \\",
|
|
" --manifest-template-b64 \"$(params.manifest-template-b64)\" \\",
|
|
" --image-repository \"$(params.image-repository)\" \\",
|
|
" --gitops-read-url \"$(params.gitops-read-url)\" \\",
|
|
" --gitops-write-url \"$(params.gitops-write-url)\" \\",
|
|
" --gitops-branch \"$(params.gitops-branch)\" \\",
|
|
" --manifest-path \"$(params.gitops-manifest-path)\" \\",
|
|
" --release-state-path \"$(params.gitops-release-state-path)\" \\",
|
|
" --max-push-attempts \"$(params.gitops-max-push-attempts)\" \\",
|
|
" --author-name \"$(params.gitops-author-name)\" \\",
|
|
" --author-email \"$(params.gitops-author-email)\" \\",
|
|
" --worktree \"$root/gitops\"",
|
|
].join("\n");
|
|
}
|
|
|
|
function proxyEnv(): Record<string, unknown>[] {
|
|
return [
|
|
{ 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)" },
|
|
];
|
|
}
|
|
|
|
function pipelineOwner(config: Sub2RankConfig, target: Sub2RankTarget): Record<string, unknown> {
|
|
return {
|
|
application: {
|
|
repository: config.application.repository,
|
|
remote: config.application.remote,
|
|
branch: config.application.branch,
|
|
sourceConfigPath: config.application.sourceConfigPath,
|
|
dockerfile: config.application.dockerfile,
|
|
runtimeTarget: config.application.runtimeTarget,
|
|
},
|
|
image: config.image,
|
|
delivery: config.delivery,
|
|
target,
|
|
runtime: config.runtime,
|
|
};
|
|
}
|