diff --git a/.dockerignore b/.dockerignore index d59c4e2..1bc823c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,8 +2,9 @@ * # Include +!alpine-packages.txt !Dockerfile !LICENSE !README.md -!show-versions.sh +!entrypoint.sh !pip diff --git a/.github/workflows/auto-create-pull-request.yml b/.github/workflows/auto-pull-request-create.yml similarity index 64% rename from .github/workflows/auto-create-pull-request.yml rename to .github/workflows/auto-pull-request-create.yml index 8102c39..8bd37f8 100644 --- a/.github/workflows/auto-create-pull-request.yml +++ b/.github/workflows/auto-pull-request-create.yml @@ -1,4 +1,4 @@ -name: (Auto) Create Pull Request +name: (Auto) Pull Request Create on: push: @@ -14,7 +14,7 @@ permissions: jobs: call: - uses: devops-infra/.github/.github/workflows/reusable-auto-create-pull-request.yml@v1 + uses: devops-infra/.github/.github/workflows/reusable-auto-pull-request-create.yml@v1 with: profile: dockerized secrets: inherit diff --git a/.github/workflows/cron-check-dependencies.yml b/.github/workflows/cron-dependency-update.yml similarity index 64% rename from .github/workflows/cron-check-dependencies.yml rename to .github/workflows/cron-dependency-update.yml index 62dd5c2..03c8ee8 100644 --- a/.github/workflows/cron-check-dependencies.yml +++ b/.github/workflows/cron-dependency-update.yml @@ -1,4 +1,4 @@ -name: (Cron) Check dependencies +name: (Cron) Dependency Update on: schedule: @@ -13,7 +13,7 @@ permissions: jobs: call: - uses: devops-infra/.github/.github/workflows/reusable-cron-check-dependencies.yml@v1 + uses: devops-infra/.github/.github/workflows/reusable-cron-dependency-update.yml@v1 with: profile: dockerized secrets: inherit diff --git a/.github/workflows/manual-update-version.yml b/.github/workflows/manual-release-create.yml similarity index 94% rename from .github/workflows/manual-update-version.yml rename to .github/workflows/manual-release-create.yml index 8d43eea..627d2a9 100644 --- a/.github/workflows/manual-update-version.yml +++ b/.github/workflows/manual-release-create.yml @@ -1,4 +1,4 @@ -name: (Manual) Update Version +name: (Manual) Release Create on: workflow_dispatch: @@ -30,7 +30,7 @@ permissions: jobs: call: - uses: devops-infra/.github/.github/workflows/reusable-manual-update-version.yml@v1 + uses: devops-infra/.github/.github/workflows/reusable-manual-release-create.yml@v1 with: bump-type: ${{ inputs.type }} explicit-version: ${{ inputs.version }} diff --git a/Dockerfile b/Dockerfile index 32663b6..9437d55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,16 @@ FROM alpine:3.23.3 +# Copy all needed files +COPY pip/requirements.txt /tmp/requirements.txt +COPY alpine-packages.txt /tmp/alpine-packages.txt +COPY entrypoint.sh /usr/bin/ + # Install prerequisits SHELL ["/bin/sh", "-euxo", "pipefail", "-c"] RUN apk update --no-cache ;\ - apk add --no-cache \ - bash~=5.3 \ - docker=~=29.1 \ - make~=4.4 \ - ncurses~=6.5 \ - python3~=3.12 \ - py3-pip~=25.1 - -# List of Python packages -COPY pip/requirements.txt /tmp/requirements.txt - -# Python packages -SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] -RUN pip3 install --break-system-packages --no-cache-dir -r /tmp/requirements.txt - -COPY show-versions.sh /usr/bin/ -SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] -RUN chmod +x \ - /usr/bin/show-versions.sh ;\ - # Cleanup \ + xargs -r apk add --no-cache < /tmp/alpine-packages.txt ;\ + pip3 install --break-system-packages --no-cache-dir -r /tmp/requirements.txt ;\ + chmod +x /usr/bin/entrypoint.sh ;\ rm -rf /var/cache/* ;\ rm -rf /root/.cache/* ;\ rm -rf /tmp/* @@ -64,4 +52,4 @@ LABEL \ repository="${REPO_URL}" WORKDIR /data -CMD ["show-versions.sh"] +CMD ["entrypoint.sh"] diff --git a/README.md b/README.md index 1c21b76..b743628 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,4 @@ Recommended setup: - GitHub Actions: set repo variables for the four values above, and secrets for `DOCKER_TOKEN` and `GITHUB_TOKEN`. Publish images without a release: -- Run the `(Manual) Update Version` workflow with `build_only: true` to build and push images without tagging a release. +- Run the `(Manual) Release Create` workflow with `build_only: true` to build and push images without tagging a release. diff --git a/Taskfile.cicd.yml b/Taskfile.cicd.yml index 7071bb2..9ea1fbf 100644 --- a/Taskfile.cicd.yml +++ b/Taskfile.cicd.yml @@ -79,10 +79,10 @@ tasks: exit $rc fi - version:get: - desc: Get current version + dependency:update: + desc: Update repository dependencies not covered by dependabot cmds: - - echo "{{.VERSION}}" + - task: scripts:packages:update version:set: desc: Validate version @@ -242,45 +242,7 @@ tasks: - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - sync:all: - desc: Sync all common files - cmds: - - task sync:configs - - task sync:ignores - - task sync:taskfiles - - sync:configs: - desc: Sync configuration files with devops-infra/.github - cmds: - - | - echo "▶️ Syncing configuration files from devops-infra/.github..." - curl -fsSL {{.CONFIGS_BASE_URL}}/.editorconfig -o ./.editorconfig - curl -fsSL {{.CONFIGS_BASE_URL}}/.hadolint.yaml -o ./.hadolint.yaml - curl -fsSL {{.CONFIGS_BASE_URL}}/.pre-commit-config.yaml -o ./.pre-commit-config.yaml - curl -fsSL {{.CONFIGS_BASE_URL}}/.shellcheckrc -o ./.shellcheckrc - curl -fsSL {{.CONFIGS_BASE_URL}}/.yamllint.yml -o ./.yamllint.yml - git add .editorconfig .hadolint.yaml .pre-commit-config.yaml .shellcheckrc .yamllint.yml - echo "✅ Synced configuration files" - - sync:ignores: - desc: Sync ignore files with devops-infra/.github - cmds: - - | - echo "▶️ Syncing ignore files from devops-infra/.github..." - curl -fsSL {{.CONFIGS_BASE_URL}}/.gitignore -o ./.gitignore - curl -fsSL {{.CONFIGS_BASE_URL}}/.dockerignore -o ./.dockerignore - git add .gitignore .dockerignore - echo "✅ Synced ignore files" - - sync:taskfiles: - desc: Sync Taskfiles with devops-infra/.github + version:get: + desc: Get current version cmds: - - | - echo "▶️ Syncing Taskfiles from devops-infra/.github..." - curl -fsSL {{.TASKFILES_BASE_URL}}/Taskfile.yml -o ./Taskfile.yml - curl -fsSL {{.TASKFILES_BASE_URL}}/Taskfile.cicd.yml -o ./Taskfile.cicd.yml - curl -fsSL {{.TASKFILES_BASE_URL}}/Taskfile.scripts.yml -o ./Taskfile.scripts.yml - curl -fsSL {{.TASKFILES_BASE_URL}}/Taskfile.docker.yml -o ./Taskfile.docker.yml - curl -fsSL {{.TASKFILES_BASE_URL}}/Taskfile.variables.yml -o ./Taskfile.variables.yml - git add Taskfile*.yml - echo "✅ Synced Taskfiles" + - echo "{{.VERSION}}" diff --git a/Taskfile.scripts.yml b/Taskfile.scripts.yml index b183f7d..ee738fc 100644 --- a/Taskfile.scripts.yml +++ b/Taskfile.scripts.yml @@ -49,7 +49,7 @@ tasks: - | echo "▶️ Running shellcheck..." set +e - docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style show-versions.sh + docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style entrypoint.sh rc=$? set -e if [ "$rc" -eq 0 ]; then @@ -87,6 +87,137 @@ tasks: - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" + packages:update: + desc: Update Alpine package pins in alpine-packages.txt + cmds: + - | + set -eu + if [ ! -f Dockerfile ]; then + echo "INFO: Dockerfile not found; nothing to update" + exit 0 + fi + if [ ! -f alpine-packages.txt ]; then + echo "INFO: alpine-packages.txt not found; nothing to update" + exit 0 + fi + + base_image="$(sed -nE 's/^FROM[[:space:]]+([^[:space:]]+).*/\1/p' Dockerfile | head -1)" + if [ -z "$base_image" ]; then + echo "INFO: Could not resolve base image; nothing to update" + exit 0 + fi + + case "$base_image" in + alpine:*|alpine) + : + ;; + *) + echo "INFO: Base image is '$base_image', not Alpine; nothing to update" + exit 0 + ;; + esac + + alpine_line="${base_image#alpine:}" + if [ "$alpine_line" = "$base_image" ] || [ -z "$alpine_line" ]; then + echo "INFO: Could not parse Alpine version from '$base_image'; nothing to update" + exit 0 + fi + alpine_minor="$(printf '%s' "$alpine_line" | awk -F. '{print $1 "." $2}')" + if ! printf '%s' "$alpine_minor" | grep -Eq '^[0-9]+\.[0-9]+$'; then + echo "INFO: Unsupported Alpine version '$alpine_line'; nothing to update" + exit 0 + fi + alpine_repo="v${alpine_minor}" + arch="x86_64" + + normalize_minor() { + version="$1" + printf '%s' "$version" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/' + } + + fetch_index() { + repo="$1" + out="$2" + url="https://dl-cdn.alpinelinux.org/alpine/${alpine_repo}/${repo}/${arch}/APKINDEX.tar.gz" + curl --fail --silent --show-error "$url" | tar -O -zx APKINDEX > "$out" + } + + lookup_latest() { + pkg="$1" + for index in "$index_main" "$index_community"; do + found="$(awk -v pkg="$pkg" ' + BEGIN { RS=""; FS="\n" } + { + p=""; v="" + for (i=1; i<=NF; i++) { + if ($i ~ /^P:/) p=substr($i,3) + if ($i ~ /^V:/) v=substr($i,3) + } + if (p==pkg) { print v; exit } + } + ' "$index")" + if [ -n "$found" ]; then + printf '%s' "$found" + return 0 + fi + done + return 1 + } + + mkdir -p .tmp + index_main=".tmp/apkindex-main-${alpine_repo}-${arch}.txt" + index_community=".tmp/apkindex-community-${alpine_repo}-${arch}.txt" + fetch_index main "$index_main" + fetch_index community "$index_community" + + if ! grep -Eq '^[a-zA-Z0-9+_.-]+(=~|~=)[0-9]+\.[0-9]+$' alpine-packages.txt; then + echo "INFO: No pinned Alpine packages (~=X.Y) found in alpine-packages.txt" + exit 0 + fi + + tmp_out=".tmp/alpine-packages.updated.txt" + : > "$tmp_out" + updated=0 + while IFS= read -r line || [ -n "$line" ]; do + if [ -z "$line" ] || printf '%s' "$line" | grep -Eq '^[[:space:]]*#'; then + echo "$line" >> "$tmp_out" + continue + fi + if ! printf '%s' "$line" | grep -Eq '^[a-zA-Z0-9+_.-]+(=~|~=)[0-9]+\.[0-9]+$'; then + echo "$line" >> "$tmp_out" + continue + fi + + pkg="$(printf '%s' "$line" | sed -E 's/^([a-zA-Z0-9+_.-]+)(=~|~=).*/\1/')" + current_minor="$(printf '%s' "$line" | sed -E 's/^[a-zA-Z0-9+_.-]+(=~|~=)([0-9]+\.[0-9]+).*$/\2/')" + latest_full="$(lookup_latest "$pkg" || true)" + if [ -z "$latest_full" ]; then + echo "WARN: Could not resolve latest version for $pkg; keeping $line" + echo "$line" >> "$tmp_out" + continue + fi + + latest_minor="$(normalize_minor "$latest_full")" + if [ "$latest_minor" = "$current_minor" ]; then + echo "OK: $pkg already up to date at $current_minor" + echo "$pkg~=$current_minor" >> "$tmp_out" + continue + fi + echo "UPDATE: $pkg $current_minor -> $latest_minor" + echo "$pkg~=$latest_minor" >> "$tmp_out" + updated=1 + done < alpine-packages.txt + + if ! cmp -s alpine-packages.txt "$tmp_out"; then + mv "$tmp_out" alpine-packages.txt + else + rm -f "$tmp_out" + fi + + if [ "$updated" -eq 0 ]; then + echo "INFO: No Alpine package updates were required" + fi + version:get: desc: Get current version cmds: diff --git a/alpine-packages.txt b/alpine-packages.txt new file mode 100644 index 0000000..e037727 --- /dev/null +++ b/alpine-packages.txt @@ -0,0 +1,6 @@ +bash~=5.3 +docker~=29.1 +make~=4.4 +ncurses~=6.5 +python3~=3.12 +py3-pip~=25.1 diff --git a/show-versions.sh b/entrypoint.sh similarity index 100% rename from show-versions.sh rename to entrypoint.sh