Compare commits

...

1 commit

Author SHA1 Message Date
eding cea3e09b71 feat: combine static clone build and push 2026-09-12 22:30:32 +02:00
2 changed files with 113 additions and 105 deletions

View file

@ -1,8 +1,14 @@
apiVersion: tekton.dev/v1 apiVersion: tekton.dev/v1
kind: Task kind: Task
metadata: metadata:
name: maidn-git-clone name: maidn-node-static-image
namespace: tekton-pipelines 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: spec:
stepTemplate: stepTemplate:
env: env:
@ -23,8 +29,36 @@ spec:
type: string type: string
- name: revision - name: revision
type: string 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: 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: steps:
- name: clone - name: clone
image: alpine/git:2.47.2 image: alpine/git:2.47.2
@ -34,7 +68,15 @@ spec:
- name: REVISION - name: REVISION
value: $(params.revision) value: $(params.revision)
- name: SOURCE_PATH - 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: | script: |
#!/bin/sh #!/bin/sh
set -eu set -eu
@ -44,55 +86,37 @@ spec:
case "$repository_path" in ''|*[!A-Za-z0-9._/-]*|/*|*//*|*..*) 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 "$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" [ -r "$GIT_CREDENTIALS_PATH/username" ] && [ -r "$GIT_CREDENTIALS_PATH/password" ] || exit 1
git config --global --add safe.directory "$SOURCE_PATH" cat > /tmp/git-askpass <<'EOF'
git -C "$SOURCE_PATH" checkout "$REVISION" #!/bin/sh
--- case "$1" in
apiVersion: tekton.dev/v1 *Username*) cat "$GIT_CREDENTIALS_PATH/username" ;;
kind: Task *Password*) cat "$GIT_CREDENTIALS_PATH/password" ;;
metadata: *) exit 1 ;;
name: maidn-node-static-build esac
namespace: tekton-pipelines EOF
spec: chmod 700 /tmp/git-askpass
stepTemplate: trap 'rm -f /tmp/git-askpass' EXIT
env: GIT_ASKPASS=/tmp/git-askpass GIT_TERMINAL_PROMPT=0 git clone "$REPOSITORY_URL" "$SOURCE_PATH"
- name: HOME git -c safe.directory="$SOURCE_PATH" -C "$SOURCE_PATH" checkout "$REVISION"
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:
- name: build-layer - name: build-layer
image: node:22-alpine image: node:22-alpine
env: env:
- name: OUTPUT_DIRECTORY - name: OUTPUT_DIRECTORY
value: $(params.output-directory) value: $(params.output-directory)
- name: SOURCE_PATH - name: SOURCE_PATH
value: $(workspaces.source.path) value: /workspace/local-source
- name: ARTIFACT_PATH
value: $(workspaces.artifacts.path)
- name: TARGET_DIRECTORY - name: TARGET_DIRECTORY
value: $(params.target-directory) value: $(params.target-directory)
- name: BUILD_CONFIGURATION - name: BUILD_CONFIGURATION
value: $(params.build-configuration) value: $(params.build-configuration)
- name: NODE_OPTIONS - name: NODE_OPTIONS
value: --max-old-space-size=384 value: --max-old-space-size=384
volumeMounts:
- name: local-source
mountPath: /workspace/local-source
resources: resources:
requests: requests:
memory: 256Mi memory: 256Mi
@ -107,62 +131,23 @@ spec:
case "$target_path" in ''|-*|*[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac case "$target_path" in ''|-*|*[!A-Za-z0-9._/-]*|*..*|*//*|*/) exit 1 ;; esac
case "$BUILD_CONFIGURATION" in ''|-*|*[!A-Za-z0-9._-]*) exit 1 ;; esac case "$BUILD_CONFIGURATION" in ''|-*|*[!A-Za-z0-9._-]*) exit 1 ;; esac
build_dir=$(mktemp -d) build_dir=$(mktemp -d)
layer_dir=$(mktemp -d)
trap 'rm -rf "$build_dir" "$layer_dir"' EXIT
cp -R "$SOURCE_PATH"/. "$build_dir" cp -R "$SOURCE_PATH"/. "$build_dir"
cd "$build_dir" cd "$build_dir"
# ponytail: no lockfile; skip install scripts and use npm ci when package-lock.json is committed. # 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 install --ignore-scripts --no-audit --no-fund --package-lock=false
npm run build -- --configuration "$BUILD_CONFIGURATION" npm run build -- --configuration "$BUILD_CONFIGURATION"
test -d "$OUTPUT_DIRECTORY" test -d "$OUTPUT_DIRECTORY"
layer_dir=$(mktemp -d)
trap 'rm -rf "$build_dir" "$layer_dir"' EXIT
mkdir -p "$layer_dir/$target_path" mkdir -p "$layer_dir/$target_path"
cp -R "$OUTPUT_DIRECTORY"/. "$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 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 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 case "$memory_peak" in ''|*[!0-9]*) ;; *) printf 'cgroup memory peak: %s\n' "$memory_peak"; break ;; esac
fi fi
done done
--- - name: validate-push-inputs
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 image: alpine:3.21.3
env: env:
- name: IMAGE - name: IMAGE
@ -185,7 +170,7 @@ spec:
args: args:
- append - append
- --base=$(params.base-image) - --base=$(params.base-image)
- --new_layer=$(workspaces.source.path)/layer.tar - --new_layer=$(workspaces.artifacts.path)/layer.tar
- --new_tag=$(params.image):$(params.revision) - --new_tag=$(params.image):$(params.revision)
volumeMounts: volumeMounts:
- name: registry-credentials - name: registry-credentials

