Merge pull request #1147 from docker/deps/regctl-version #111
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: update-deps | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: '0 9 * * *' | |
| push: | |
| branches: | |
| - 'main' | |
| jobs: | |
| update: | |
| runs-on: ubuntu-24.04 | |
| environment: update-deps # secrets are gated by this environment | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| dep: | |
| - docker | |
| - buildx | |
| - buildkit | |
| - compose | |
| - cosign | |
| - regctl | |
| - undock | |
| steps: | |
| - | |
| name: GitHub auth token from GitHub App | |
| id: write-app | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| client-id: ${{ vars.GHACTIONS_REPO_WRITE_CLIENT_ID }} | |
| private-key: ${{ secrets.GHACTIONS_REPO_WRITE_PRIVATE_KEY }} | |
| owner: docker | |
| repositories: actions-toolkit | |
| permission-contents: write | |
| permission-pull-requests: write | |
| permission-workflows: write | |
| - | |
| name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| token: ${{ steps.write-app.outputs.token }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - | |
| name: Update dependency | |
| id: update | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| INPUT_DEP: ${{ matrix.dep }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const dep = core.getInput('dep'); | |
| function escapeRegExp(value) { | |
| return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| } | |
| function formatList(values) { | |
| const quoted = values.map(value => `\`${value}\``); | |
| if (quoted.length === 1) { | |
| return quoted[0]; | |
| } | |
| if (quoted.length === 2) { | |
| return `${quoted[0]} and ${quoted[1]}`; | |
| } | |
| return `${quoted.slice(0, -1).join(', ')}, and ${quoted.at(-1)}`; | |
| } | |
| function unique(values) { | |
| return [...new Set(values)]; | |
| } | |
| function stripLeadingV(value) { | |
| return value.startsWith('v') ? value.slice(1) : value; | |
| } | |
| function stripDockerTag(value) { | |
| return value.replace(/^docker-v/, '').replace(/^v/, ''); | |
| } | |
| function majorMinor(value) { | |
| const match = value.match(/^(\d+\.\d+)/); | |
| if (!match) { | |
| throw new Error(`Unable to derive major.minor version from ${value}`); | |
| } | |
| return match[1]; | |
| } | |
| function readJson(relativePath) { | |
| const absolutePath = path.join(process.env.GITHUB_WORKSPACE, relativePath); | |
| return JSON.parse(fs.readFileSync(absolutePath, 'utf8')); | |
| } | |
| function readLatestTag(relativePath) { | |
| const tag = readJson(relativePath)?.latest?.tag_name; | |
| if (!tag) { | |
| throw new Error(`Unable to resolve latest tag from ${relativePath}`); | |
| } | |
| return tag; | |
| } | |
| function dockerfileArgPattern(key) { | |
| return new RegExp(`^(ARG ${escapeRegExp(key)}=)(.+)$`, 'm'); | |
| } | |
| function workflowEnvPattern(key) { | |
| return new RegExp(`^( ${escapeRegExp(key)}: ")([^"]*)(")$`, 'm'); | |
| } | |
| const dependencyConfigs = { | |
| docker: { | |
| name: 'Docker version', | |
| branch: 'deps/docker-version', | |
| sourceUrl: 'https://github.com/docker/actions-toolkit/blob/main/.github/docker-releases.json', | |
| async resolve() { | |
| const tag = readLatestTag('.github/docker-releases.json'); | |
| const version = majorMinor(stripDockerTag(tag)); | |
| return { | |
| titleValue: version, | |
| targets: [ | |
| { | |
| path: 'dev.Dockerfile', | |
| key: 'DOCKER_VERSION', | |
| value: version, | |
| pattern: dockerfileArgPattern('DOCKER_VERSION') | |
| } | |
| ] | |
| }; | |
| } | |
| }, | |
| buildx: { | |
| name: 'Buildx version', | |
| branch: 'deps/buildx-version', | |
| sourceUrl: 'https://github.com/docker/actions-toolkit/blob/main/.github/buildx-releases.json', | |
| async resolve() { | |
| const tag = readLatestTag('.github/buildx-releases.json'); | |
| return { | |
| titleValue: tag, | |
| targets: [ | |
| { | |
| path: 'dev.Dockerfile', | |
| key: 'BUILDX_VERSION', | |
| value: stripLeadingV(tag), | |
| pattern: dockerfileArgPattern('BUILDX_VERSION') | |
| }, | |
| { | |
| path: '.github/workflows/test.yml', | |
| key: 'BUILDX_VERSION', | |
| value: tag, | |
| pattern: workflowEnvPattern('BUILDX_VERSION') | |
| } | |
| ] | |
| }; | |
| } | |
| }, | |
| buildkit: { | |
| name: 'BuildKit image', | |
| branch: 'deps/buildkit-image', | |
| sourceUrl: 'https://github.com/moby/buildkit/releases/latest', | |
| async resolve({github}) { | |
| const release = await github.rest.repos.getLatestRelease({ | |
| owner: 'moby', | |
| repo: 'buildkit' | |
| }); | |
| const image = `moby/buildkit:${release.data.tag_name}`; | |
| return { | |
| titleValue: image, | |
| targets: [ | |
| { | |
| path: '.github/workflows/test.yml', | |
| key: 'BUILDKIT_IMAGE', | |
| value: image, | |
| pattern: workflowEnvPattern('BUILDKIT_IMAGE') | |
| } | |
| ] | |
| }; | |
| } | |
| }, | |
| compose: { | |
| name: 'Compose version', | |
| branch: 'deps/compose-version', | |
| sourceUrl: 'https://github.com/docker/actions-toolkit/blob/main/.github/compose-releases.json', | |
| async resolve() { | |
| const tag = readLatestTag('.github/compose-releases.json'); | |
| return { | |
| titleValue: tag, | |
| targets: [ | |
| { | |
| path: 'dev.Dockerfile', | |
| key: 'COMPOSE_VERSION', | |
| value: stripLeadingV(tag), | |
| pattern: dockerfileArgPattern('COMPOSE_VERSION') | |
| } | |
| ] | |
| }; | |
| } | |
| }, | |
| undock: { | |
| name: 'Undock version', | |
| branch: 'deps/undock-version', | |
| sourceUrl: 'https://github.com/docker/actions-toolkit/blob/main/.github/undock-releases.json', | |
| async resolve() { | |
| const tag = readLatestTag('.github/undock-releases.json'); | |
| return { | |
| titleValue: tag, | |
| targets: [ | |
| { | |
| path: 'dev.Dockerfile', | |
| key: 'UNDOCK_VERSION', | |
| value: stripLeadingV(tag), | |
| pattern: dockerfileArgPattern('UNDOCK_VERSION') | |
| } | |
| ] | |
| }; | |
| } | |
| }, | |
| regctl: { | |
| name: 'Regctl version', | |
| branch: 'deps/regctl-version', | |
| sourceUrl: 'https://github.com/docker/actions-toolkit/blob/main/.github/regclient-releases.json', | |
| async resolve() { | |
| const tag = readLatestTag('.github/regclient-releases.json'); | |
| return { | |
| titleValue: tag, | |
| targets: [ | |
| { | |
| path: 'dev.Dockerfile', | |
| key: 'REGCTL_VERSION', | |
| value: tag, | |
| pattern: dockerfileArgPattern('REGCTL_VERSION') | |
| } | |
| ] | |
| }; | |
| } | |
| }, | |
| cosign: { | |
| name: 'Cosign version', | |
| branch: 'deps/cosign-version', | |
| sourceUrl: 'https://github.com/docker/actions-toolkit/blob/main/.github/cosign-releases.json', | |
| async resolve() { | |
| const tag = readLatestTag('.github/cosign-releases.json'); | |
| return { | |
| titleValue: tag, | |
| targets: [ | |
| { | |
| path: 'dev.Dockerfile', | |
| key: 'COSIGN_VERSION', | |
| value: tag, | |
| pattern: dockerfileArgPattern('COSIGN_VERSION') | |
| } | |
| ] | |
| }; | |
| } | |
| } | |
| }; | |
| const config = dependencyConfigs[dep]; | |
| if (!config) { | |
| core.setFailed(`Unknown dependency ${dep}`); | |
| return; | |
| } | |
| const resolved = await config.resolve({github}); | |
| const currentValues = []; | |
| const changedFiles = []; | |
| for (const target of resolved.targets) { | |
| const absolutePath = path.join(process.env.GITHUB_WORKSPACE, target.path); | |
| const content = fs.readFileSync(absolutePath, 'utf8'); | |
| const match = content.match(target.pattern); | |
| if (!match) { | |
| throw new Error(`Missing ${target.key} in ${target.path}`); | |
| } | |
| currentValues.push(match[2]); | |
| if (match[2] === target.value) { | |
| continue; | |
| } | |
| const updatedContent = content.replace(target.pattern, (...args) => { | |
| const groups = args.slice(1, -2); | |
| const prefix = groups[0]; | |
| const suffix = groups[2] || ''; | |
| return `${prefix}${target.value}${suffix}`; | |
| }); | |
| fs.writeFileSync(absolutePath, updatedContent, 'utf8'); | |
| changedFiles.push(target.path); | |
| } | |
| core.info(`Resolved ${config.name} from ${config.sourceUrl}`); | |
| if (changedFiles.length === 0) { | |
| core.info(`No workspace changes needed for ${config.name}`); | |
| } else { | |
| core.info(`New ${config.name} ${resolved.titleValue} found`); | |
| } | |
| core.setOutput('branch', config.branch); | |
| core.setOutput('title', `chore(deps): update ${config.name} to ${resolved.titleValue}`); | |
| core.setOutput('before', formatList(unique(currentValues))); | |
| core.setOutput('files', formatList(unique(changedFiles))); | |
| core.setOutput('source-url', config.sourceUrl); | |
| - | |
| name: Create pull request | |
| uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 | |
| with: | |
| base: main | |
| branch: ${{ steps.update.outputs.branch }} | |
| token: ${{ steps.write-app.outputs.token }} | |
| commit-message: ${{ steps.update.outputs.title }} | |
| title: ${{ steps.update.outputs.title }} | |
| signoff: true | |
| sign-commits: true | |
| delete-branch: true | |
| body: | | |
| This updates the pinned value from ${{ steps.update.outputs.before }} in ${{ steps.update.outputs.files }}. | |
| The source of truth for this update is ${{ steps.update.outputs.source-url }}. |