From 72700bdb845b1d7d7b5025385aa4672b6e165e78 Mon Sep 17 00:00:00 2001 From: eding Date: Tue, 15 Sep 2026 20:12:55 +0200 Subject: [PATCH] feat: align catalog runtime builds --- catalog/kustomization.yaml | 1 + catalog/maidn-node-runtime-image.yaml | 136 +++++++++++++++++ catalog/maidn-node-static-image.yaml | 209 +++++++++----------------- catalog/test-node-runtime-image.sh | 51 +++++++ catalog/test-node-static-image.sh | 59 ++++---- 5 files changed, 287 insertions(+), 169 deletions(-) create mode 100644 catalog/maidn-node-runtime-image.yaml create mode 100644 catalog/test-node-runtime-image.sh diff --git a/catalog/kustomization.yaml b/catalog/kustomization.yaml index 3698186..ee7c1f8 100644 --- a/catalog/kustomization.yaml +++ b/catalog/kustomization.yaml @@ -2,4 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - maidn-node-static-image.yaml + - maidn-node-runtime-image.yaml - maidn-preview-orphan-reconciler.yaml diff --git a/catalog/maidn-node-runtime-image.yaml b/catalog/maidn-node-runtime-image.yaml new file mode 100644 index 0000000..5a65c9b --- /dev/null +++ b/catalog/maidn-node-runtime-image.yaml @@ -0,0 +1,136 @@ +apiVersion: tekton.dev/v1 +kind: Task +metadata: + name: maidn-node-runtime-image + namespace: tekton-pipelines +spec: + params: + - name: url + type: string + - name: revision + type: string + - name: image + type: string + volumes: + - name: work + emptyDir: {} + - name: git-credentials + secret: + secretName: forgejo-git-credentials + - name: registry-credentials + secret: + secretName: forgejo-registry-credentials + items: + - key: .dockerconfigjson + path: config.json + stepTemplate: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + seccompProfile: + type: RuntimeDefault + steps: + - name: clone + image: alpine/git:2.47.2 + env: + - name: REPOSITORY_URL + value: $(params.url) + - name: REVISION + value: $(params.revision) + - name: IMAGE + value: $(params.image) + volumeMounts: + - name: work + mountPath: /work + - name: git-credentials + mountPath: /credentials + readOnly: true + script: | + #!/bin/sh + set -eu + case "$REPOSITORY_URL" in https://git.pingu.pw/*) ;; *) exit 1 ;; esac + repository_path=${REPOSITORY_URL#https://git.pingu.pw/} + case "$repository_path" in [A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*.git) ;; *) exit 1 ;; esac + case "$repository_path" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/|*/*/*) exit 1 ;; esac + case "$REVISION" in [A-Za-z0-9]*) ;; *) exit 1 ;; esac + case "$REVISION" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac + case "$IMAGE" in git.pingu.pw/*) ;; *) exit 1 ;; esac + image_path=${IMAGE#git.pingu.pw/} + case "$image_path" in [A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*) ;; *) exit 1 ;; esac + case "$image_path" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/|*/*/*) exit 1 ;; esac + cat >/work/askpass <<'EOF' + #!/bin/sh + case "$1" in *Username*) cat /credentials/username ;; *) cat /credentials/password ;; esac + EOF + chmod 0700 /work/askpass + GIT_ASKPASS=/work/askpass GIT_TERMINAL_PROMPT=0 git clone "$REPOSITORY_URL" /work/source + git -C /work/source checkout "$REVISION" + rm -f /work/askpass + - name: build-layer + image: node:22-alpine + resources: + requests: + memory: 256Mi + limits: + memory: 512Mi + volumeMounts: + - name: work + mountPath: /work + script: | + #!/bin/sh + set -eu + cd /work/source + npm install --ignore-scripts --no-audit --no-fund --package-lock=false + npm run build + npm prune --omit=dev --ignore-scripts --no-audit --no-fund + test -f package.json + test -d node_modules + test -d dist + mkdir -p /work/layer/app + cp package.json /work/layer/app/ + cp -R node_modules dist /work/layer/app/ + tar -C /work/layer -cf /work/layer.tar app + rm -rf /work/source /work/layer + - name: append + image: gcr.io/go-containerregistry/crane:v0.21.7 + env: + - name: HOME + value: /tekton/home + - name: DOCKER_CONFIG + value: /tekton/home/.docker + args: + - append + - --base=node:22-alpine + - --new_layer=/work/layer.tar + - --new_tag=$(params.image):$(params.revision) + volumeMounts: + - name: work + mountPath: /work + - name: registry-credentials + mountPath: /tekton/home/.docker + readOnly: true + - name: mutate + image: gcr.io/go-containerregistry/crane:v0.21.7 + env: + - name: HOME + value: /tekton/home + - name: DOCKER_CONFIG + value: /tekton/home/.docker + args: + - mutate + - $(params.image):$(params.revision) + - --entrypoint=node + - --cmd=dist/index.js + - --workdir=/app + - --user=node + - --env=PORT=8080 + - --exposed-ports=8080/tcp + - --tag=$(params.image):$(params.revision) + volumeMounts: + - name: registry-credentials + mountPath: /tekton/home/.docker + readOnly: true diff --git a/catalog/maidn-node-static-image.yaml b/catalog/maidn-node-static-image.yaml index 5745bc2..dc7988e 100644 --- a/catalog/maidn-node-static-image.yaml +++ b/catalog/maidn-node-static-image.yaml @@ -1,30 +1,46 @@ apiVersion: tekton.dev/v1 kind: Task metadata: - name: maidn-git-clone + name: maidn-node-static-image namespace: tekton-pipelines spec: + params: + - name: url + type: string + - name: revision + type: string + - name: image + type: string + - name: output-directory + type: string + - name: build-configuration + type: string + default: ci + - name: base-image + type: string + default: nginx:1.27-alpine + volumes: + - name: work + emptyDir: {} + - name: git-credentials + secret: + secretName: forgejo-git-credentials + - name: registry-credentials + secret: + secretName: forgejo-registry-credentials + items: + - key: .dockerconfigjson + path: config.json stepTemplate: - env: - - name: HOME - value: /tekton/home securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 allowPrivilegeEscalation: false capabilities: - drop: - - ALL + drop: [ALL] seccompProfile: type: RuntimeDefault - params: - - name: url - type: string - - name: revision - type: string - workspaces: - - name: source steps: - name: clone image: alpine/git:2.47.2 @@ -33,160 +49,71 @@ spec: value: $(params.url) - name: REVISION value: $(params.revision) - - name: SOURCE_PATH - value: $(workspaces.source.path) + volumeMounts: + - name: work + mountPath: /work + - name: git-credentials + mountPath: /credentials + readOnly: true script: | #!/bin/sh set -eu - case "$REPOSITORY_URL" in https://git.pingu.pw/*) ;; *) exit 1 ;; esac - repository_path=${REPOSITORY_URL#https://git.pingu.pw/} - case "$repository_path" in *.git) ;; *) exit 1 ;; esac - case "$repository_path" in ''|*[!A-Za-z0-9._/-]*|/*|*//*|*..*) exit 1 ;; esac + case "$REPOSITORY_URL" in https://git.pingu.pw/*.git) ;; *) exit 1 ;; esac case "$REVISION" in [A-Za-z0-9]*) ;; *) exit 1 ;; esac case "$REVISION" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac - git clone "$REPOSITORY_URL" "$SOURCE_PATH" - git config --global --add safe.directory "$SOURCE_PATH" - git -C "$SOURCE_PATH" checkout "$REVISION" ---- -apiVersion: tekton.dev/v1 -kind: Task -metadata: - name: maidn-node-static-build - namespace: tekton-pipelines -spec: - stepTemplate: - env: - - name: HOME - value: /tekton/home - securityContext: - runAsNonRoot: true - runAsUser: 1000 - runAsGroup: 1000 - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - seccompProfile: - type: RuntimeDefault - params: - - name: output-directory - type: string - - name: build-configuration - type: string - default: ci - - name: target-directory - type: string - default: /www/target - workspaces: - - name: source - steps: + cat >/work/askpass <<'EOF' + #!/bin/sh + case "$1" in *Username*) cat /credentials/username ;; *) cat /credentials/password ;; esac + EOF + chmod 0700 /work/askpass + GIT_ASKPASS=/work/askpass GIT_TERMINAL_PROMPT=0 git clone "$REPOSITORY_URL" /work/source + git -C /work/source checkout "$REVISION" + rm -f /work/askpass - name: build-layer image: node:22-alpine - env: - - name: OUTPUT_DIRECTORY - value: $(params.output-directory) - - name: SOURCE_PATH - value: $(workspaces.source.path) - - name: TARGET_DIRECTORY - value: $(params.target-directory) - - name: BUILD_CONFIGURATION - value: $(params.build-configuration) - - name: NODE_OPTIONS - value: --max-old-space-size=384 resources: requests: memory: 256Mi limits: memory: 512Mi + env: + - name: NODE_OPTIONS + value: --max-old-space-size=384 + - name: OUTPUT_DIRECTORY + value: $(params.output-directory) + - name: BUILD_CONFIGURATION + value: $(params.build-configuration) + volumeMounts: + - name: work + mountPath: /work script: | #!/bin/sh set -eu case "$OUTPUT_DIRECTORY" in ''|/*|-*|*[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac - case "$TARGET_DIRECTORY" in /*) ;; *) exit 1 ;; esac - target_path=${TARGET_DIRECTORY#/} - case "$target_path" in ''|-*|*[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac case "$BUILD_CONFIGURATION" in ''|-*|*[!A-Za-z0-9._-]*) exit 1 ;; esac - build_dir=$(mktemp -d) - cp -R "$SOURCE_PATH"/. "$build_dir" - cd "$build_dir" - # ponytail: no lockfile; skip install scripts and use npm ci when package-lock.json is committed. + cd /work/source npm install --ignore-scripts --no-audit --no-fund --package-lock=false npm run build -- --configuration "$BUILD_CONFIGURATION" test -d "$OUTPUT_DIRECTORY" - layer_dir=$(mktemp -d) - trap 'rm -rf "$build_dir" "$layer_dir"' EXIT - mkdir -p "$layer_dir/$target_path" - cp -R "$OUTPUT_DIRECTORY"/. "$layer_dir/$target_path/" - tar -C "$layer_dir" -cf "$SOURCE_PATH/layer.tar" "$target_path" - for memory_peak_path in /sys/fs/cgroup/memory.peak /sys/fs/cgroup/memory/memory.max_usage_in_bytes; do - if [ -r "$memory_peak_path" ] && memory_peak=$(cat "$memory_peak_path"); then - case "$memory_peak" in ''|*[!0-9]*) ;; *) printf 'cgroup memory peak: %s\n' "$memory_peak"; break ;; esac - fi - done ---- -apiVersion: tekton.dev/v1 -kind: Task -metadata: - name: maidn-node-static-push - namespace: tekton-pipelines -spec: - stepTemplate: - env: - - name: HOME - value: /tekton/home - securityContext: - runAsNonRoot: true - runAsUser: 1000 - runAsGroup: 1000 - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - seccompProfile: - type: RuntimeDefault - params: - - name: image - type: string - - name: revision - type: string - - name: base-image - type: string - default: nginx:1.27-alpine - workspaces: - - name: source - volumes: - - name: registry-credentials - secret: - secretName: forgejo-registry-credentials - items: - - key: .dockerconfigjson - path: config.json - steps: - - name: validate-inputs - image: alpine:3.21.3 - env: - - name: IMAGE - value: $(params.image) - - name: REVISION - value: $(params.revision) - - name: BASE_IMAGE - value: $(params.base-image) - script: | - #!/bin/sh - set -eu - case "$IMAGE" in git.pingu.pw/*) ;; *) exit 1 ;; esac - image_path=${IMAGE#git.pingu.pw/} - case "$image_path" in ''|*[!A-Za-z0-9._/-]*|/*|*//*|*..*|*/) exit 1 ;; esac - case "$REVISION" in [A-Za-z0-9]*) ;; *) exit 1 ;; esac - case "$REVISION" in *[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac - case "$BASE_IMAGE" in ''|-*|*[!A-Za-z0-9._/@:-]*|/*|*//*|*..*) exit 1 ;; esac + mkdir -p /work/layer/www + cp -R "$OUTPUT_DIRECTORY"/. /work/layer/www/ + tar -C /work/layer -cf /work/layer.tar www + rm -rf /work/source /work/layer - name: push image: gcr.io/go-containerregistry/crane:v0.21.7 + env: + - name: HOME + value: /tekton/home + - name: DOCKER_CONFIG + value: /tekton/home/.docker args: - append - --base=$(params.base-image) - - --new_layer=$(workspaces.source.path)/layer.tar + - --new_layer=/work/layer.tar - --new_tag=$(params.image):$(params.revision) volumeMounts: + - name: work + mountPath: /work - name: registry-credentials mountPath: /tekton/home/.docker + readOnly: true diff --git a/catalog/test-node-runtime-image.sh b/catalog/test-node-runtime-image.sh new file mode 100644 index 0000000..3b97719 --- /dev/null +++ b/catalog/test-node-runtime-image.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu + +root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +task="$root/catalog/maidn-node-runtime-image.yaml" + +contains() { + grep -qF -- "$1" "$task" +} + +for value in \ + 'apiVersion: tekton.dev/v1' \ + 'kind: Task' \ + 'name: maidn-node-runtime-image' \ + 'emptyDir: {}' \ + 'forgejo-git-credentials' \ + 'forgejo-registry-credentials' \ + 'case "$REPOSITORY_URL" in https://git.pingu.pw/*)' \ + 'case "$REVISION" in [A-Za-z0-9]*)' \ + 'case "$IMAGE" in git.pingu.pw/*)' \ + 'GIT_ASKPASS=/work/askpass' \ + 'runAsNonRoot: true' \ + 'allowPrivilegeEscalation: false' \ + 'drop: [ALL]' \ + 'type: RuntimeDefault' \ + 'npm install --ignore-scripts --no-audit --no-fund --package-lock=false' \ + 'memory: 256Mi' \ + 'memory: 512Mi' \ + 'npm run build' \ + 'npm prune --omit=dev --ignore-scripts --no-audit --no-fund' \ + 'cp package.json /work/layer/app/' \ + 'cp -R node_modules dist /work/layer/app/' \ + 'tar -C /work/layer -cf /work/layer.tar app' \ + '--base=node:22-alpine' \ + '--new_tag=$(params.image):$(params.revision)' \ + '- mutate' \ + '--entrypoint=node' \ + '--cmd=dist/index.js' \ + '--workdir=/app' \ + '--user=node' \ + '--env=PORT=8080' \ + '--exposed-ports=8080/tcp' \ + '--tag=$(params.image):$(params.revision)'; do + contains "$value" +done + +for param in url revision image; do + contains "name: $param" +done + +! grep -qiE 'kaniko|privileged: true' "$task" diff --git a/catalog/test-node-static-image.sh b/catalog/test-node-static-image.sh index bc86caa..a67659b 100644 --- a/catalog/test-node-static-image.sh +++ b/catalog/test-node-static-image.sh @@ -2,36 +2,39 @@ set -eu root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -catalog="$root/catalog/maidn-node-static-image.yaml" -tmp=$(mktemp -d) -trap 'rm -rf "$tmp"' EXIT +task="$root/catalog/maidn-node-static-image.yaml" -task() { - awk -v name="$1" ' - /^---$/ { if (found) exit } - $0 == " name: " name { found=1 } - found { print } - ' "$catalog" +contains() { + grep -qF -- "$1" "$task" } -[ "$(grep -c '^apiVersion: tekton.dev/v1$' "$catalog")" -eq 3 ] -[ "$(grep -c '^kind: Task$' "$catalog")" -eq 3 ] -[ "$(grep -cF 'runAsNonRoot: true' "$catalog")" -eq 3 ] -[ "$(grep -cF 'forgejo-registry-credentials' "$catalog")" -eq 1 ] - -for name in maidn-git-clone maidn-node-static-build maidn-node-static-push; do - task "$name" > "$tmp/$name" - grep -qF " name: $name" "$tmp/$name" - grep -qF ' - name: source' "$tmp/$name" +for value in \ + 'apiVersion: tekton.dev/v1' \ + 'kind: Task' \ + 'name: maidn-node-static-image' \ + 'emptyDir: {}' \ + 'forgejo-git-credentials' \ + 'forgejo-registry-credentials' \ + 'case "$REPOSITORY_URL" in https://git.pingu.pw/*.git)' \ + 'case "$REVISION" in [A-Za-z0-9]*)' \ + 'case "$OUTPUT_DIRECTORY" in' \ + 'case "$BUILD_CONFIGURATION" in' \ + 'GIT_ASKPASS=/work/askpass' \ + 'runAsNonRoot: true' \ + 'allowPrivilegeEscalation: false' \ + 'drop: [ALL]' \ + 'type: RuntimeDefault' \ + 'npm install --ignore-scripts --no-audit --no-fund --package-lock=false' \ + 'memory: 256Mi' \ + 'memory: 512Mi' \ + 'tar -C /work/layer -cf /work/layer.tar www' \ + '--base=$(params.base-image)' \ + '--new_tag=$(params.image):$(params.revision)'; do + contains "$value" done -grep -qF 'git clone "$REPOSITORY_URL" "$SOURCE_PATH"' "$tmp/maidn-git-clone" -grep -qF 'value: --max-old-space-size=384' "$tmp/maidn-node-static-build" -grep -qF 'memory: 256Mi' "$tmp/maidn-node-static-build" -grep -qF 'memory: 512Mi' "$tmp/maidn-node-static-build" -grep -qF 'tar -C "$layer_dir" -cf "$SOURCE_PATH/layer.tar" "$target_path"' "$tmp/maidn-node-static-build" -grep -qF 'for memory_peak_path in /sys/fs/cgroup/memory.peak /sys/fs/cgroup/memory/memory.max_usage_in_bytes; do' "$tmp/maidn-node-static-build" -grep -qF "if [ -r \"\$memory_peak_path\" ] && memory_peak=\$(cat \"\$memory_peak_path\"); then" "$tmp/maidn-node-static-build" -grep -qF 'image: gcr.io/go-containerregistry/crane:v0.21.7' "$tmp/maidn-node-static-push" -grep -qF ' - append' "$tmp/maidn-node-static-push" -grep -qF 'mountPath: /tekton/home/.docker' "$tmp/maidn-node-static-push" +for param in url revision image output-directory build-configuration base-image; do + contains "name: $param" +done + +! grep -qiE 'kaniko|privileged: true' "$task" -- 2.43.7