diff --git a/.github/workflows/docker-dependency-updater.yml b/.github/workflows/docker-dependency-updater.yml index dda4a173..951f5483 100644 --- a/.github/workflows/docker-dependency-updater.yml +++ b/.github/workflows/docker-dependency-updater.yml @@ -3,12 +3,12 @@ name: udx-automation / dependency upgrade "on": schedule: - - cron: "0 5 * * 1" + - cron: "0 5 * * *" workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + group: ${{ github.workflow }} + cancel-in-progress: false permissions: contents: read @@ -84,7 +84,7 @@ jobs: test -n "${PROBE_DOCKERFILE}" test -f "${DOCKERFILE}" for prompt_part in ${COPILOT_PROMPT_PARTS}; do - test -f "${prompt_part}" + test -s "${prompt_part}" done echo "Dependency updater defaults loaded for ${DOCKERFILE}" @@ -145,7 +145,6 @@ jobs: permissions: contents: write - issues: write pull-requests: write steps: @@ -216,11 +215,41 @@ jobs: base_image="$(awk '$1 == "FROM" { print $2; exit }' "${DOCKERFILE}")" + non_apt_path="${probe_dir}/non-apt.json" + node <<'NODE' > "${non_apt_path}" + const fs = require("node:fs"); + + const dockerfile = fs.readFileSync(process.env.DOCKERFILE, "utf8"); + const pins = []; + const urls = []; + + for (const line of dockerfile.split(/\r?\n/)) { + const argMatch = line.match(/^ARG\s+([A-Za-z_][A-Za-z0-9_]*)=(.+)$/); + if (argMatch) { + pins.push({ + kind: "ARG", + name: argMatch[1], + value: argMatch[2].replace(/^["']|["']$/g, "") + }); + } + + for (const urlMatch of line.matchAll(/https?:\/\/[^"'\s]+/g)) { + urls.push(urlMatch[0]); + } + } + + fs.writeFileSync( + process.stdout.fd, + JSON.stringify({method: "Dockerfile ARG and URL inventory", pins, urls}, null, 2) + ); + NODE + jq -n \ --arg generated_at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --arg base_image "${base_image}" \ --arg dockerfile "${DOCKERFILE}" \ --arg probe_dockerfile "${PROBE_DOCKERFILE}" \ + --slurpfile non_apt "${non_apt_path}" \ --rawfile apt_versions "${apt_versions_path}" \ '{ generated_at: $generated_at, @@ -237,7 +266,8 @@ jobs: | split("\n") | map(select(length > 0)) | map(split("\t") | {name: .[0], installed: .[1]}) - ) + ), + non_apt: $non_apt[0] } }' > "${PROBE_REPORT}" @@ -273,15 +303,17 @@ jobs: - name: Update Dockerfile with Copilot CLI timeout-minutes: 8 - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_ALLOW_ALL: "true" - COPILOT_AUTO_UPDATE: "false" - PROMPT_PARTS: ${{ needs.config.outputs.prompt_parts }} - REPORT_PATH: ${{ needs.config.outputs.report_path }} run: | set -euo pipefail + test -n "${DOCKERFILE}" + test -n "${REPORT_PATH}" + test -n "${PROMPT_PARTS}" + test -f "${DOCKERFILE}" + test -s "${REPORT_PATH}" + jq -e '.dependencies.apt and .dependencies.non_apt' \ + "${REPORT_PATH}" >/dev/null + if [ -z "${COPILOT_GITHUB_TOKEN:-}" ]; then echo "COPILOT_GITHUB_TOKEN secret is required. Use a fine-grained token with Copilot Requests permission." >&2 exit 1 @@ -291,30 +323,61 @@ jobs: mkdir -p "$(dirname "${prompt_path}")" : > "${prompt_path}" for prompt_part in ${PROMPT_PARTS}; do - test -f "${prompt_part}" + test -s "${prompt_part}" { cat "${prompt_part}" echo } >> "${prompt_path}" done + test -s "${prompt_path}" + + mapfile -t copilot_urls < <( + jq -r '.dependencies.non_apt.urls[]?' "${REPORT_PATH}" \ + | sed -E 's#^https?://([^/]+).*$#\1#' \ + | sort -u + ) + if [ "${#copilot_urls[@]}" -eq 0 ]; then + echo "Copilot requires at least one Dockerfile-owned non-apt source URL." >&2 + exit 1 + fi - copilot \ - --prompt "$(cat "${prompt_path}")" \ - --allow-all-tools \ - --allow-all-urls \ - --no-ask-user \ - --no-auto-update \ - --silent \ + copilot_args=( + --prompt "$(cat "${prompt_path}")" + --available-tools "apply_patch,edit,view,grep,glob,web_fetch" + --allow-tool "read" + --allow-tool "write(${DOCKERFILE})" + --secret-env-vars "COPILOT_GITHUB_TOKEN" + --no-ask-user + --no-auto-update + --silent --share copilot-docker-dependency-session.md + ) + for copilot_url in "${copilot_urls[@]}"; do + copilot_args+=(--allow-url "${copilot_url}") + done + + copilot "${copilot_args[@]}" echo "Upgrade Copilot: completed; session saved to copilot-docker-dependency-session.md" + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_AUTO_UPDATE: "false" + DOCKERFILE: ${{ needs.config.outputs.dockerfile }} + PROMPT_PARTS: ${{ needs.config.outputs.prompt_parts }} + REPORT_PATH: ${{ needs.config.outputs.report_path }} - name: Guard Dockerfile-only changes timeout-minutes: 1 run: | set -euo pipefail - unexpected_changes="$(git diff --name-only | awk -v dockerfile="${DOCKERFILE}" '$0 != dockerfile')" + changed_files="$( + { + git diff --name-only + git diff --cached --name-only + } | sort -u + )" + unexpected_changes="$(printf '%s\n' "${changed_files}" | awk -v dockerfile="${DOCKERFILE}" 'NF && $0 != dockerfile')" if [ -n "${unexpected_changes}" ]; then echo "Upgrade guard: Copilot modified tracked files outside ${DOCKERFILE}:" >&2 printf '%s\n' "${unexpected_changes}" >&2 @@ -330,12 +393,16 @@ jobs: shell: bash run: | set -euo pipefail - if git diff --quiet -- "${DOCKERFILE}"; then + if git diff --quiet -- "${DOCKERFILE}" \ + && git diff --cached --quiet -- "${DOCKERFILE}"; then echo "changed=false" >> "${GITHUB_OUTPUT}" echo "Upgrade changes: no ${DOCKERFILE} changes" else echo "changed=true" >> "${GITHUB_OUTPUT}" - git diff -- "${DOCKERFILE}" > docker-dependency-update.diff + { + git diff -- "${DOCKERFILE}" + git diff --cached -- "${DOCKERFILE}" + } > docker-dependency-update.diff changed_lines="$(wc -l < docker-dependency-update.diff | tr -d ' ')" echo "Upgrade changes: ${DOCKERFILE} changed; diff has ${changed_lines} lines" fi diff --git a/.rabbit/README.md b/.rabbit/README.md new file mode 100644 index 00000000..47480649 --- /dev/null +++ b/.rabbit/README.md @@ -0,0 +1,20 @@ +# Rabbit CI repository configuration + +[`repo.yaml`](./repo.yaml) is generated by `rabbit.ci`. It records the +repository's observable GitHub delivery shape: branch rules, GitHub +Environments, workflow triggers, and configured secret and variable names. It +does not include secret or variable values. + +Regenerate it after changing a GitHub workflow, branch protection, Environment, +or configured secret or variable name: + +```bash +rabbit.ci +``` + +Review and commit the resulting diff with the source change. Do not edit +`repo.yaml` manually. + +For the Docker image build and release path, see +[Development](../docs/development.md). For the scheduled Docker dependency +updater, see [Dependency updates](../README.md#dependency-updates). diff --git a/.rabbit/context.yaml b/.rabbit/context.yaml deleted file mode 100644 index 6c7fe7e7..00000000 --- a/.rabbit/context.yaml +++ /dev/null @@ -1,101 +0,0 @@ -# Generated by dev.kit repo — do not edit manually. -# Run `dev.kit repo` to refresh. -kind: repoContext -version: udx.dev/dev.kit/v1 -generator: - tool: dev.kit - repo: https://github.com/udx/dev.kit - version: 0.13.0 - generated_at: 2026-06-15T11:33:01Z - sources: - homepage: https://udx.dev/kit - repository: https://github.com/udx/dev.kit - package: https://www.npmjs.com/package/@udx/dev-kit - installation: https://github.com/udx/dev.kit/blob/latest/docs/installation.md - -repo: - name: worker - archetype: manifest-repo - -# Refs — Direct-read files and paths that define the repo contract. -# Note: Include only files or directories a repo consumer should read before code exploration. -# Note: Prefer README, focused docs, workflows, manifests, and explicit operational files. -# Note: Exclude broad implementation directories unless they are the contract themselves. - -refs: - - ./README.md - - ./Makefile - - ./docs/child-images.md - - ./docs/core-image.md - - ./src/configs/services.yaml - - ./src/configs/worker.yaml - - ./.github/workflows - - ./Dockerfile - - ./docs - -# Commands — Canonical repo entrypoints detected from strong repo signals. -# Note: Prefer declared make targets and package scripts before regex matches in docs. -# Note: Emit only commands that can be traced to a concrete source. -# Note: Record the source path so the command can be reviewed and corrected. - -commands: - verify: - run: make test - source: Makefile - build: - run: make build - source: Makefile - run: - run: make run - source: Makefile - -# Dependencies — Meaningful dependency-repo contracts such as reusable workflows, images, or versioned manifests this repo relies on. -# Note: Capture execution-shaping behavior defined outside the current checkout. -# Note: Avoid promoting standard package inventory or ordinary GitHub action refs into top-level context. -# Note: Normalize same-org versioned refs into repo slugs when possible. - -dependencies: - - repo: udx/reusable-workflows - kind: reusable workflow - resolved: true - archetype: workflow-repo - description: Reusable GitHub Actions workflow templates for CI/CD - used_by: - - .github/workflows/context7_sync.yml - - .github/workflows/docker-ops.yml - - repo: ubuntu:25.10 - kind: base image - resolved: false - used_by: - - Dockerfile - -# Manifests — YAML files that define repo-specific workflow, deploy, or contract behavior. -# Note: Include custom config/manifests that materially shape repo behavior or contract understanding. -# Note: Do not include workflow YAML only because it lives under .github/workflows. -# Note: Promote workflow files only when they declare reusable workflow refs or other repo-specific execution contracts. -# Note: Prefer structured kind and description metadata from the manifest itself. -# Note: Include hidden or nested contract dirs when they contain repo-owned manifests with meaningful metadata. - -manifests: - - path: .github/workflows/context7_sync.yml - kind: githubWorkflow - - path: .github/workflows/docker-ops.yml - kind: githubWorkflow - - path: src/configs/services.yaml - kind: workerService - declared_as: udx.io/worker-v1/service - source_repo: udx/worker - used_by: - - Dockerfile - evidence: - - version: udx.io/worker-v1/service - - path reference: Dockerfile - - path: src/configs/worker.yaml - kind: workerConfig - declared_as: udx.io/worker-v1/config - source_repo: udx/worker - used_by: - - Dockerfile - evidence: - - version: udx.io/worker-v1/config - - path reference: Dockerfile diff --git a/.rabbit/repo.yaml b/.rabbit/repo.yaml new file mode 100644 index 00000000..3c621811 --- /dev/null +++ b/.rabbit/repo.yaml @@ -0,0 +1,99 @@ +kind: repo +version: udx.dev/rabbit.ci/repo/v1 +repository: + name: worker + owner: udx + default_branch: latest +branches: + - name: ci/daily-docker-dependency-upgrades + rules: {} + - name: latest + rules: + allow_deletions: false + allow_force_pushes: false + pull_request: + approvals: 1 + code_owner_review: true + stale_reviews: true + last_push_approval: false + conversation_resolution: true + merge_methods: + - merge + - squash + - rebase + copilot_review: + on_push: true + drafts: false +configuration: + secrets: + organization: + - BITBUCKET_TOKEN + - CONTEXT7_API_KEY + - COPILOT_GITHUB_TOKEN + - DEPENDABOT_REVIEWER_TOKEN + repository: + - AZURE_CREDS + - DOCKER_TOKEN + - GH_TOKEN + variables: + organization: + - DOCKER_LOGIN + - DOCKER_ORG + - GCP_PROJECT_ID + - GCP_REGION + - GCP_WORKLOAD_IDENTITY_PROVIDER + - GH_SERVICE_ACCOUNT_EMAIL + repository: + - AZURE_APPLICATION_ID + - AZURE_SUBSCRIPTION_ID + - AZURE_TENANT_ID + - DOCKER_IMAGE_NAME + - GCP_REGISTRY_REPO +environments: + - name: copilot + branches: + - "*" + approvals: [] + wait_minutes: 0 + admin_bypass: true + secrets: [] + variables: [] +workflows: + - path: .github/workflows/codeql.yml + triggers: + push: {} + permissions: + contents: read + - path: .github/workflows/context7_sync.yml + triggers: + push: + branches: + - latest + workflow_dispatch: {} + permissions: + contents: write + - path: .github/workflows/docker-dependency-updater.yml + triggers: + schedule: + - cron: 0 5 * * * + workflow_dispatch: {} + permissions: + contents: read + - path: .github/workflows/docker-ops.yml + triggers: + push: + branches: + - "**" + paths: + - .github/workflows/docker-ops.yml + - .dockerignore + - Dockerfile + - bin/** + - lib/** + - src/** + - etc/** + - test/** + - Makefile + - Makefile.variables + - ci/** + workflow_dispatch: {} diff --git a/README.md b/README.md index 73c19d55..7920b6ce 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ More examples available in [src/examples/README.md](src/examples/README.md). - [Deployment](docs/deployment.md) - Docker, Kubernetes, and CI usage - [Runtime Output](docs/runtime-output.md) - redacted runtime contract capture - [Development](docs/development.md) - Build, test, and child image workflow +- [Rabbit CI repository configuration](.rabbit/README.md) - generated GitHub delivery metadata - [Reference Docs](docs/references/README.md) - provider auth options and container structure - [Examples](src/examples/README.md) - Runnable samples @@ -156,6 +157,13 @@ make test make help ``` +## Dependency updates + +The Docker dependency updater runs daily at 05:00 UTC and can also be started +manually from GitHub Actions. It probes current APT versions, inventories +Dockerfile ARG and URL pins, and opens a Dockerfile-only pull request when an +update is available. An open updater pull request prevents duplicate runs. + ## Resources - [Docker Hub](https://hub.docker.com/r/usabilitydynamics/udx-worker)