View file

@ -6,32 +6,55 @@ catalog="$root/catalog/maidn-node-static-image.yaml"
tmp=$(mktemp -d) tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT trap 'rm -rf "$tmp"' EXIT
task() { step() {
awk -v name="$1" ' 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 } found { print }
' "$catalog" ' "$catalog"
} }
[ "$(grep -c '^apiVersion: tekton.dev/v1$' "$catalog")" -eq 3 ] [ "$(grep -c '^apiVersion: tekton.dev/v1$' "$catalog")" -eq 1 ]
[ "$(grep -c '^kind: Task$' "$catalog")" -eq 3 ] [ "$(grep -c '^kind: Task$' "$catalog")" -eq 1 ]
[ "$(grep -cF 'runAsNonRoot: true' "$catalog")" -eq 3 ] grep -qF 'runAsNonRoot: true' "$catalog"
[ "$(grep -cF 'forgejo-registry-credentials' "$catalog")" -eq 1 ] 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 for param in url revision image output-directory build-configuration base-image target-directory; do
task "$name" > "$tmp/$name" grep -qF " - name: $param" "$catalog"
grep -qF " name: $name" "$tmp/$name"
grep -qF ' - name: source' "$tmp/$name"
done 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" step clone > "$tmp/clone"
grep -qF 'value: --max-old-space-size=384' "$tmp/maidn-node-static-build" step build-layer > "$tmp/build-layer"
grep -qF 'memory: 256Mi' "$tmp/maidn-node-static-build" step validate-push-inputs > "$tmp/validate-push-inputs"
grep -qF 'memory: 512Mi' "$tmp/maidn-node-static-build" step push > "$tmp/push"
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 'emptyDir: {}' "$catalog"
grep -qF "if [ -r \"\$memory_peak_path\" ] && memory_peak=\$(cat \"\$memory_peak_path\"); then" "$tmp/maidn-node-static-build" grep -qF 'secretName: forgejo-git-credentials' "$catalog"
grep -qF 'image: gcr.io/go-containerregistry/crane:v0.21.7' "$tmp/maidn-node-static-push" grep -qF 'mountPath: /var/run/secrets/forgejo-git-credentials' "$tmp/clone"
grep -qF ' - append' "$tmp/maidn-node-static-push" grep -qF 'GIT_ASKPASS=/tmp/git-askpass GIT_TERMINAL_PROMPT=0 git clone "$REPOSITORY_URL" "$SOURCE_PATH"' "$tmp/clone"
grep -qF 'mountPath: /tekton/home/.docker' "$tmp/maidn-node-static-push" ! 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"