From cea3e09b711e7ad03b14b44c3a6b76e31aa3a121 Mon Sep 17 00:00:00 2001 From: eding Date: Sat, 12 Sep 2026 22:30:32 +0200 Subject: [PATCH] feat: combine static clone build and push --- catalog/maidn-node-static-image.yaml | 153 ++++++++++++--------------- catalog/test-node-static-image.sh | 65 ++++++++---- 2 files changed, 113 insertions(+), 105 deletions(-) diff --git a/catalog/maidn-node-static-image.yaml b/catalog/maidn-node-static-image.yaml index 5745bc2..77e6c19 100644 --- a/catalog/maidn-node-static-image.yaml +++ b/catalog/maidn-node-static-image.yaml @@ -1,8 +1,14 @@ apiVersion: tekton.dev/v1 kind: Task metadata: - name: maidn-git-clone + name: maidn-node-static-image namespace: tekton-pipelines + annotations: + # MaidnCLI's generated Pipeline invokes this as task `build-and-push`. + maidn.io/maidncli-pipeline-task: build-and-push + # Required params: url, revision, image, output-directory, build-configuration. + # Optional params: base-image, target-directory. Required workspace: artifacts. + maidn.io/contract: params=url,revision,image,output-directory,build-configuration[,base-image,target-directory];workspace=artifacts spec: stepTemplate: env: @@ -23,8 +29,36 @@ spec: 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 + - name: target-directory + type: string + default: /www/target workspaces: - - name: source + # Persistent artifact workspace: this Task writes only layer.tar here. + - name: artifacts + volumes: + - name: local-source + emptyDir: {} + # Do not attach this annotated Secret to the TaskRun service account: Tekton + # would otherwise initialize it for every step instead of clone only. + - name: forgejo-git-credentials + secret: + secretName: forgejo-git-credentials + - name: registry-credentials + secret: + secretName: forgejo-registry-credentials + items: + - key: .dockerconfigjson + path: config.json steps: - name: clone image: alpine/git:2.47.2 @@ -34,7 +68,15 @@ spec: - name: REVISION value: $(params.revision) - name: SOURCE_PATH - value: $(workspaces.source.path) + value: /workspace/local-source + - name: GIT_CREDENTIALS_PATH + value: /var/run/secrets/forgejo-git-credentials + volumeMounts: + - name: local-source + mountPath: /workspace/local-source + - name: forgejo-git-credentials + mountPath: /var/run/secrets/forgejo-git-credentials + readOnly: true script: | #!/bin/sh set -eu @@ -44,55 +86,37 @@ spec: 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 - 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: + [ -r "$GIT_CREDENTIALS_PATH/username" ] && [ -r "$GIT_CREDENTIALS_PATH/password" ] || exit 1 + cat > /tmp/git-askpass <<'EOF' + #!/bin/sh + case "$1" in + *Username*) cat "$GIT_CREDENTIALS_PATH/username" ;; + *Password*) cat "$GIT_CREDENTIALS_PATH/password" ;; + *) exit 1 ;; + esac + EOF + chmod 700 /tmp/git-askpass + trap 'rm -f /tmp/git-askpass' EXIT + GIT_ASKPASS=/tmp/git-askpass GIT_TERMINAL_PROMPT=0 git clone "$REPOSITORY_URL" "$SOURCE_PATH" + git -c safe.directory="$SOURCE_PATH" -C "$SOURCE_PATH" checkout "$REVISION" - name: build-layer image: node:22-alpine env: - name: OUTPUT_DIRECTORY value: $(params.output-directory) - name: SOURCE_PATH - value: $(workspaces.source.path) + value: /workspace/local-source + - name: ARTIFACT_PATH + value: $(workspaces.artifacts.path) - name: TARGET_DIRECTORY value: $(params.target-directory) - name: BUILD_CONFIGURATION value: $(params.build-configuration) - name: NODE_OPTIONS value: --max-old-space-size=384 + volumeMounts: + - name: local-source + mountPath: /workspace/local-source resources: requests: memory: 256Mi @@ -107,62 +131,23 @@ spec: 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) + layer_dir=$(mktemp -d) + trap 'rm -rf "$build_dir" "$layer_dir"' EXIT 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. 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" + tar -C "$layer_dir" -cf "$ARTIFACT_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 + - name: validate-push-inputs image: alpine:3.21.3 env: - name: IMAGE @@ -185,7 +170,7 @@ spec: args: - append - --base=$(params.base-image) - - --new_layer=$(workspaces.source.path)/layer.tar + - --new_layer=$(workspaces.artifacts.path)/layer.tar - --new_tag=$(params.image):$(params.revision) volumeMounts: - name: registry-credentials diff --git a/catalog/test-node-static-image.sh b/catalog/test-node-static-image.sh index bc86caa..bcc125a 100644 --- a/catalog/test-node-static-image.sh +++ b/catalog/test-node-static-image.sh @@ -6,32 +6,55 @@ catalog="$root/catalog/maidn-node-static-image.yaml" tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT -task() { +step() { awk -v name="$1" ' - /^---$/ { if (found) exit } - $0 == " name: " name { found=1 } + $0 == " - name: " name { found=1 } + found && $0 ~ /^ - name: / && $0 != " - name: " name { exit } found { print } ' "$catalog" } -[ "$(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 ] +[ "$(grep -c '^apiVersion: tekton.dev/v1$' "$catalog")" -eq 1 ] +[ "$(grep -c '^kind: Task$' "$catalog")" -eq 1 ] +grep -qF 'runAsNonRoot: true' "$catalog" +grep -qF 'maidn.io/maidncli-pipeline-task: build-and-push' "$catalog" +grep -qF 'workspace=artifacts' "$catalog" -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 param in url revision image output-directory build-configuration base-image target-directory; do + grep -qF " - name: $param" "$catalog" done +grep -qF ' - name: artifacts' "$catalog" +! grep -qF 'maidn-git-clone' "$catalog" +! grep -qF 'maidn-node-static-build' "$catalog" +! grep -qF 'maidn-node-static-push' "$catalog" -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" +step clone > "$tmp/clone" +step build-layer > "$tmp/build-layer" +step validate-push-inputs > "$tmp/validate-push-inputs" +step push > "$tmp/push" + +grep -qF 'emptyDir: {}' "$catalog" +grep -qF 'secretName: forgejo-git-credentials' "$catalog" +grep -qF 'mountPath: /var/run/secrets/forgejo-git-credentials' "$tmp/clone" +grep -qF 'GIT_ASKPASS=/tmp/git-askpass GIT_TERMINAL_PROMPT=0 git clone "$REPOSITORY_URL" "$SOURCE_PATH"' "$tmp/clone" +! grep -qF 'registry-credentials' "$tmp/clone" +! grep -qF 'forgejo-git-credentials' "$tmp/build-layer" +! grep -qF 'GIT_CREDENTIALS_PATH' "$tmp/build-layer" +! grep -qF 'registry-credentials' "$tmp/build-layer" +grep -qF 'value: --max-old-space-size=384' "$tmp/build-layer" +grep -qF 'memory: 256Mi' "$tmp/build-layer" +grep -qF 'memory: 512Mi' "$tmp/build-layer" +grep -qF 'tar -C "$layer_dir" -cf "$ARTIFACT_PATH/layer.tar" "$target_path"' "$tmp/build-layer" +grep -qF 'for memory_peak_path in /sys/fs/cgroup/memory.peak /sys/fs/cgroup/memory/memory.max_usage_in_bytes; do' "$tmp/build-layer" +grep -qF "if [ -r \"\$memory_peak_path\" ] && memory_peak=\$(cat \"\$memory_peak_path\"); then" "$tmp/build-layer" +grep -qF 'image: alpine:3.21.3' "$tmp/validate-push-inputs" +grep -qF 'case "$IMAGE" in git.pingu.pw/*) ;; *) exit 1 ;; esac' "$tmp/validate-push-inputs" +! grep -qF 'forgejo-git-credentials' "$tmp/validate-push-inputs" +grep -qF 'image: gcr.io/go-containerregistry/crane:v0.21.7' "$tmp/push" +grep -qF 'mountPath: /tekton/home/.docker' "$tmp/push" +grep -qF ' - --new_layer=$(workspaces.artifacts.path)/layer.tar' "$tmp/push" +! grep -qF 'forgejo-git-credentials' "$tmp/push" + +kubectl kustomize "$root/catalog" > "$tmp/catalog.yaml" +[ "$(grep -c '^kind: Task$' "$tmp/catalog.yaml")" -eq 2 ] +grep -qF ' name: maidn-node-static-image' "$tmp/catalog.yaml"