diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 184da5b8f136..b33c305e5345 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,10 +9,3 @@ updates: - "status/2-code-review" cooldown: default-days: 7 - groups: - codeql-actions: - patterns: - - "github/codeql-action/*" - docker-actions: - patterns: - - "docker/*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ebe878131e3..212c548d4ce1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,10 +65,10 @@ jobs: steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Build - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 with: targets: ${{ matrix.target }} set: | @@ -104,20 +104,20 @@ jobs: - name: Login to DockerHub if: github.event_name != 'pull_request' - uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0 with: username: ${{ secrets.DOCKERHUB_CLIBIN_USERNAME }} password: ${{ secrets.DOCKERHUB_CLIBIN_TOKEN }} - name: Set up QEMU - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Docker meta id: meta - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0 with: images: dockereng/cli-bin tags: | @@ -128,7 +128,7 @@ jobs: type=semver,pattern={{major}}.{{minor}} - name: Build and push image - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 with: files: | ./docker-bake.hcl @@ -170,10 +170,10 @@ jobs: steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Build - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 with: targets: plugins-cross set: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 29738a53ff69..eb03bd5ca1a3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -62,20 +62,20 @@ jobs: ln -s vendor.sum go.sum - name: Update Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: "1.26.5" + go-version: "1.26.4" cache: false - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: languages: go - name: Autobuild - uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 with: category: "/language:go" diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 9fea79cae96a..9de7fb2b120e 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -65,7 +65,7 @@ jobs: docker info - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Run ${{ matrix.target }} run: | diff --git a/.github/workflows/pr-review-trigger.yml b/.github/workflows/pr-review-trigger.yml index 1ecc03b098dc..c176020256e5 100644 --- a/.github/workflows/pr-review-trigger.yml +++ b/.github/workflows/pr-review-trigger.yml @@ -7,17 +7,8 @@ on: permissions: {} -# Deduplicate simultaneous pull_request events for the same fork PR. -# When reviewers are requested at the same time, GitHub fires multiple -# review_requested events. Without this group each event triggers a -# separate review via workflow_run, producing duplicate reviews. -concurrency: - group: pr-review-trigger-${{ github.event.pull_request.number }} - cancel-in-progress: true - jobs: save-context: - if: github.event.pull_request.head.repo.fork runs-on: ubuntu-latest steps: - name: Save event context diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index 1f459fc27335..d2e6a3585541 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -11,7 +11,7 @@ permissions: jobs: review: - uses: docker/docker-agent-action/.github/workflows/review-pr.yml@774b6e0e60d6c648b0f2dc43bd5221377a0a7420 # v2.0.2 + uses: docker/docker-agent-action/.github/workflows/review-pr.yml@e96a4bb40cac114f64358621e1d08346c8eadc8c # v2.0.1 permissions: contents: read # Read repository files and PR diffs pull-requests: write # Post review comments diff --git a/.github/workflows/sync-release-branch.yml b/.github/workflows/sync-release-branch.yml deleted file mode 100644 index c5b5775b7da3..000000000000 --- a/.github/workflows/sync-release-branch.yml +++ /dev/null @@ -1,157 +0,0 @@ -name: Sync Docker release branch - -concurrency: - group: ${{ github.workflow }}-${{ inputs.release_branch }} - cancel-in-progress: false - -permissions: - contents: read - -on: - workflow_dispatch: - inputs: - release_branch: - description: Release branch to sync, for example 29.x - required: true - type: string - tag: - description: Tag to sync from, for example v29.6.0 - required: true - type: string - dry_run: - description: Merge but don't push - required: true - default: false - type: boolean - -jobs: - sync-release-branch: - runs-on: ubuntu-24.04 - permissions: - contents: write - actions: write - outputs: - base_sha: ${{ steps.sync.outputs.base_sha }} - has_changes: ${{ steps.sync.outputs.has_changes }} - temporary_branch: ${{ steps.sync.outputs.temporary_branch }} - temporary_sha: ${{ steps.sync.outputs.temporary_sha }} - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - - - name: Validate - env: - BRANCH: ${{ github.ref_name }} - RELEASE_BRANCH: ${{ inputs.release_branch }} - run: | - if [ "$BRANCH" != "master" ]; then - echo "::error::This workflow is expected to be run on master, not $BRANCH" - exit 1 - fi - if [ "$RELEASE_BRANCH" = "master" ]; then - echo "::error::The release branch must not be master" - exit 1 - fi - if ! [[ "$RELEASE_BRANCH" =~ ^[0-9]+\.[x0-9]+$ ]]; then - echo "::error::Invalid release branch name: '$RELEASE_BRANCH'. Expected format: 29.x" - exit 1 - fi - - - name: Configure git author - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Sync release branch to tag range - id: sync - env: - DRY_RUN: ${{ inputs.dry_run }} - RELEASE_BRANCH: ${{ inputs.release_branch }} - RUN_ATTEMPT: ${{ github.run_attempt }} - RUN_ID: ${{ github.run_id }} - TAG: ${{ inputs.tag }} - run: | - set -o pipefail - base_sha=$(git rev-parse "origin/$RELEASE_BRANCH") - temporary_branch="process/sync-release-branch/$RUN_ID-$RUN_ATTEMPT" - echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT" - echo "temporary_branch=$temporary_branch" >> "$GITHUB_OUTPUT" - - # Keep the master checkout unchanged so scripts run from the dispatched revision. - release_worktree="$RUNNER_TEMP/release-branch" - git worktree add --detach "$release_worktree" "$base_sha" - cd "$release_worktree" - - tags_file=$(mktemp) - "$GITHUB_WORKSPACE/scripts/unmerged-tags" \ - "origin/$RELEASE_BRANCH" \ - "$TAG" \ - > "$tags_file" - - echo >> "$GITHUB_STEP_SUMMARY" - echo "## Tags to sync" >> "$GITHUB_STEP_SUMMARY" - echo >> "$GITHUB_STEP_SUMMARY" - sed 's/^/- /' "$tags_file" >> "$GITHUB_STEP_SUMMARY" - - xargs -r "$GITHUB_WORKSPACE/scripts/sync-branch" < "$tags_file" | tee -a "$GITHUB_STEP_SUMMARY" - - if [[ "$DRY_RUN" == "true" ]]; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - if [[ $(git rev-parse HEAD) == $(git rev-parse "origin/$RELEASE_BRANCH") ]]; then - echo "has_changes=false" >> "$GITHUB_OUTPUT" - echo "No changes to push" - exit 0 - fi - - echo "has_changes=true" >> "$GITHUB_OUTPUT" - git push origin "HEAD:refs/heads/$temporary_branch" - echo "temporary_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - push-release-branch: - needs: sync-release-branch - if: ${{ !inputs.dry_run && needs.sync-release-branch.outputs.has_changes == 'true' }} - runs-on: ubuntu-24.04 - environment: docker-releases - permissions: - contents: write - actions: write - timeout-minutes: 10 - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - - - name: Push release branch - env: - BASE_SHA: ${{ needs.sync-release-branch.outputs.base_sha }} - RELEASE_BRANCH: ${{ inputs.release_branch }} - TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }} - TEMPORARY_SHA: ${{ needs.sync-release-branch.outputs.temporary_sha }} - run: | - git fetch origin "$RELEASE_BRANCH" - current_sha=$(git rev-parse "origin/$RELEASE_BRANCH") - if [[ "$current_sha" != "$BASE_SHA" ]]; then - echo "$RELEASE_BRANCH changed from $BASE_SHA to $current_sha" - exit 1 - fi - - git fetch origin "$TEMPORARY_BRANCH" - current_temporary_sha=$(git rev-parse FETCH_HEAD) - if [[ "$current_temporary_sha" != "$TEMPORARY_SHA" ]]; then - echo "$TEMPORARY_BRANCH changed from $TEMPORARY_SHA to $current_temporary_sha" - exit 1 - fi - - git push origin "FETCH_HEAD:$RELEASE_BRANCH" - - - name: Delete temporary branch - env: - TEMPORARY_BRANCH: ${{ needs.sync-release-branch.outputs.temporary_branch }} - run: git push origin --delete "$TEMPORARY_BRANCH" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8b51f7e8d916..85959f2bb0f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,10 +30,10 @@ jobs: steps: - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0 - name: Test - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 with: targets: test-coverage - @@ -66,9 +66,9 @@ jobs: persist-credentials: false - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: "1.26.5" + go-version: "1.26.4" cache: false - name: Test diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ca0e06745cae..e1d8b4ab3406 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -38,7 +38,7 @@ jobs: steps: - name: Run - uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7.3.0 + uses: docker/bake-action@6614cfa25eff9a0b2b2697efb0b6159e7680d584 # v7.2.0 with: targets: ${{ matrix.target }} @@ -99,9 +99,9 @@ jobs: persist-credentials: false - name: Set up Go - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: - go-version: "1.26.5" + go-version: "1.26.4" cache: false - name: Run gocompat check diff --git a/.golangci.yml b/.golangci.yml index 6aec71dbb74e..dfb592403cc0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -5,7 +5,7 @@ run: # which causes it to fallback to go1.17 semantics. # # TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24 - go: "1.26.5" + go: "1.26.4" timeout: 5m diff --git a/Dockerfile b/Dockerfile index 2a71bf8889e8..ccde94ee6df7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine ARG ALPINE_VERSION=3.23 ARG BASE_DEBIAN_DISTRO=bookworm -ARG GO_VERSION=1.26.5 +ARG GO_VERSION=1.26.4 # XX_VERSION specifies the version of the xx utility to use. # It must be a valid tag in the docker.io/tonistiigi/xx image repository. diff --git a/README.md b/README.md index 512eb9ab0f50..1183834102b4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![PkgGoDev](https://pkg.go.dev/badge/github.com/docker/cli)](https://pkg.go.dev/github.com/docker/cli) [![Build Status](https://img.shields.io/github/actions/workflow/status/docker/cli/build.yml?branch=master&label=build&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Abuild) [![Test Status](https://img.shields.io/github/actions/workflow/status/docker/cli/test.yml?branch=master&label=test&logo=github)](https://github.com/docker/cli/actions?query=workflow%3Atest) +[![Go Report Card](https://goreportcard.com/badge/github.com/docker/cli)](https://goreportcard.com/report/github.com/docker/cli) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/docker/cli/badge)](https://scorecard.dev/viewer/?uri=github.com/docker/cli) [![Codecov](https://img.shields.io/codecov/c/github/docker/cli?logo=codecov)](https://codecov.io/gh/docker/cli) diff --git a/VERSION b/VERSION index bfa22ee30d79..d73ed40a839d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -29.7.0 +29.6.1 diff --git a/cmd/docker-trust/go.mod b/cmd/docker-trust/go.mod index e29e424805e8..9ea8a225a193 100644 --- a/cmd/docker-trust/go.mod +++ b/cmd/docker-trust/go.mod @@ -65,11 +65,11 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - golang.org/x/crypto v0.52.0 // indirect - golang.org/x/net v0.54.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/term v0.43.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect diff --git a/cmd/docker-trust/go.sum b/cmd/docker-trust/go.sum index cbf1eca9b8f8..8630a983c978 100644 --- a/cmd/docker-trust/go.sum +++ b/cmd/docker-trust/go.sum @@ -265,14 +265,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= -golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -285,14 +285,14 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= diff --git a/docker.Makefile b/docker.Makefile index 75a7dca34e79..ce0cbd533d6c 100644 --- a/docker.Makefile +++ b/docker.Makefile @@ -8,7 +8,6 @@ DOCKER_CLI_MOUNTS ?= -v "$(CURDIR)":/go/src/github.com/docker/cli DOCKER_CLI_CONTAINER_NAME ?= DOCKER_CLI_GO_BUILD_CACHE ?= y -DOCKER_SOCK ?= $(or $(patsubst unix://%,%,$(filter unix://%,$(shell docker context inspect --format '{{.Endpoints.docker.Host}}'))),/var/run/docker.sock) # Sets the name of the company that produced the windows binary. PACKAGER_NAME ?= @@ -63,7 +62,7 @@ dynbinary: ## build dynamically linked binary .PHONY: dev dev: build_docker_image ## start a build container in interactive mode for in-container development $(DOCKER_RUN) -it \ - --mount 'type=bind,src=$(DOCKER_SOCK),dst=/var/run/docker.sock' \ + --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ $(DEV_DOCKER_IMAGE_NAME) shell: dev ## alias for dev @@ -135,14 +134,14 @@ test-e2e: test-e2e-local test-e2e-connhelper-ssh ## run all e2e tests test-e2e-local: build-e2e-image # run experimental e2e tests docker run --rm $(ENVVARS) \ --mount type=bind,src=$(CURDIR)/build/coverage,dst=/tmp/coverage \ - --mount 'type=bind,src=$(DOCKER_SOCK),dst=/var/run/docker.sock' \ + --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ $(E2E_IMAGE_NAME) .PHONY: test-e2e-connhelper-ssh test-e2e-connhelper-ssh: build-e2e-image # run experimental SSH-connection helper e2e tests docker run --rm $(ENVVARS) -e TEST_CONNHELPER=ssh \ --mount type=bind,src=$(CURDIR)/build/coverage,dst=/tmp/coverage \ - --mount 'type=bind,src=$(DOCKER_SOCK),dst=/var/run/docker.sock' \ + --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \ $(E2E_IMAGE_NAME) .PHONY: help diff --git a/dockerfiles/Dockerfile.dev b/dockerfiles/Dockerfile.dev index b40cbb287c11..c4a9a39b1ba5 100644 --- a/dockerfiles/Dockerfile.dev +++ b/dockerfiles/Dockerfile.dev @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.26.5 +ARG GO_VERSION=1.26.4 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.lint b/dockerfiles/Dockerfile.lint index 4f8e1b46b380..10cc37266258 100644 --- a/dockerfiles/Dockerfile.lint +++ b/dockerfiles/Dockerfile.lint @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.26.5 +ARG GO_VERSION=1.26.4 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/dockerfiles/Dockerfile.vendor b/dockerfiles/Dockerfile.vendor index df06caaaf3ed..9c2f91a323b1 100644 --- a/dockerfiles/Dockerfile.vendor +++ b/dockerfiles/Dockerfile.vendor @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -ARG GO_VERSION=1.26.5 +ARG GO_VERSION=1.26.4 # ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image. # It must be a supported tag in the docker.io/library/alpine image repository diff --git a/docs/reference/commandline/network_create.md b/docs/reference/commandline/network_create.md index ac493a844e10..55c9115c132f 100644 --- a/docs/reference/commandline/network_create.md +++ b/docs/reference/commandline/network_create.md @@ -149,9 +149,10 @@ fails and Docker Engine returns an error. ### Bridge driver options -When creating a custom `bridge` network, the following additional options can -be passed. Some of these have equivalent flags that can be used on the dockerd -command line or in `daemon.json` to configure the default bridge, `docker0`: +When creating a custom `bridge` network, you can configure driver options using +the `--opt` flag. Some of these options have equivalent flags that can be used +on the dockerd command line or in `daemon.json` to configure the default bridge, +`docker0`: | Network create option | Daemon option for `docker0` | Description | |--------------------------------------------------|-----------------------------|-------------------------------------------------------| @@ -162,6 +163,15 @@ command line or in `daemon.json` to configure the default bridge, `docker0`: | `com.docker.network.driver.mtu` | `--mtu` | Set the containers network MTU | | `com.docker.network.container_iface_prefix` | - | Set a custom prefix for container interfaces | +For example, to create a custom `bridge` network with IP masquerading disabled: + +```console +$ docker network create \ + --driver bridge \ + --opt com.docker.network.bridge.enable_ip_masquerade=false \ + no-masq-network +``` + The following arguments can be passed to `docker network create` for any network driver, again with their approximate equivalents to Docker daemon flags used for the `docker0` bridge: diff --git a/e2e/.gitignore b/e2e/.gitignore deleted file mode 100644 index 262c637b7361..000000000000 --- a/e2e/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Generated by gen-certs.sh at setup time -testdata/registry/certs/ca.crt -testdata/registry/certs/ca.key -testdata/registry/certs/tlsregistry.crt -testdata/registry/certs/tlsregistry.key diff --git a/e2e/compose-env.yaml b/e2e/compose-env.yaml index c4f3774319ff..651d5d145aee 100644 --- a/e2e/compose-env.yaml +++ b/e2e/compose-env.yaml @@ -3,35 +3,9 @@ services: registry: image: 'registry:3' - privateregistry: - build: - context: ./testdata/registry - environment: - - REGISTRY_HTTP_ADDR=0.0.0.0:5001 - - REGISTRY_HTTP_DEBUG_ADDR=0.0.0.0:5002 - - REGISTRY_AUTH=htpasswd - - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm - - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd - - tlsregistry: - build: - context: ./testdata/registry - environment: - - REGISTRY_HTTP_ADDR=0.0.0.0:5003 - - REGISTRY_HTTP_DEBUG_ADDR=0.0.0.0:5004 - - REGISTRY_AUTH=htpasswd - - REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm - - REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd - - REGISTRY_HTTP_TLS_CERTIFICATE=/certs/tlsregistry.crt - - REGISTRY_HTTP_TLS_KEY=/certs/tlsregistry.key - engine: - build: - context: ./testdata - dockerfile: engine/Dockerfile - args: - ENGINE_VERSION: ${ENGINE_VERSION:-29} + image: 'docker:${ENGINE_VERSION:-29}-dind' privileged: true - command: ['--insecure-registry=registry:5000', '--insecure-registry=privateregistry:5001', '--experimental'] + command: ['--insecure-registry=registry:5000', '--experimental'] environment: - DOCKER_TLS_CERTDIR= diff --git a/e2e/container/run_test.go b/e2e/container/run_test.go index 574a203603b9..6b35127b47a3 100644 --- a/e2e/container/run_test.go +++ b/e2e/container/run_test.go @@ -209,11 +209,7 @@ func TestProcessTermination(t *testing.T) { assert.NilError(t, result.Cmd.Process.Signal(syscall.SIGTERM)) - // Use a generous timeout (20s) because when run through SSH connhelper, - // the Docker engine may take longer to close the attach stream after - // the container exits. This is a known timing difference across engine - // versions (e.g. engine 25 over SSH connhelper). - icmd.WaitOnCmd(time.Second*20, result).Assert(t, icmd.Expected{ + icmd.WaitOnCmd(time.Second*10, result).Assert(t, icmd.Expected{ ExitCode: 0, }) } diff --git a/e2e/image/private_test.go b/e2e/image/private_test.go deleted file mode 100644 index 351a1983cf6f..000000000000 --- a/e2e/image/private_test.go +++ /dev/null @@ -1,126 +0,0 @@ -package image - -import ( - "strings" - "testing" - "time" - - "github.com/docker/cli/e2e/internal/fixtures" - "gotest.tools/v3/assert" - "gotest.tools/v3/icmd" -) - -// Regression test for https://github.com/docker/cli/issues/5963 -func TestPullPushPrivateRepository(t *testing.T) { - t.Parallel() - - for _, tc := range []struct { - name string - registryPrefix string - tagSuffix string - }{ - {name: "insecure", registryPrefix: "privateregistry:5001", tagSuffix: "private"}, - {name: "tls", registryPrefix: "tlsregistry:5003", tagSuffix: "tls"}, - } { - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - dir := fixtures.SetupConfigFile(t) - t.Cleanup(dir.Remove) - emptyConfigDir := t.TempDir() - - sourceImage := fixtures.AlpineImage - privateImage := tc.registryPrefix + "/private/alpine:test-" + tc.tagSuffix + "-pull-push" - - runWithPrivateRegistryRetry(t, - icmd.Command("docker", "pull", sourceImage), - ).Assert(t, icmd.Success) - t.Cleanup(func() { - icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success) - }) - - icmd.RunCommand("docker", "tag", sourceImage, privateImage).Assert(t, icmd.Success) - - pushNoAuth := runWithPrivateRegistryRetry(t, - icmd.Command("docker", "push", privateImage), - fixtures.WithConfig(emptyConfigDir), - ) - pushNoAuth.Assert(t, icmd.Expected{ExitCode: 1}) - assertAuthDenied(t, pushNoAuth) - - pushWithAuth := runWithPrivateRegistryRetry(t, - icmd.Command("docker", "push", privateImage), - fixtures.WithConfig(dir.Path()), - ) - pushWithAuth.Assert(t, icmd.Success) - // Docker omits the tag in the "push refers to repository" line; strip it before asserting. - privateRepo := privateImage[:strings.LastIndex(privateImage, ":")] - assert.Check(t, strings.Contains(pushWithAuth.Combined(), "The push refers to repository ["+privateRepo+"]"), pushWithAuth.Combined()) - - icmd.RunCommand("docker", "image", "rm", "-f", privateImage).Assert(t, icmd.Success) - - pullNoAuth := runWithPrivateRegistryRetry(t, - icmd.Command("docker", "pull", privateImage), - fixtures.WithConfig(emptyConfigDir), - ) - pullNoAuth.Assert(t, icmd.Expected{ExitCode: 1}) - assertAuthDenied(t, pullNoAuth) - - pullWithAuth := runWithPrivateRegistryRetry(t, - icmd.Command("docker", "pull", privateImage), - fixtures.WithConfig(dir.Path()), - ) - pullWithAuth.Assert(t, icmd.Success) - assert.Check(t, strings.Contains(pullWithAuth.Combined(), privateImage), pullWithAuth.Combined()) - }) - } -} - -func assertAuthDenied(t *testing.T, result *icmd.Result) { - t.Helper() - output := result.Combined() - if isPrivateRegistryTransient(output) { - t.Fatalf("private registry unavailable while expecting auth failure: %s", output) - } - - assert.Assert(t, - strings.Contains(output, "requested access to the resource is denied") || - strings.Contains(output, "no basic auth credentials") || - strings.Contains(output, "unauthorized") || - strings.Contains(output, "authentication required"), - output, - ) -} - -func runWithPrivateRegistryRetry(t *testing.T, cmd icmd.Cmd, opts ...icmd.CmdOp) *icmd.Result { - t.Helper() - - deadline := time.Now().Add(90 * time.Second) - for { - result := icmd.RunCmd(cmd, opts...) - output := result.Combined() - if isPrivateRegistryTransient(output) { - if time.Now().Before(deadline) { - t.Logf("waiting for private registry availability: %s", output) - time.Sleep(500 * time.Millisecond) - continue - } - } - return result - } -} - -func isPrivateRegistryTransient(output string) bool { - return strings.Contains(output, "lookup privateregistry") || - strings.Contains(output, "lookup tlsregistry") || - strings.Contains(output, "lookup registry") || - strings.Contains(output, "no such host") || - strings.Contains(output, "server misbehaving") || - strings.Contains(output, "Temporary failure in name resolution") || - strings.Contains(output, "connection refused") || - strings.Contains(output, "i/o timeout") || - strings.Contains(output, "TLS handshake timeout") || - strings.Contains(output, "context deadline exceeded") || - strings.Contains(output, "connection reset by peer") || - strings.Contains(output, "unexpected EOF") -} diff --git a/e2e/internal/fixtures/fixtures.go b/e2e/internal/fixtures/fixtures.go index 26bec1d49ce9..256e14f17612 100644 --- a/e2e/internal/fixtures/fixtures.go +++ b/e2e/internal/fixtures/fixtures.go @@ -23,12 +23,6 @@ func SetupConfigFile(t *testing.T) fs.Dir { "auths": { "registry:5000": { "auth": "ZWlhaXM6cGFzc3dvcmQK" - }, - "privateregistry:5001": { - "auth": "ZTJlOnBhc3N3b3Jk" - }, - "tlsregistry:5003": { - "auth": "ZTJlOnBhc3N3b3Jk" } }}`), fs.WithDir("trust", fs.WithDir("private"))) return *dir diff --git a/e2e/testdata/Dockerfile.connhelper-ssh b/e2e/testdata/Dockerfile.connhelper-ssh index 6f65ea793410..61c24b6cf458 100644 --- a/e2e/testdata/Dockerfile.connhelper-ssh +++ b/e2e/testdata/Dockerfile.connhelper-ssh @@ -12,9 +12,6 @@ RUN apk --no-cache add openssl openssh-client openssh-server shadow && \ useradd --create-home --shell /bin/sh --password $(head -c32 /dev/urandom | base64) penguin && \ usermod -aG docker penguin && \ ssh-keygen -A -# Trust the tlsregistry CA so dockerd connects without --insecure-registry. -COPY registry/certs/ca.crt /usr/local/share/ca-certificates/tlsregistry-ca.crt -RUN update-ca-certificates # workaround: ssh session excludes /usr/local/bin from $PATH RUN ln -s /usr/local/bin/docker /usr/bin/docker COPY ./connhelper-ssh/entrypoint.sh / diff --git a/e2e/testdata/engine/Dockerfile b/e2e/testdata/engine/Dockerfile deleted file mode 100644 index d688a2060694..000000000000 --- a/e2e/testdata/engine/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -ARG ENGINE_VERSION -FROM docker:${ENGINE_VERSION}-dind - -# Trust the tlsregistry CA so dockerd connects without --insecure-registry. -COPY registry/certs/ca.crt /usr/local/share/ca-certificates/tlsregistry-ca.crt -RUN update-ca-certificates diff --git a/e2e/testdata/registry/Dockerfile b/e2e/testdata/registry/Dockerfile deleted file mode 100644 index 26a4f63656b5..000000000000 --- a/e2e/testdata/registry/Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM registry:3 -COPY auth /auth -COPY certs /certs diff --git a/e2e/testdata/registry/auth/htpasswd b/e2e/testdata/registry/auth/htpasswd deleted file mode 100644 index 708391c2c1cf..000000000000 --- a/e2e/testdata/registry/auth/htpasswd +++ /dev/null @@ -1 +0,0 @@ -e2e:$2y$05$DxRBsGSy61vZsBgNVxwUh.UtZmlg3wZHMxYcHYAlupY7r1xbIiuoq diff --git a/e2e/testdata/registry/certs/gen-certs.sh b/e2e/testdata/registry/certs/gen-certs.sh deleted file mode 100755 index 4d754e631ce9..000000000000 --- a/e2e/testdata/registry/certs/gen-certs.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -set -eu - -# Regenerate test certificates for the TLS-enabled private registry. -# Run this from the repository root or from e2e/testdata/registry/certs/. - -cd "$(dirname "$0")" - -# --- CA --- -openssl genrsa -out ca.key 2048 -openssl req -new -x509 -days 3650 \ - -key ca.key \ - -subj '/CN=Test CA (TLS Registry)' \ - -out ca.crt - -# --- Server cert for tlsregistry (signed by CA) --- -cat > openssl-tlsregistry.cnf <<-EOF - [v3_req] - subjectAltName=DNS:tlsregistry -EOF -openssl genrsa -out tlsregistry.key 2048 -openssl req -new \ - -key tlsregistry.key \ - -subj '/CN=tlsregistry' \ - -out tlsregistry.csr -openssl x509 -req -days 3650 \ - -in tlsregistry.csr \ - -CA ca.crt -CAkey ca.key \ - -CAcreateserial \ - -out tlsregistry.crt \ - -extfile openssl-tlsregistry.cnf \ - -extensions v3_req -rm -f tlsregistry.csr ca.srl openssl-tlsregistry.cnf diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 58b6bbc17587..4d47a10e6b7a 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -88,18 +88,8 @@ func Confirm(ctx context.Context, in io.Reader, out io.Writer, message string) ( _, _ = out.Write([]byte(message)) // On Windows, force the use of the regular OS stdin stream. - // - // StdStreams() may wrap stdin with windowsconsole.NewAnsiReader to - // emulate VT input on consoles that do not support it natively, but - // that wrapper has historically caused interactive prompts to hang - // or behave incorrectly. - // - // See: - // - https://github.com/moby/moby/issues/14336 - // - https://github.com/moby/moby/issues/14210 - // - https://github.com/moby/moby/pull/17738 if runtime.GOOS == "windows" { - in = os.Stdin + in = streams.NewIn(os.Stdin) } result := make(chan bool) diff --git a/scripts/sync-branch b/scripts/sync-branch deleted file mode 100755 index ed24e507ffcc..000000000000 --- a/scripts/sync-branch +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -# Merge the given release tags. -set -Eeuo pipefail - -if [[ $# -lt 1 ]]; then - echo "usage: $0 TAG..." >&2 - exit 1 -fi - -tags_to_sync=("$@") - -for tag_to_sync in "${tags_to_sync[@]}"; do - if git merge --no-edit --no-ff "$tag_to_sync"; then - continue - fi - - if ! git rev-parse --verify --quiet MERGE_HEAD > /dev/null || git diff --quiet --diff-filter=U; then - exit 1 - fi - - git checkout "$tag_to_sync" -- '.' - git add --all - - # Can't use --no-edit with --continue - EDITOR=true git merge --continue -done - -# Check that every tag is in the branch. -# This catches cases where a merge did not actually incorporate one of the -# requested release tags. -for tag_to_sync in "${tags_to_sync[@]}"; do - if ! git merge-base --is-ancestor "$tag_to_sync" HEAD; then - echo "tag $tag_to_sync is not contained in $(git rev-parse --abbrev-ref HEAD)" >&2 - exit 1 - fi -done diff --git a/scripts/test/e2e/run b/scripts/test/e2e/run index ad6caaaa1362..a13359660fc6 100755 --- a/scripts/test/e2e/run +++ b/scripts/test/e2e/run @@ -26,55 +26,8 @@ setup() { export TEST_CONNHELPER_SSH_ID_RSA_PUB file="${file}:./e2e/compose-env.connhelper-ssh.yaml" fi - # Generate TLS certificates for the TLS-enabled private registry. - # The certs are baked into the tlsregistry and engine container images, - # so they must exist on disk before docker compose up --build. - # gen-certs.sh handles its own directory navigation. - certdir=e2e/testdata/registry/certs - missing=0 - for f in ca.crt ca.key tlsregistry.crt tlsregistry.key; do - if [ ! -f "${certdir}/${f}" ]; then - missing=1 - break - fi - done - if [ "$missing" -eq 1 ]; then - sh e2e/testdata/registry/certs/gen-certs.sh - fi - COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose up --build -d >&2 - # Ensure supporting services exist before running tests. If one fails to start, - # fail fast and surface logs instead of waiting on downstream DNS timeouts. - local deadline=$((SECONDS + 120)) - while [ $SECONDS -lt $deadline ]; do - local ok=1 - for svc in registry privateregistry tlsregistry engine; do - cid="$(COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose ps -q "$svc" 2>/dev/null || true)" - if [ -z "$cid" ]; then - ok=0 - break - fi - if ! docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null | grep -q true; then - ok=0 - break - fi - done - if [ "$ok" -eq 1 ]; then - break - fi - sleep 1 - done - if [ $SECONDS -ge $deadline ]; then - echo "Timed out waiting for e2e services to start" >&2 - COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose ps >&2 || true - for svc in registry privateregistry tlsregistry engine; do - echo "--- logs: $svc ---" >&2 - COMPOSE_PROJECT_NAME=$project COMPOSE_FILE=$file docker compose logs --no-color --tail=200 "$svc" >&2 || true - done - exit 1 - fi - local network="${project}_default" # TODO: only run if inside a container docker network connect "$network" "$(hostname)" diff --git a/scripts/unmerged-tags b/scripts/unmerged-tags deleted file mode 100755 index 57b90666af3c..000000000000 --- a/scripts/unmerged-tags +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -if [[ $# -ne 2 ]]; then - echo "usage: $0 " >&2 - exit 1 -fi - -release_branch="$1" -tag="$2" - -if [[ "$tag" == *"-rc."* ]]; then - echo "error: RC tags cannot be used as sync targets" >&2 - exit 1 -fi - -if [[ "$tag" != v* ]]; then - echo "error: Tag must start with 'v' (e.g. v29.6.0)" >&2 - exit 1 -fi - -if ! git rev-parse --verify --quiet "refs/tags/$tag" > /dev/null; then - echo "error: Tag $tag does not exist" >&2 - exit 1 -fi - -# Return early the requested tag is already merged into release branch. -if git merge-base --is-ancestor "$tag" "$release_branch"; then - exit 0 -fi - -if git rev-parse --verify --quiet upstream/master > /dev/null 2>&1; then - master="upstream/master" -else - master="origin/master" -fi - -if ! git merge-base --is-ancestor "$tag" "$master"; then - echo "error: Tag $tag is not in $master" >&2 - exit 1 -fi - -# Get all docker release tags merged into master but not into release branch -git tag --merged "$master" --no-merged "$release_branch" \ - | grep '^v' \ - | grep -v -- "-rc." \ - | sort -V \ - | awk -v tag="$tag" '{print} $0==tag{exit}'