diff --git a/.dockerignore b/.dockerignore index 1773ff6..ef4ac8f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,7 @@ * # Include +!Dockerfile +!LICENSE !README.md +!alpine-packages.txt diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b3c739b..1f8bbc8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,9 +2,10 @@ version: 2 updates: - package-ecosystem: github-actions directory: / + target-branch: master schedule: interval: weekly - day: monday + day: saturday time: "05:00" timezone: "UTC" assignees: @@ -13,3 +14,18 @@ updates: gha-all: patterns: - "*" + + - package-ecosystem: docker + directory: / + target-branch: master + schedule: + interval: weekly + day: saturday + time: "05:00" + timezone: "UTC" + assignees: + - ChristophShyper + groups: + docker-base-images: + patterns: + - "*" diff --git a/.github/workflows/auto-pull-request-create.yml b/.github/workflows/auto-pull-request-create.yml index 2c2650a..5d1dbdc 100644 --- a/.github/workflows/auto-pull-request-create.yml +++ b/.github/workflows/auto-pull-request-create.yml @@ -18,5 +18,5 @@ jobs: call: uses: devops-infra/.github/.github/workflows/reusable-auto-pull-request-create.yml@v1 with: - profile: other + profile: dockerized secrets: inherit diff --git a/.github/workflows/auto-release-create.yml b/.github/workflows/auto-release-create.yml deleted file mode 100644 index 022d35d..0000000 --- a/.github/workflows/auto-release-create.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: (A) Release Create - -on: - push: - branches: - - master - - main - workflow_dispatch: - inputs: - release_branch: - description: Release branch to publish from (e.g. release/v1.3.0) - required: false - default: '' - type: string - release_version: - description: Explicit release version override (e.g. v1.3.0) - required: false - default: '' - type: string - -permissions: - contents: write - packages: write - pull-requests: write - issues: write - -jobs: - call: - uses: devops-infra/.github/.github/workflows/reusable-auto-release-create.yml@v1 - with: - profile: other - release-branch: ${{ inputs.release_branch }} - release-version: ${{ inputs.release_version }} - secrets: inherit diff --git a/.github/workflows/cron-dependency-update.yml b/.github/workflows/cron-dependency-update.yml index 5bf1f36..19b3503 100644 --- a/.github/workflows/cron-dependency-update.yml +++ b/.github/workflows/cron-dependency-update.yml @@ -2,7 +2,7 @@ name: (C) Dependency Update on: schedule: - - cron: 0 5 * * 1 + - cron: 0 5 * * 6 workflow_dispatch: permissions: @@ -12,8 +12,58 @@ permissions: issues: read jobs: - call: - uses: devops-infra/.github/.github/workflows/reusable-cron-dependency-update.yml@v1 - with: - profile: other - secrets: inherit + check: + name: Check for Azure CLI updates + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Install Task + uses: go-task/setup-task@v2 + with: + version: 3.x + + - name: Install Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Check for new versions + run: task dependency:update + + - name: Run linters + run: task lint + + - name: Build candidate image + if: env.VERSION_TAG != '' + run: task docker:build VERSION_PREFIX=test- + + - name: Run container-structure-tests + if: env.VERSION_TAG != '' + run: task test:structure VERSION_PREFIX=test- + + - name: Commit and push changes + if: env.VERSION_TAG != '' + uses: devops-infra/action-commit-push@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + target_branch: dependency/${{ env.VERSION_BRANCH }} + commit_message: "chore(deps): bump ${{ env.VERSION_TAG }}" + signing_mode: ssh + signing_key: ${{ secrets.SSH_SIGNING_KEY }} + + - name: Get template + if: env.VERSION_TAG != '' + run: task git:get-pr-template + + - name: Create pull request + if: env.VERSION_TAG != '' + uses: devops-infra/action-pull-request@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + target_branch: master + title: "chore(deps): bump ${{ env.VERSION_TAG }}" + template: .tmp/PULL_REQUEST_TEMPLATE.md + get_diff: true diff --git a/.github/workflows/manual-release-branch-prepare.yml b/.github/workflows/manual-release-branch-prepare.yml deleted file mode 100644 index 35312b9..0000000 --- a/.github/workflows/manual-release-branch-prepare.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: (M) Release Branch Prepare - -on: - workflow_dispatch: - inputs: - type: - description: Bump type - required: false - default: patch - type: choice - options: - - patch - - minor - - major - - set - version: - description: Explicit version when type="set" (e.g., v1.2.3) - required: false - default: '' - build_only: - description: Build and push artifacts without version bump - required: false - default: false - type: boolean - -permissions: - contents: write - packages: write - pull-requests: write - issues: write - -jobs: - call: - uses: devops-infra/.github/.github/workflows/reusable-manual-release-branch-prepare.yml@v1 - with: - bump-type: ${{ inputs.type }} - explicit-version: ${{ inputs.version }} - build-and-push-only: ${{ inputs.build_only }} - profile: other - secrets: inherit diff --git a/.github/workflows/manual-release-create.yml b/.github/workflows/manual-release-create.yml new file mode 100644 index 0000000..6ed3995 --- /dev/null +++ b/.github/workflows/manual-release-create.yml @@ -0,0 +1,120 @@ +name: (M) Release Create + +on: + workflow_dispatch: + inputs: + version: + description: Azure CLI version to publish. Leave empty to use the Dockerfile version. + required: false + type: string + +permissions: + contents: read + packages: write + +jobs: + lint: + name: Lint + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Install Task + uses: go-task/setup-task@v2 + with: + version: 3.x + + - name: Run linters + run: task lint + + prepare: + name: Validate version + runs-on: ubuntu-24.04 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install Task + uses: go-task/setup-task@v2 + with: + version: 3.x + + - name: Resolve version + id: version + run: | + set -eu + current="$(task version:get)" + requested="${{ inputs.version }}" + if [ -n "$requested" ] && [ "$requested" != "$current" ]; then + echo "❌ Requested version '$requested' does not match Dockerfile version '$current'" + exit 1 + fi + if ! printf '%s' "$current" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "❌ Invalid Azure CLI version: $current" + exit 1 + fi + echo "version=$current" >> "$GITHUB_OUTPUT" + + build-and-push: + name: Build and push + runs-on: ubuntu-24.04 + timeout-minutes: 90 + needs: [lint, prepare] + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Install Task + uses: go-task/setup-task@v2 + with: + version: 3.x + + - name: Install Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Install QEMU + uses: docker/setup-qemu-action@v4 + with: + image: tonistiigi/binfmt:latest + platforms: amd64,arm64 + + - name: Build and push images + env: + BUILDX_NO_DEFAULT_ATTESTATIONS: 1 + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ needs.prepare.outputs.version }}" + task docker:push VERSION_OVERRIDE="$VERSION" + + - name: Pull published tags + run: | + set -eu + VERSION="${{ needs.prepare.outputs.version }}" + MINOR="$(printf '%s' "$VERSION" | awk -F. '{print $1 "." $2}')" + for image in \ + "devopsinfra/docker-azure-cli:${VERSION}" \ + "devopsinfra/docker-azure-cli:${MINOR}" \ + "devopsinfra/docker-azure-cli:latest" \ + "ghcr.io/devops-infra/docker-azure-cli:${VERSION}" \ + "ghcr.io/devops-infra/docker-azure-cli:${MINOR}" \ + "ghcr.io/devops-infra/docker-azure-cli:latest" + do + docker pull "$image" + done + + - name: Run container-structure-tests + env: + IMAGE: devopsinfra/docker-azure-cli:${{ needs.prepare.outputs.version }} + run: | + VERSION="${{ needs.prepare.outputs.version }}" + task test:structure VERSION_OVERRIDE="$VERSION" IMAGE="$IMAGE" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c03960b..7e16b3a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,6 +29,11 @@ repos: entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work rhysd/actionlint:latest -color' language: system pass_filenames: false + - id: hadolint + name: hadolint + entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian /bin/hadolint "$@"' -- + language: system + files: (^|/)Dockerfile(\..*)?$ - id: shellcheck name: shellcheck entry: bash -lc 'docker run --rm -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style "$@"' -- diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3109732 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM alpine:3.24.1 + +ENV PIP_BREAK_SYSTEM_PACKAGES=1 + +ARG AZ_VERSION=2.87.0 + +COPY alpine-packages.txt /tmp/alpine-packages.txt + +SHELL ["/bin/ash", "-euxo", "pipefail", "-c"] +# hadolint ignore=DL3018 +RUN xargs -r apk add --no-cache < /tmp/alpine-packages.txt; \ + ln -sf /usr/bin/python3 /usr/bin/python; \ + ln -sf /usr/bin/pip3 /usr/bin/pip; \ + pip3 install --no-cache-dir "azure-cli==${AZ_VERSION}"; \ + test "$(az version --output json | jq -r '."azure-cli"')" = "${AZ_VERSION}"; \ + rm -rf /var/cache/*; \ + rm -rf /root/.cache/*; \ + rm -rf /tmp/* diff --git a/README.md b/README.md index 596a8ba..5624783 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,92 @@ -# Universal template for organizational repository +# `docker-azure-cli` +Minimal Alpine-based Azure CLI base image for the `devops-infra` organization. -## 📊 Badges -[ -![GitHub repo](https://img.shields.io/badge/GitHub-devops--infra%2Ftemplate--repository-blueviolet.svg?style=plastic&logo=github) -![GitHub last commit](https://img.shields.io/github/last-commit/devops-infra/template-repository?color=blueviolet&logo=github&style=plastic&label=Last%20commit) -![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/devops-infra/template-repository?color=blueviolet&label=Code%20size&style=plastic&logo=github) -![GitHub license](https://img.shields.io/github/license/devops-infra/template-repository?color=blueviolet&logo=github&style=plastic&label=License) -](https://github.com/devops-infra/template-repository "shields.io") +## Source and Images -## Forking -To publish images from a fork, set these variables so Task uses your registry identities: -`DOCKER_USERNAME`, `DOCKER_ORG_NAME`, `GITHUB_USERNAME`, `GITHUB_ORG_NAME`. +- Source: +- Docker Hub: +- GHCR: + +## Current Version + +| Image | Current Azure CLI version | +| --- | --- | +| `docker-azure-cli` | `2.87.0` | + +Published image tags: + +- `2.87.0` +- `2.87` +- `latest` + +All published images are multi-architecture: + +- `linux/amd64` +- `linux/arm64` + +## What Is Included + +- `alpine:3.24.x` +- `bash` +- `ca-certificates` +- `jq` +- `python3` (`python`) +- `pip3` (`pip`) +- `azure-cli` + +This image intentionally does **not** include: + +- AWS CLI +- Google Cloud SDK +- Terraform +- OpenTofu +- Terragrunt + +## Local Usage + +Build the image for the local host platform: -Two supported options (environment variables take precedence over `.env`): ```bash -# .env (local only, not committed) -DOCKER_USERNAME=your-dockerhub-user -DOCKER_ORG_NAME=your-dockerhub-org -GITHUB_USERNAME=your-github-user -GITHUB_ORG_NAME=your-github-org +task docker:build ``` +Push the multi-arch image to Docker Hub and GHCR: + ```bash -# Shell override -DOCKER_USERNAME=your-dockerhub-user \ -DOCKER_ORG_NAME=your-dockerhub-org \ -GITHUB_USERNAME=your-github-user \ -GITHUB_ORG_NAME=your-github-org \ -task docker:build +task docker:push +``` + +Check for Azure CLI and Alpine package updates: + +```bash +task dependency:update +``` + +Run container-structure-tests against the built image: + +```bash +task test:structure +``` + +## Downstream Usage + +Use this image as a base for Azure-enabled Docker images: + +```dockerfile +FROM ghcr.io/devops-infra/docker-azure-cli:2.87.0 +``` + +Example interactive usage: + +```bash +docker run --rm -it devopsinfra/docker-azure-cli:latest az version ``` -Recommended setup: -- Local development: use a `.env` file. -- GitHub Actions: set repo variables for the four values above, and secrets for `DOCKER_TOKEN` and `GITHUB_TOKEN`. +## Automation + +The repository includes workflows for: -Publish images without a release: -- Run the `(Manual) Release Create` workflow with `build_only: true` to build and push images without tagging a release. +- dependency update pull requests when a new Azure CLI version is published +- manual multi-arch image publishing to Docker Hub and GHCR +- automatic pull request creation for non-default branches diff --git a/Taskfile.cicd.yml b/Taskfile.cicd.yml index 82fe5b3..31d35b1 100644 --- a/Taskfile.cicd.yml +++ b/Taskfile.cicd.yml @@ -1,9 +1,11 @@ version: '3' silent: true + vars: PR_TEMPLATE: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/PULL_REQUEST_TEMPLATE.md - CONFIGS_BASE_URL: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/templates/other/configs - TASKFILES_BASE_URL: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/templates/other/taskfiles + CONFIGS_BASE_URL: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/templates/dockerized/configs + TASKFILES_BASE_URL: https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/templates/dockerized/taskfiles + tasks: pre-commit: desc: Run all pre-commit hooks @@ -19,6 +21,7 @@ tasks: desc: Run all linters (Dockerfile, shell scripts, workflows, YAML) cmds: - task: lint:actionlint + - task: lint:hadolint - task: lint:shellcheck - task: lint:yamllint @@ -27,6 +30,11 @@ tasks: cmds: - task: scripts:lint:actionlint + lint:hadolint: + desc: Lint Dockerfile with hadolint + cmds: + - task: scripts:lint:hadolint + lint:shellcheck: desc: Lint shell scripts with shellcheck cmds: @@ -38,39 +46,14 @@ tasks: - task: scripts:lint:yamllint dependency:update: - desc: 'No-op: no dedicated dependency updater configured for this profile' + desc: Check Azure CLI and Alpine package versions cmds: - task: scripts:dependency:update - version:set: - desc: Validate version - cmds: - - task: scripts:version:set - - version:update:patch: - desc: Increment patch version (e.g., 1.2.3 -> 1.2.4) - cmds: - - task version:set VERSION=v{{.MAJOR}}.{{.MINOR}}.{{.NEXT_PATCH}} - - version:update:minor: - desc: Increment minor version (e.g., 1.2.3 -> 1.3.0) + test:structure: + desc: Run container-structure-tests against the built image cmds: - - task version:set VERSION=v{{.MAJOR}}.{{.NEXT_MINOR}}.0 - - version:update:major: - desc: Increment major version (e.g., 1.2.3 -> 2.0.0) - cmds: - - task version:set VERSION=v{{.NEXT_MAJOR}}.0.0 - - version:resolve-next: - desc: Resolve next version from bump type and profile - cmds: - - task: scripts:version:resolve-next - - version:tag-release: - desc: Create set of git tags - cmds: - - task: scripts:version:tag-release + - task: scripts:test:structure git:get-pr-template: desc: Get pull request template @@ -83,6 +66,11 @@ tasks: - task: scripts:git:set-config version:get: - desc: Get current version + desc: Get current Azure CLI version from Dockerfile cmds: - task: scripts:version:get + + version:set: + desc: Validate version format + cmds: + - task: scripts:version:set diff --git a/Taskfile.docker.yml b/Taskfile.docker.yml new file mode 100644 index 0000000..7b50fa9 --- /dev/null +++ b/Taskfile.docker.yml @@ -0,0 +1,36 @@ +version: '3' + +silent: true + +tasks: + docker:login: + desc: Login to hub.docker.com and ghcr.io + cmds: + - task: scripts:docker:login + + docker:cmds: + desc: Show docker build commands + cmds: + - task: scripts:docker:cmds + + docker:build: + desc: Build Docker image for the local host platform + cmds: + - task: scripts:docker:build + + docker:build:inspect: + desc: Inspect built Docker image + cmds: + - task: scripts:docker:build:inspect + + docker:push: + desc: Build and push multi-arch Docker images + deps: + - task: docker:login + cmds: + - task: scripts:docker:push + + docker:push:inspect: + desc: Inspect pushed Docker image + cmds: + - task: scripts:docker:push:inspect diff --git a/Taskfile.scripts.yml b/Taskfile.scripts.yml index 05194b2..8c7791c 100644 --- a/Taskfile.scripts.yml +++ b/Taskfile.scripts.yml @@ -31,10 +31,6 @@ tasks: cmds: - | echo "â–ļī¸ Running hadolint..." - if [ ! -f Dockerfile ]; then - echo "â„šī¸ No Dockerfile found, skipping hadolint" - exit 0 - fi set +e docker run --rm -i -v "$PWD:/work" -w /work hadolint/hadolint:latest-debian < Dockerfile rc=$? @@ -53,18 +49,12 @@ tasks: - | echo "â–ļī¸ Running shellcheck..." mapfile -t files < <(git ls-files '*.sh') - existing_files=() - for f in "${files[@]}"; do - if [ -f "$f" ]; then - existing_files+=("$f") - fi - done - if [ "${#existing_files[@]}" -eq 0 ]; then + if [ "${#files[@]}" -eq 0 ]; then echo "â„šī¸ No shell scripts found, skipping shellcheck" exit 0 fi set +e - docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style -- "${existing_files[@]}" + docker run --rm -i -v "$PWD:/work" -w /work koalaman/shellcheck:stable -x -S style -- "${files[@]}" rc=$? set -e if [ "$rc" -eq 0 ]; then @@ -90,174 +80,351 @@ tasks: exit $rc fi - dependency:update: - desc: 'No-op: no dedicated dependency updater configured for this profile' + docker:login: + desc: Login to container registries cmds: - | - echo "INFO: No dedicated dependency updater configured for this repository profile." - echo "INFO: Dependabot handles GitHub Actions and package metadata updates." - echo "INFO: Keep this task as a safe no-op until a repo-specific dependency updater is defined." - - git:get-pr-template: - desc: Get pull request template - cmds: - - mkdir -p .tmp - - curl -LsS https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/PULL_REQUEST_TEMPLATE.md -o .tmp/PULL_REQUEST_TEMPLATE.md + set -eu + has_dockerhub=false + has_ghcr=false - git:set-config: - desc: Set git user config - cmds: - - git config user.name "github-actions[bot]" - - git config user.email "github-actions[bot]@users.noreply.github.com" + if [ -n '{{.DOCKER_USERNAME}}' ] && [ -n "${DOCKER_TOKEN:-}" ]; then + has_dockerhub=true + fi - version:get: - desc: Get current version - cmds: - - echo "{{.VERSION}}" + if [ -n '{{.GITHUB_USERNAME}}' ] && [ -n "${GITHUB_TOKEN:-}" ]; then + has_ghcr=true + fi - version:set: - desc: Validate version - cmds: - - | - if [ -z "{{.VERSION}}" ]; then - echo "❌ ERROR: VERSION is empty" + if [ "$has_dockerhub" = false ] && [ "$has_ghcr" = false ]; then + echo "❌ No registry credentials provided. Set DOCKER_USERNAME/DOCKER_TOKEN or GITHUB_USERNAME/GITHUB_TOKEN." exit 1 fi - if ! echo "{{.VERSION}}" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "❌ ERROR: VERSION '{{.VERSION}}' is not a valid semantic version (expected vX.Y.Z or X.Y.Z)" - exit 1 + + if [ "$has_dockerhub" = true ]; then + printf '%s' "${DOCKER_TOKEN}" | docker login -u '{{.DOCKER_USERNAME}}' --password-stdin fi - version:update:patch: - desc: Increment patch version (e.g., 1.2.3 -> 1.2.4) + if [ "$has_ghcr" = true ]; then + printf '%s' "${GITHUB_TOKEN}" | docker login ghcr.io -u '{{.GITHUB_USERNAME}}' --password-stdin + fi + + docker:cmds: + desc: Show docker build commands cmds: - - task version:set VERSION=v{{.MAJOR}}.{{.MINOR}}.{{.NEXT_PATCH}} + - | + echo "Local build:" + echo "docker buildx build --platform {{.PLATFORMS_LOCAL}} --load ..." + echo + echo "Multi-arch push:" + echo "docker buildx build --platform {{.PLATFORMS}} --push ..." - version:update:minor: - desc: Increment minor version (e.g., 1.2.3 -> 1.3.0) + docker:build: + desc: Build image for the local host platform + shell: bash cmds: - - task version:set VERSION=v{{.MAJOR}}.{{.NEXT_MINOR}}.0 + - | + set -eu + docker buildx create --use >/dev/null 2>&1 || true + cmd=(docker buildx build --platform '{{.PLATFORMS_LOCAL}}' --load --file '{{.DOCKERFILE}}') + cmd+=(--build-arg "AZ_VERSION={{.VERSION_FULL}}") + cmd+=(--label "org.label-schema.schema-version=1.0") + cmd+=(--label "org.label-schema.build-date={{.BUILD_DATE}}") + cmd+=(--label "org.label-schema.name={{.LABEL_NAME}}") + cmd+=(--label "org.label-schema.description={{.LABEL_DESCRIPTION}}") + cmd+=(--label "org.label-schema.usage={{.LABEL_DOCS_URL}}") + cmd+=(--label "org.label-schema.url={{.LABEL_HOMEPAGE}}") + cmd+=(--label "org.label-schema.vcs-url={{.LABEL_REPO_URL}}") + cmd+=(--label "org.label-schema.vcs-ref={{.GIT_SHA}}") + cmd+=(--label "org.label-schema.vendor={{.LABEL_VENDOR}}") + cmd+=(--label "org.label-schema.version={{.VERSION_FULL}}") + cmd+=(--label "org.opencontainers.image.created={{.BUILD_DATE}}") + cmd+=(--label "org.opencontainers.image.authors={{.LABEL_AUTHOR}}") + cmd+=(--label "org.opencontainers.image.url={{.LABEL_HOMEPAGE}}") + cmd+=(--label "org.opencontainers.image.documentation={{.LABEL_DOCS_URL}}") + cmd+=(--label "org.opencontainers.image.source={{.LABEL_REPO_URL}}") + cmd+=(--label "org.opencontainers.image.version={{.VERSION_FULL}}") + cmd+=(--label "org.opencontainers.image.revision={{.GIT_SHA}}") + cmd+=(--label "org.opencontainers.image.vendor={{.LABEL_VENDOR}}") + cmd+=(--label "org.opencontainers.image.licenses={{.LABEL_LICENSE}}") + cmd+=(--label "org.opencontainers.image.title={{.LABEL_NAME}}") + cmd+=(--label "org.opencontainers.image.description={{.LABEL_DESCRIPTION}}") + cmd+=(--tag '{{.DOCKER_NAME}}:{{.EXACT_TAG}}') + cmd+=(--tag '{{.DOCKER_NAME}}:{{.MINOR_TAG}}') + cmd+=(--tag '{{.DOCKER_NAME}}:{{.LATEST_TAG}}') + cmd+=(--tag '{{.GITHUB_NAME}}:{{.EXACT_TAG}}') + cmd+=(--tag '{{.GITHUB_NAME}}:{{.MINOR_TAG}}') + cmd+=(--tag '{{.GITHUB_NAME}}:{{.LATEST_TAG}}') + cmd+=(.) + "${cmd[@]}" - version:update:major: - desc: Increment major version (e.g., 1.2.3 -> 2.0.0) + docker:build:inspect: + desc: Inspect the local exact-tag image cmds: - - task version:set VERSION=v{{.NEXT_MAJOR}}.0.0 + - docker image inspect {{.DOCKER_NAME}}:{{.EXACT_TAG}} | jq - version:resolve-next: - desc: Resolve next version from bump type and profile + docker:push: + desc: Build and push multi-arch images + shell: bash cmds: - | set -eu - bump_type="${BUMP_TYPE:-patch}" - input_version="${INPUT_VERSION:-}" + docker buildx create --use >/dev/null 2>&1 || true + cmd=(docker buildx build --platform '{{.PLATFORMS}}' --push --file '{{.DOCKERFILE}}') + cmd+=(--build-arg "AZ_VERSION={{.VERSION_FULL}}") + cmd+=(--annotation 'index:org.label-schema.schema-version=1.0') + cmd+=(--annotation 'index:org.label-schema.build-date={{.BUILD_DATE}}') + cmd+=(--annotation 'index:org.label-schema.name={{.LABEL_NAME}}') + cmd+=(--annotation 'index:org.label-schema.description={{.LABEL_DESCRIPTION}}') + cmd+=(--annotation 'index:org.label-schema.usage={{.LABEL_DOCS_URL}}') + cmd+=(--annotation 'index:org.label-schema.url={{.LABEL_HOMEPAGE}}') + cmd+=(--annotation 'index:org.label-schema.vcs-url={{.LABEL_REPO_URL}}') + cmd+=(--annotation 'index:org.label-schema.vcs-ref={{.GIT_SHA}}') + cmd+=(--annotation 'index:org.label-schema.vendor={{.LABEL_VENDOR}}') + cmd+=(--annotation 'index:org.label-schema.version={{.VERSION_FULL}}') + cmd+=(--annotation 'index:org.opencontainers.image.created={{.BUILD_DATE}}') + cmd+=(--annotation 'index:org.opencontainers.image.authors={{.LABEL_AUTHOR}}') + cmd+=(--annotation 'index:org.opencontainers.image.url={{.LABEL_HOMEPAGE}}') + cmd+=(--annotation 'index:org.opencontainers.image.documentation={{.LABEL_DOCS_URL}}') + cmd+=(--annotation 'index:org.opencontainers.image.source={{.LABEL_REPO_URL}}') + cmd+=(--annotation 'index:org.opencontainers.image.version={{.VERSION_FULL}}') + cmd+=(--annotation 'index:org.opencontainers.image.revision={{.GIT_SHA}}') + cmd+=(--annotation 'index:org.opencontainers.image.vendor={{.LABEL_VENDOR}}') + cmd+=(--annotation 'index:org.opencontainers.image.licenses={{.LABEL_LICENSE}}') + cmd+=(--annotation 'index:org.opencontainers.image.title={{.LABEL_NAME}}') + cmd+=(--annotation 'index:org.opencontainers.image.description={{.LABEL_DESCRIPTION}}') + cmd+=(--label "org.label-schema.schema-version=1.0") + cmd+=(--label "org.label-schema.build-date={{.BUILD_DATE}}") + cmd+=(--label "org.label-schema.name={{.LABEL_NAME}}") + cmd+=(--label "org.label-schema.description={{.LABEL_DESCRIPTION}}") + cmd+=(--label "org.label-schema.usage={{.LABEL_DOCS_URL}}") + cmd+=(--label "org.label-schema.url={{.LABEL_HOMEPAGE}}") + cmd+=(--label "org.label-schema.vcs-url={{.LABEL_REPO_URL}}") + cmd+=(--label "org.label-schema.vcs-ref={{.GIT_SHA}}") + cmd+=(--label "org.label-schema.vendor={{.LABEL_VENDOR}}") + cmd+=(--label "org.label-schema.version={{.VERSION_FULL}}") + cmd+=(--label "org.opencontainers.image.created={{.BUILD_DATE}}") + cmd+=(--label "org.opencontainers.image.authors={{.LABEL_AUTHOR}}") + cmd+=(--label "org.opencontainers.image.url={{.LABEL_HOMEPAGE}}") + cmd+=(--label "org.opencontainers.image.documentation={{.LABEL_DOCS_URL}}") + cmd+=(--label "org.opencontainers.image.source={{.LABEL_REPO_URL}}") + cmd+=(--label "org.opencontainers.image.version={{.VERSION_FULL}}") + cmd+=(--label "org.opencontainers.image.revision={{.GIT_SHA}}") + cmd+=(--label "org.opencontainers.image.vendor={{.LABEL_VENDOR}}") + cmd+=(--label "org.opencontainers.image.licenses={{.LABEL_LICENSE}}") + cmd+=(--label "org.opencontainers.image.title={{.LABEL_NAME}}") + cmd+=(--label "org.opencontainers.image.description={{.LABEL_DESCRIPTION}}") + cmd+=(--tag '{{.DOCKER_NAME}}:{{.EXACT_TAG}}') + cmd+=(--tag '{{.DOCKER_NAME}}:{{.MINOR_TAG}}') + cmd+=(--tag '{{.DOCKER_NAME}}:{{.LATEST_TAG}}') + cmd+=(--tag '{{.GITHUB_NAME}}:{{.EXACT_TAG}}') + cmd+=(--tag '{{.GITHUB_NAME}}:{{.MINOR_TAG}}') + cmd+=(--tag '{{.GITHUB_NAME}}:{{.LATEST_TAG}}') + cmd+=(.) + "${cmd[@]}" - normalize_version() { - candidate="${1#v}" - if ! printf "%s" "${candidate}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then - return 1 - fi - printf "v%s" "${candidate}" - } + docker:push:inspect: + desc: Inspect the pushed exact-tag image + cmds: + - docker buildx imagetools inspect {{.DOCKER_NAME}}:{{.EXACT_TAG}} - current="$(task version:get 2>/dev/null || true)" + test:structure: + desc: Run container-structure-tests + cmds: + - | + set -eu + image="${IMAGE:-{{.DOCKER_NAME}}:{{.EXACT_TAG}}}" + docker run --rm \ + -e AZ_VERSION='{{.VERSION_FULL}}' \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "$PWD:/work" \ + -w /work \ + gcr.io/gcp-runtimes/container-structure-test:v1.19.3 \ + test \ + --image "$image" \ + --config tests/azure.yml - case "$bump_type" in - set) - [ -n "$input_version" ] || { echo "Missing version for type=set"; exit 1; } - next="$(normalize_version "$input_version")" || { - echo "Invalid explicit version: $input_version. Expected vX.Y.Z or X.Y.Z" - exit 1 + packages:update: + desc: Update Alpine package pins in alpine-packages.txt + cmds: + - | + set -eu + if [ ! -f Dockerfile ] || [ ! -f alpine-packages.txt ]; then + echo "INFO: Dockerfile or alpine-packages.txt not found; nothing to update" + exit 0 + fi + + base_image="$(awk ' + toupper($1) == "FROM" { + i = 2 + while (i <= NF && $i ~ /^--/) { + i++ } - ;; - patch|minor|major) - [ -n "$current" ] || { echo "Current version not found or invalid. Expected vX.Y.Z"; exit 1; } - current="$(normalize_version "$current")" || { echo "Current version not found or invalid. Expected vX.Y.Z"; exit 1; } - no_v="${current#v}" - major="$(printf "%s" "$no_v" | awk -F. '{print $1}')" - minor="$(printf "%s" "$no_v" | awk -F. '{print $2}')" - patch="$(printf "%s" "$no_v" | awk -F. '{print $3}')" - case "$bump_type" in - patch) next="v${major}.${minor}.$((patch + 1))" ;; - minor) next="v${major}.$((minor + 1)).0" ;; - major) next="v$((major + 1)).0.0" ;; - esac - ;; - *) - echo "Unknown type: $bump_type" - exit 1 - ;; + if (i <= NF) { + image = $i + } + } + END { + if (image != "") { + print image + } + } + ' Dockerfile)" + + case "$base_image" in + alpine:*) ;; + *) echo "INFO: Base image is '$base_image', not Alpine; nothing to update"; exit 0 ;; esac - printf "%s" "$next" + alpine_line="${base_image#alpine:}" + alpine_minor="$(printf '%s' "$alpine_line" | awk -F. '{print $1 "." $2}')" + alpine_repo="v${alpine_minor}" + arch="x86_64" - version:tag-release: - desc: Create set of git tags - cmds: - - | - set -eu - if (set -o | grep -q pipefail) 2>/dev/null; then set -o pipefail; fi + normalize_minor() { + printf '%s' "$1" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/' + } - REMOTE='origin' - FULL='{{.VERSION_FULL}}' - MINOR='{{.VERSION_MINOR}}' - MAJOR='{{.VERSION_MAJOR}}' + 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" + } - # Validate vX.Y.Z - if ! printf "%s" "$FULL" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "❌ ERROR: VERSION '$FULL' must match vX.Y.Z" >&2 - exit 1 - fi + 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 + } - tag_sha() { git rev-parse "refs/tags/$1" 2>/dev/null || true; } - remote_tag_sha() { git ls-remote --tags "$REMOTE" "refs/tags/$1" 2>/dev/null | awk '{print $1}' || true; } + 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" - echo "â„šī¸ INFO: Tags - Full: $FULL | Minor: $MINOR | Major: $MAJOR" + 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 - # Full tag: must NOT exist on remote; fail fast if it does - full_remote_sha="$(remote_tag_sha "$FULL")" - if [ -n "$full_remote_sha" ]; then - echo "❌ ERROR: Full tag '$FULL' already exists on remote; aborting" >&2 - exit 1 - fi + pkg="$(printf '%s' "$line" | sed -E 's/^([a-zA-Z0-9+_.-]+).*/\1/')" + current_minor="$(printf '%s' "$line" | sed -nE 's/^[a-zA-Z0-9+_.-]+(~=|=~)([0-9]+\.[0-9]+).*$/\2/p')" + latest_full="$(lookup_latest "$pkg" || true)" + + if [ -z "$latest_full" ]; then + echo "$line" >> "$tmp_out" + continue + fi - # Create full tag locally (if missing) and push - if git rev-parse --quiet --verify "refs/tags/$FULL" >/dev/null 2>&1; then - echo "â„šī¸ INFO: Full tag '$FULL' exists locally but not on remote; pushing" + latest_minor="$(normalize_minor "$latest_full")" + if [ "$latest_minor" != "$current_minor" ]; then + updated=1 + fi + echo "$pkg~=$latest_minor" >> "$tmp_out" + done < alpine-packages.txt + + if ! cmp -s alpine-packages.txt "$tmp_out"; then + mv "$tmp_out" alpine-packages.txt else - echo "â„šī¸ INFO: Creating full tag '$FULL'" - git tag --annotate "$FULL" --message "$FULL" + rm -f "$tmp_out" fi - git push "$REMOTE" "refs/tags/$FULL" - echo "✅ OK: Pushed full tag '$FULL'" - - # Minor tag: create or update - git tag --force --annotate "$MINOR" --message "$FULL" - minor_local_sha="$(tag_sha "$MINOR")" - minor_remote_sha="$(remote_tag_sha "$MINOR")" - if [ -z "$minor_remote_sha" ]; then - git push "$REMOTE" "refs/tags/$MINOR" - echo "✅ OK: Created and pushed minor tag '$MINOR' -> $minor_local_sha" - else - if [ "$minor_local_sha" != "$minor_remote_sha" ]; then - echo "âš ī¸ WARN: Updating remote minor tag '$MINOR' to $minor_local_sha (was $minor_remote_sha)" - git push --force "$REMOTE" "refs/tags/$MINOR" - else - echo "â„šī¸ INFO: Minor tag '$MINOR' already up-to-date" - fi + + if [ "$updated" -eq 0 ]; then + echo "INFO: No Alpine package updates were required" fi - # Major tag: create or update - git tag --force --annotate "$MAJOR" --message "$FULL" - major_local_sha="$(tag_sha "$MAJOR")" - major_remote_sha="$(remote_tag_sha "$MAJOR")" - if [ -z "$major_remote_sha" ]; then - git push "$REMOTE" "refs/tags/$MAJOR" - echo "✅ OK: Created and pushed major tag '$MAJOR' -> $major_local_sha" - else - if [ "$major_local_sha" != "$major_remote_sha" ]; then - echo "âš ī¸ WARN: Updating remote major tag '$MAJOR' to $major_local_sha (was $major_remote_sha)" - git push --force "$REMOTE" "refs/tags/$MAJOR" - else - echo "â„šī¸ INFO: Major tag '$MAJOR' already up-to-date" + dependency:update: + desc: Update Azure CLI version and Alpine package pins + cmds: + - task: packages:update + - | + set -eu + az_latest="$(curl -LsS https://pypi.org/pypi/azure-cli/json | jq -r '.info.version')" + current='{{.AZ_VERSION}}' + + update_file() { + file="$1" + from="$2" + to="$3" + if [ "$from" = "$to" ]; then + return 0 fi + from_escaped="$(printf '%s\n' "$from" | sed 's/[][\\.^$*&/]/\\&/g')" + to_escaped="$(printf '%s\n' "$to" | sed 's/[&/\\]/\\&/g')" + {{.SED}} -i "s/${from_escaped}/${to_escaped}/g" "$file" + } + + set_arg() { + file="$1" + key="$2" + value="$3" + {{.SED}} -i "s|^ARG ${key}=.*$|ARG ${key}=${value}|g" "$file" + } + + echo "Current Azure CLI: $current | Latest: $az_latest" + set_arg Dockerfile AZ_VERSION "$az_latest" + update_file README.md "$current" "$az_latest" + + version_tag="" + [ "$current" = "$az_latest" ] || version_tag="$version_tag az-$az_latest" + if ! git diff --quiet -- alpine-packages.txt 2>/dev/null; then + version_tag="$version_tag alpine-packages" + fi + version_tag="$(echo "$version_tag" | sed 's/^ *//')" + version_branch="$(echo "$version_tag" | sed 's/ /-/g')" + if [ -n "${GITHUB_ENV:-}" ]; then + echo "VERSION_TAG=$version_tag" >> "$GITHUB_ENV" + echo "VERSION_BRANCH=$version_branch" >> "$GITHUB_ENV" + fi + + git:get-pr-template: + desc: Download pull request template + cmds: + - mkdir -p .tmp + - curl -LsS https://raw.githubusercontent.com/devops-infra/.github/refs/tags/v1/PULL_REQUEST_TEMPLATE.md -o .tmp/PULL_REQUEST_TEMPLATE.md + + git:set-config: + desc: Set git identity for automation + cmds: + - git config user.name "github-actions[bot]" + - git config user.email "github-actions[bot]@users.noreply.github.com" + + version:get: + desc: Get current Azure CLI version + cmds: + - echo "{{.VERSION_FULL}}" + + version:set: + desc: Validate version format + cmds: + - | + if [ -z "{{.VERSION}}" ]; then + echo "❌ ERROR: VERSION is empty" + exit 1 + fi + if ! printf '%s' "{{.VERSION}}" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "❌ ERROR: VERSION '{{.VERSION}}' is not a valid Azure CLI version (expected X.Y.Z)" + exit 1 fi diff --git a/Taskfile.variables.yml b/Taskfile.variables.yml index b84a5ca..f3397f4 100644 --- a/Taskfile.variables.yml +++ b/Taskfile.variables.yml @@ -3,6 +3,7 @@ version: '3' silent: true env: + DOCKER_BUILDKIT: '1' TERM: xterm-256color vars: @@ -13,7 +14,6 @@ vars: echo gsed else if command -v brew >/dev/null 2>&1; then - # Quietly ensure gnu-sed is installed brew list gnu-sed >/dev/null 2>&1 || { HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_ANALYTICS=1 brew update >/dev/null 2>&1 || true HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_ANALYTICS=1 brew install gnu-sed >/dev/null 2>&1 @@ -28,46 +28,73 @@ vars: fi PROJECT_DIR_NAME: sh: basename "$PWD" + DEFAULT_BRANCH: + sh: | + if [ -n "${DEFAULT_BRANCH:-}" ]; then + printf "%s\n" "${DEFAULT_BRANCH}" + elif branch_ref="$(git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null)"; then + printf "%s\n" "${branch_ref#origin/}" + elif current_branch="$(git symbolic-ref --quiet --short HEAD 2>/dev/null)" && { [ "${current_branch}" = "main" ] || [ "${current_branch}" = "master" ]; }; then + printf "%s\n" "${current_branch}" + elif git show-ref --verify --quiet refs/heads/main || git show-ref --verify --quiet refs/remotes/origin/main; then + echo main + elif git show-ref --verify --quiet refs/heads/master || git show-ref --verify --quiet refs/remotes/origin/master; then + echo master + else + echo master + fi + VERSION_PREFIX: + sh: echo "${VERSION_PREFIX:-}" VERSION_OVERRIDE: sh: echo "${VERSION_OVERRIDE:-}" + AZ_VERSION: + sh: sed -nE 's/^ARG AZ_VERSION=([0-9.]+)$/\1/p' Dockerfile | head -1 VERSION: sh: | - override="{{.VERSION_OVERRIDE}}" - if [ -n "$override" ]; then - echo "$override" + if [ -n "${VERSION_OVERRIDE:-}" ]; then + printf "%s" "${VERSION_OVERRIDE}" else - latest_tag="$(git tag --sort=-v:refname 2>/dev/null | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)" - if [ -n "$latest_tag" ]; then - echo "$latest_tag" - else - echo "v0.0.0" - fi + printf "%s" "{{.AZ_VERSION}}" fi - VERSION_NO_V: - sh: v="{{.VERSION}}"; echo "${v#v}" - MAJOR: - sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $1}' - MINOR: - sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $2}' - PATCH: - sh: echo "{{.VERSION_NO_V}}" | awk -F\. '{print $3}' - VERSION_MAJOR: v{{.MAJOR}} - VERSION_MINOR: v{{.MAJOR}}.{{.MINOR}} - VERSION_FULL: v{{.MAJOR}}.{{.MINOR}}.{{.PATCH}} - NEXT_PATCH: - sh: echo $(( {{.PATCH}} + 1 )) - NEXT_MINOR: - sh: echo $(( {{.MINOR}} + 1 )) - NEXT_MAJOR: - sh: echo $(( {{.MAJOR}} + 1 )) + VERSION_FULL: '{{.VERSION}}' + VERSION_MINOR: + sh: printf "%s" "{{.VERSION}}" | awk -F. '{print $1 "." $2}' + DOCKER_USERNAME: + sh: echo "${DOCKER_USERNAME:-christophshyper}" + DOCKER_ORG_NAME: + sh: echo "${DOCKER_ORG_NAME:-devopsinfra}" + DOCKER_IMAGE: docker-azure-cli + DOCKER_NAME: '{{.DOCKER_ORG_NAME}}/{{.DOCKER_IMAGE}}' + GITHUB_USERNAME: + sh: echo "${GITHUB_USERNAME:-ChristophShyper}" + GITHUB_ORG_NAME: + sh: echo "${GITHUB_ORG_NAME:-devops-infra}" + GITHUB_REPO: docker-azure-cli + GITHUB_NAME: ghcr.io/{{.GITHUB_ORG_NAME}}/{{.GITHUB_REPO}} + HOST_PLATFORM: + sh: | + case "$(uname -m)" in + x86_64) echo linux/amd64 ;; + aarch64|arm64) echo linux/arm64 ;; + *) echo linux/amd64 ;; + esac + PLATFORMS_LOCAL: '{{.PLATFORMS_LOCAL | default .HOST_PLATFORM}}' + PLATFORMS: '{{.PLATFORMS | default "linux/amd64,linux/arm64"}}' + BUILD_DATE: + sh: date -u +"%Y-%m-%dT%H:%M:%SZ" GIT_SHA: sh: git rev-parse HEAD 2>/dev/null || echo 0000000000000000000000000000000000000000 - GIT_SHORT_SHA: - sh: git rev-parse --short HEAD 2>/dev/null || echo 0000000 - GIT_BRANCH: - sh: | - if [ -n "${GITHUB_REF:-}" ]; then - echo "${GITHUB_REF#refs/heads/}" - else - git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown" - fi + LABEL_AUTHOR: Krzysztof ChristophShyper Szyper + LABEL_NAME: Alpine Azure CLI base image + LABEL_DESCRIPTION: Minimal Alpine-based Azure CLI image for downstream DevOps-Infra containers. + LABEL_REPO_URL: https://github.com/devops-infra/docker-azure-cli + LABEL_DOCS_URL: https://github.com/devops-infra/docker-azure-cli/blob/{{.DEFAULT_BRANCH}}/README.md + LABEL_HOMEPAGE: https://shyper.pro + LABEL_VENDOR: DevOps-Infra + LABEL_LICENSE: MIT + DOCKERFILE: Dockerfile + EFFECTIVE_PREFIX: + sh: printf "%s" "${VERSION_PREFIX:-}" + EXACT_TAG: '{{.EFFECTIVE_PREFIX}}{{.VERSION_FULL}}' + MINOR_TAG: '{{.EFFECTIVE_PREFIX}}{{.VERSION_MINOR}}' + LATEST_TAG: '{{.EFFECTIVE_PREFIX}}latest' diff --git a/Taskfile.yml b/Taskfile.yml index b2f1774..95c7932 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -8,6 +8,9 @@ dotenv: includes: variables: ./Taskfile.variables.yml scripts: ./Taskfile.scripts.yml + docker: + taskfile: ./Taskfile.docker.yml + flatten: true cicd: taskfile: ./Taskfile.cicd.yml flatten: true diff --git a/alpine-packages.txt b/alpine-packages.txt new file mode 100644 index 0000000..cdda7d2 --- /dev/null +++ b/alpine-packages.txt @@ -0,0 +1,6 @@ +bash~=5.3 +ca-certificates~=20260611-r0 +jq~=1.8 +libc6-compat~=1.1 +py3-pip~=26.1 +python3~=3.14 diff --git a/tests/azure.yml b/tests/azure.yml new file mode 100644 index 0000000..5566237 --- /dev/null +++ b/tests/azure.yml @@ -0,0 +1,68 @@ +schemaVersion: 2.0.0 + +commandTests: + - name: Alpine version + command: cat + args: [/etc/os-release] + expectedOutput: [VERSION_ID=3\.24] + + - name: Azure CLI exact version + command: bash + envVars: + - key: EXPECTED_AZ_VERSION + value: ${AZ_VERSION} + args: + - -lc + - test "$(az version --output json | jq -r '."azure-cli"')" = "${EXPECTED_AZ_VERSION}" + + - name: Runtime tools smoke tests + command: bash + args: + - -lc + - | + bash --version >/dev/null + jq --version >/dev/null + python --version >/dev/null + python3 --version >/dev/null + pip --version >/dev/null + pip3 --version >/dev/null + az --version >/dev/null + + - name: Unrelated CLIs are absent + command: bash + args: + - -lc + - | + for bin in aws gcloud terraform tofu terragrunt; do + ! command -v "$bin" >/dev/null 2>&1 || exit 1 + done + + - name: Temporary and APK cache cleaned + command: bash + args: + - -lc + - >- + test ! -f /tmp/alpine-packages.txt && + (test ! -d /var/cache/apk || test -z "$(find /var/cache/apk -mindepth 1 -maxdepth 1 2>/dev/null)") && + (test ! -d /root/.cache || test -z "$(find /root/.cache -mindepth 1 -maxdepth 1 2>/dev/null)") + +fileExistenceTests: + - name: az binary + path: /usr/bin/az + shouldExist: true + + - name: Python binary + path: /usr/bin/python3 + shouldExist: true + + - name: Python alias + path: /usr/bin/python + shouldExist: true + + - name: Pip binary + path: /usr/bin/pip3 + shouldExist: true + + - name: Pip alias + path: /usr/bin/pip + shouldExist: true