├── .github ├── CODEOWNERS ├── dependabot.yml ├── labels.yml ├── pipeline-descriptor.yml ├── pipeline-version ├── release-drafter.yml └── workflows │ ├── action-buildpack-compute-metadata.yml │ ├── action-buildpackage-verify-metadata.yml │ ├── action-registry-add-entry.yml │ ├── action-registry-compute-metadata.yml │ ├── action-registry-index-update-release.yml │ ├── action-registry-request-add-entry.yml │ ├── action-registry-request-yank-entry.yml │ ├── action-registry-verify-namespace-owner.yml │ ├── action-registry-yank-entry.yml │ ├── pb-minimal-labels.yml │ ├── pb-synchronize-labels.yml │ ├── pb-tests.yml │ ├── pb-update-draft-release.yml │ ├── pb-update-go.yml │ ├── pb-update-pipeline.yml │ └── update-pack-version.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── NOTICE ├── README.md ├── buildpack └── compute-metadata │ ├── cmd │ └── main.go │ ├── compute_metadata.go │ ├── compute_metadata_test.go │ └── testdata │ └── buildpack.toml ├── buildpackage └── verify-metadata │ ├── cmd │ └── main.go │ ├── image_function.go │ ├── internal │ └── mocks │ │ └── image_function.go │ ├── verify_metadata.go │ └── verify_metadata_test.go ├── go.mod ├── go.sum ├── internal └── toolkit │ ├── mock_toolkit.go │ ├── toolkit.go │ └── toolkit_test.go ├── registry ├── add-entry │ ├── add_entry.go │ ├── add_entry_test.go │ └── cmd │ │ └── main.go ├── compute-metadata │ ├── cmd │ │ └── main.go │ ├── compute_metadata.go │ └── compute_metadata_test.go ├── internal │ ├── index │ │ ├── entry.go │ │ ├── entry_test.go │ │ ├── path.go │ │ ├── path_test.go │ │ ├── request.go │ │ ├── wait_for_completion.go │ │ └── wait_for_completion_test.go │ ├── namespace │ │ ├── namespace.go │ │ ├── namespace_test.go │ │ ├── owner.go │ │ ├── owner_test.go │ │ ├── path.go │ │ └── path_test.go │ └── services │ │ ├── mock_issues_service.go │ │ ├── mock_organizations_service.go │ │ ├── mock_repositories_service.go │ │ └── services.go ├── request-add-entry │ ├── cmd │ │ └── main.go │ ├── request_add_entry.go │ └── request_add_entry_test.go ├── request-yank-entry │ ├── cmd │ │ └── main.go │ ├── request_yank_entry.go │ └── request_yank_entry_test.go ├── verify-namespace-owner │ ├── cmd │ │ └── main.go │ ├── verify_namespace_owner.go │ └── verify_namespace_owner_test.go └── yank-entry │ ├── cmd │ └── main.go │ ├── yank_entry.go │ └── yank_entry_test.go ├── setup-pack └── action.yml └── setup-tools └── action.yml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @buildpacks/platform-maintainers @buildpacks/toc @edmorley -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: / 5 | schedule: 6 | interval: daily 7 | ignore: 8 | - dependency-name: github.com/onsi/gomega 9 | labels: 10 | - semver:patch 11 | - type:dependency-upgrade 12 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: semver:major 2 | description: A change requiring a major version bump 3 | color: f9d0c4 4 | - name: semver:minor 5 | description: A change requiring a minor version bump 6 | color: f9d0c4 7 | - name: semver:patch 8 | description: A change requiring a patch version bump 9 | color: f9d0c4 10 | - name: type:bug 11 | description: A general bug 12 | color: e3d9fc 13 | - name: type:dependency-upgrade 14 | description: A dependency upgrade 15 | color: e3d9fc 16 | - name: type:documentation 17 | description: A documentation update 18 | color: e3d9fc 19 | - name: type:enhancement 20 | description: A general enhancement 21 | color: e3d9fc 22 | - name: type:question 23 | description: A user question 24 | color: e3d9fc 25 | - name: type:task 26 | description: A general task 27 | color: e3d9fc 28 | - name: type:informational 29 | description: Provides information or notice to the community 30 | color: e3d9fc 31 | - name: type:poll 32 | description: Request for feedback from the community 33 | color: e3d9fc 34 | - name: note:ideal-for-contribution 35 | description: An issue that a contributor can help us with 36 | color: 54f7a8 37 | - name: note:on-hold 38 | description: We can't start working on this issue yet 39 | color: 54f7a8 40 | - name: note:good-first-issue 41 | description: A good first issue to get started with 42 | color: 54f7a8 43 | -------------------------------------------------------------------------------- /.github/pipeline-descriptor.yml: -------------------------------------------------------------------------------- 1 | github: 2 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 3 | token: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 4 | 5 | codeowners: 6 | - path: "*" 7 | owner: "@buildpacks/platform-maintainers @buildpacks/toc" 8 | -------------------------------------------------------------------------------- /.github/pipeline-version: -------------------------------------------------------------------------------- 1 | 1.41.2 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: $CHANGES 2 | name-template: $RESOLVED_VERSION 3 | tag-template: v$RESOLVED_VERSION 4 | categories: 5 | - title: ⭐️ Enhancements 6 | labels: 7 | - type:enhancement 8 | - title: "\U0001F41E Bug Fixes" 9 | labels: 10 | - type:bug 11 | - title: "\U0001F4D4 Documentation" 12 | labels: 13 | - type:documentation 14 | - title: ⛏ Dependency Upgrades 15 | labels: 16 | - type:dependency-upgrade 17 | - title: "\U0001F6A7 Tasks" 18 | labels: 19 | - type:task 20 | exclude-labels: 21 | - type:question 22 | version-resolver: 23 | major: 24 | labels: 25 | - semver:major 26 | minor: 27 | labels: 28 | - semver:minor 29 | patch: 30 | labels: 31 | - semver:patch 32 | default: patch 33 | -------------------------------------------------------------------------------- /.github/workflows/action-buildpack-compute-metadata.yml: -------------------------------------------------------------------------------- 1 | name: Action buildpack-compute-metadata 2 | "on": 3 | pull_request: 4 | paths: 5 | - buildpack/compute-metadata/** 6 | - internal/** 7 | push: 8 | branches: 9 | - main 10 | - test 11 | paths: 12 | - buildpack/compute-metadata/** 13 | - internal/** 14 | release: 15 | types: 16 | - published 17 | jobs: 18 | create-action: 19 | name: Create Action 20 | runs-on: 21 | - ubuntu-latest 22 | steps: 23 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 24 | name: Docker login ghcr.io 25 | uses: docker/login-action@v3.0.0 26 | with: 27 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 28 | registry: ghcr.io 29 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 30 | - uses: actions/checkout@v2.3.4 31 | - id: version 32 | name: Compute Version 33 | run: | 34 | #!/usr/bin/env bash 35 | 36 | set -euo pipefail 37 | 38 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 39 | VERSION=${BASH_REMATCH[1]} 40 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | else 43 | VERSION=$(git rev-parse --short HEAD) 44 | fi 45 | 46 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 47 | echo "Selected ${VERSION} from 48 | * ref: ${GITHUB_REF} 49 | * sha: ${GITHUB_SHA} 50 | " 51 | - name: Create Action 52 | run: | 53 | #!/usr/bin/env bash 54 | 55 | set -euo pipefail 56 | 57 | echo "::group::Building ${TARGET}:${VERSION}" 58 | docker build \ 59 | --file Dockerfile \ 60 | --build-arg "SOURCE=${SOURCE}" \ 61 | --tag "${TARGET}:${VERSION}" \ 62 | . 63 | echo "::endgroup::" 64 | 65 | if [[ "${PUSH}" == "true" ]]; then 66 | echo "::group::Pushing ${TARGET}:${VERSION}" 67 | docker push "${TARGET}:${VERSION}" 68 | echo "::endgroup::" 69 | else 70 | echo "Skipping push" 71 | fi 72 | env: 73 | PUSH: ${{ github.event_name != 'pull_request' }} 74 | SOURCE: buildpack/compute-metadata/cmd 75 | TARGET: ghcr.io/buildpacks/actions/buildpack/compute-metadata 76 | VERSION: ${{ steps.version.outputs.version }} 77 | -------------------------------------------------------------------------------- /.github/workflows/action-buildpackage-verify-metadata.yml: -------------------------------------------------------------------------------- 1 | name: Action buildpackage-verify-metadata 2 | "on": 3 | pull_request: 4 | paths: 5 | - buildpackage/verify-metadata/** 6 | - internal/** 7 | push: 8 | branches: 9 | - main 10 | - test 11 | paths: 12 | - buildpackage/verify-metadata/** 13 | - internal/** 14 | release: 15 | types: 16 | - published 17 | jobs: 18 | create-action: 19 | name: Create Action 20 | runs-on: 21 | - ubuntu-latest 22 | steps: 23 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 24 | name: Docker login ghcr.io 25 | uses: docker/login-action@v3.0.0 26 | with: 27 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 28 | registry: ghcr.io 29 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 30 | - uses: actions/checkout@v2.3.4 31 | - id: version 32 | name: Compute Version 33 | run: | 34 | #!/usr/bin/env bash 35 | 36 | set -euo pipefail 37 | 38 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 39 | VERSION=${BASH_REMATCH[1]} 40 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | else 43 | VERSION=$(git rev-parse --short HEAD) 44 | fi 45 | 46 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 47 | echo "Selected ${VERSION} from 48 | * ref: ${GITHUB_REF} 49 | * sha: ${GITHUB_SHA} 50 | " 51 | - name: Create Action 52 | run: | 53 | #!/usr/bin/env bash 54 | 55 | set -euo pipefail 56 | 57 | echo "::group::Building ${TARGET}:${VERSION}" 58 | docker build \ 59 | --file Dockerfile \ 60 | --build-arg "SOURCE=${SOURCE}" \ 61 | --tag "${TARGET}:${VERSION}" \ 62 | . 63 | echo "::endgroup::" 64 | 65 | if [[ "${PUSH}" == "true" ]]; then 66 | echo "::group::Pushing ${TARGET}:${VERSION}" 67 | docker push "${TARGET}:${VERSION}" 68 | echo "::endgroup::" 69 | else 70 | echo "Skipping push" 71 | fi 72 | env: 73 | PUSH: ${{ github.event_name != 'pull_request' }} 74 | SOURCE: buildpackage/verify-metadata/cmd 75 | TARGET: ghcr.io/buildpacks/actions/buildpackage/verify-metadata 76 | VERSION: ${{ steps.version.outputs.version }} 77 | -------------------------------------------------------------------------------- /.github/workflows/action-registry-add-entry.yml: -------------------------------------------------------------------------------- 1 | name: Action registry-add-entry 2 | "on": 3 | pull_request: 4 | paths: 5 | - internal/** 6 | - registry/add-entry/** 7 | - registry/internal/** 8 | push: 9 | branches: 10 | - main 11 | - test 12 | paths: 13 | - internal/** 14 | - registry/add-entry/** 15 | - registry/internal/** 16 | release: 17 | types: 18 | - published 19 | jobs: 20 | create-action: 21 | name: Create Action 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 26 | name: Docker login ghcr.io 27 | uses: docker/login-action@v3.0.0 28 | with: 29 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 30 | registry: ghcr.io 31 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 32 | - uses: actions/checkout@v2.3.4 33 | - id: version 34 | name: Compute Version 35 | run: | 36 | #!/usr/bin/env bash 37 | 38 | set -euo pipefail 39 | 40 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 43 | VERSION=${BASH_REMATCH[1]} 44 | else 45 | VERSION=$(git rev-parse --short HEAD) 46 | fi 47 | 48 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 49 | echo "Selected ${VERSION} from 50 | * ref: ${GITHUB_REF} 51 | * sha: ${GITHUB_SHA} 52 | " 53 | - name: Create Action 54 | run: | 55 | #!/usr/bin/env bash 56 | 57 | set -euo pipefail 58 | 59 | echo "::group::Building ${TARGET}:${VERSION}" 60 | docker build \ 61 | --file Dockerfile \ 62 | --build-arg "SOURCE=${SOURCE}" \ 63 | --tag "${TARGET}:${VERSION}" \ 64 | . 65 | echo "::endgroup::" 66 | 67 | if [[ "${PUSH}" == "true" ]]; then 68 | echo "::group::Pushing ${TARGET}:${VERSION}" 69 | docker push "${TARGET}:${VERSION}" 70 | echo "::endgroup::" 71 | else 72 | echo "Skipping push" 73 | fi 74 | env: 75 | PUSH: ${{ github.event_name != 'pull_request' }} 76 | SOURCE: registry/add-entry/cmd 77 | TARGET: ghcr.io/buildpacks/actions/registry/add-entry 78 | VERSION: ${{ steps.version.outputs.version }} 79 | -------------------------------------------------------------------------------- /.github/workflows/action-registry-compute-metadata.yml: -------------------------------------------------------------------------------- 1 | name: Action registry-compute-metadata 2 | "on": 3 | pull_request: 4 | paths: 5 | - internal/** 6 | - registry/compute-metadata/** 7 | - registry/internal/** 8 | push: 9 | branches: 10 | - main 11 | - test 12 | paths: 13 | - internal/** 14 | - registry/compute-metadata/** 15 | - registry/internal/** 16 | release: 17 | types: 18 | - published 19 | jobs: 20 | create-action: 21 | name: Create Action 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 26 | name: Docker login ghcr.io 27 | uses: docker/login-action@v3.0.0 28 | with: 29 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 30 | registry: ghcr.io 31 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 32 | - uses: actions/checkout@v2.3.4 33 | - id: version 34 | name: Compute Version 35 | run: | 36 | #!/usr/bin/env bash 37 | 38 | set -euo pipefail 39 | 40 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 43 | VERSION=${BASH_REMATCH[1]} 44 | else 45 | VERSION=$(git rev-parse --short HEAD) 46 | fi 47 | 48 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 49 | echo "Selected ${VERSION} from 50 | * ref: ${GITHUB_REF} 51 | * sha: ${GITHUB_SHA} 52 | " 53 | - name: Create Action 54 | run: | 55 | #!/usr/bin/env bash 56 | 57 | set -euo pipefail 58 | 59 | echo "::group::Building ${TARGET}:${VERSION}" 60 | docker build \ 61 | --file Dockerfile \ 62 | --build-arg "SOURCE=${SOURCE}" \ 63 | --tag "${TARGET}:${VERSION}" \ 64 | . 65 | echo "::endgroup::" 66 | 67 | if [[ "${PUSH}" == "true" ]]; then 68 | echo "::group::Pushing ${TARGET}:${VERSION}" 69 | docker push "${TARGET}:${VERSION}" 70 | echo "::endgroup::" 71 | else 72 | echo "Skipping push" 73 | fi 74 | env: 75 | PUSH: ${{ github.event_name != 'pull_request' }} 76 | SOURCE: registry/compute-metadata/cmd 77 | TARGET: ghcr.io/buildpacks/actions/registry/compute-metadata 78 | VERSION: ${{ steps.version.outputs.version }} 79 | -------------------------------------------------------------------------------- /.github/workflows/action-registry-index-update-release.yml: -------------------------------------------------------------------------------- 1 | name: Creating the PR to update the version of buildpacks/github-actions on buildpacks/registry-index 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | myEvent: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Repository Dispatch 10 | uses: peter-evans/repository-dispatch@v3 11 | with: 12 | token: ${{ secrets.DISTRIBUTION_GITHUB_TOKEN }} 13 | repository: buildpacks/registry-index 14 | event-type: release-event -------------------------------------------------------------------------------- /.github/workflows/action-registry-request-add-entry.yml: -------------------------------------------------------------------------------- 1 | name: Action registry-request-add-entry 2 | "on": 3 | pull_request: 4 | paths: 5 | - internal/** 6 | - registry/internal/** 7 | - registry/request-add-entry/** 8 | push: 9 | branches: 10 | - main 11 | - test 12 | paths: 13 | - internal/** 14 | - registry/internal/** 15 | - registry/request-add-entry/** 16 | release: 17 | types: 18 | - published 19 | jobs: 20 | create-action: 21 | name: Create Action 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 26 | name: Docker login ghcr.io 27 | uses: docker/login-action@v3.0.0 28 | with: 29 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 30 | registry: ghcr.io 31 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 32 | - uses: actions/checkout@v2.3.4 33 | - id: version 34 | name: Compute Version 35 | run: | 36 | #!/usr/bin/env bash 37 | 38 | set -euo pipefail 39 | 40 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 43 | VERSION=${BASH_REMATCH[1]} 44 | else 45 | VERSION=$(git rev-parse --short HEAD) 46 | fi 47 | 48 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 49 | echo "Selected ${VERSION} from 50 | * ref: ${GITHUB_REF} 51 | * sha: ${GITHUB_SHA} 52 | " 53 | - name: Create Action 54 | run: | 55 | #!/usr/bin/env bash 56 | 57 | set -euo pipefail 58 | 59 | echo "::group::Building ${TARGET}:${VERSION}" 60 | docker build \ 61 | --file Dockerfile \ 62 | --build-arg "SOURCE=${SOURCE}" \ 63 | --tag "${TARGET}:${VERSION}" \ 64 | . 65 | echo "::endgroup::" 66 | 67 | if [[ "${PUSH}" == "true" ]]; then 68 | echo "::group::Pushing ${TARGET}:${VERSION}" 69 | docker push "${TARGET}:${VERSION}" 70 | echo "::endgroup::" 71 | else 72 | echo "Skipping push" 73 | fi 74 | env: 75 | PUSH: ${{ github.event_name != 'pull_request' }} 76 | SOURCE: registry/request-add-entry/cmd 77 | TARGET: ghcr.io/buildpacks/actions/registry/request-add-entry 78 | VERSION: ${{ steps.version.outputs.version }} 79 | -------------------------------------------------------------------------------- /.github/workflows/action-registry-request-yank-entry.yml: -------------------------------------------------------------------------------- 1 | name: Action registry-request-yank-entry 2 | "on": 3 | pull_request: 4 | paths: 5 | - internal/** 6 | - registry/internal/** 7 | - registry/request-yank-entry/** 8 | push: 9 | branches: 10 | - main 11 | - test 12 | paths: 13 | - internal/** 14 | - registry/internal/** 15 | - registry/request-yank-entry/** 16 | release: 17 | types: 18 | - published 19 | jobs: 20 | create-action: 21 | name: Create Action 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 26 | name: Docker login ghcr.io 27 | uses: docker/login-action@v3.0.0 28 | with: 29 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 30 | registry: ghcr.io 31 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 32 | - uses: actions/checkout@v2.3.4 33 | - id: version 34 | name: Compute Version 35 | run: | 36 | #!/usr/bin/env bash 37 | 38 | set -euo pipefail 39 | 40 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 43 | VERSION=${BASH_REMATCH[1]} 44 | else 45 | VERSION=$(git rev-parse --short HEAD) 46 | fi 47 | 48 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 49 | echo "Selected ${VERSION} from 50 | * ref: ${GITHUB_REF} 51 | * sha: ${GITHUB_SHA} 52 | " 53 | - name: Create Action 54 | run: | 55 | #!/usr/bin/env bash 56 | 57 | set -euo pipefail 58 | 59 | echo "::group::Building ${TARGET}:${VERSION}" 60 | docker build \ 61 | --file Dockerfile \ 62 | --build-arg "SOURCE=${SOURCE}" \ 63 | --tag "${TARGET}:${VERSION}" \ 64 | . 65 | echo "::endgroup::" 66 | 67 | if [[ "${PUSH}" == "true" ]]; then 68 | echo "::group::Pushing ${TARGET}:${VERSION}" 69 | docker push "${TARGET}:${VERSION}" 70 | echo "::endgroup::" 71 | else 72 | echo "Skipping push" 73 | fi 74 | env: 75 | PUSH: ${{ github.event_name != 'pull_request' }} 76 | SOURCE: registry/request-yank-entry/cmd 77 | TARGET: ghcr.io/buildpacks/actions/registry/request-yank-entry 78 | VERSION: ${{ steps.version.outputs.version }} 79 | -------------------------------------------------------------------------------- /.github/workflows/action-registry-verify-namespace-owner.yml: -------------------------------------------------------------------------------- 1 | name: Action registry-verify-namespace-owner 2 | "on": 3 | pull_request: 4 | paths: 5 | - internal/** 6 | - registry/internal/** 7 | - registry/verify-namespace-owner/** 8 | push: 9 | branches: 10 | - main 11 | - test 12 | paths: 13 | - internal/** 14 | - registry/internal/** 15 | - registry/verify-namespace-owner/** 16 | release: 17 | types: 18 | - published 19 | jobs: 20 | create-action: 21 | name: Create Action 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 26 | name: Docker login ghcr.io 27 | uses: docker/login-action@v3.0.0 28 | with: 29 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 30 | registry: ghcr.io 31 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 32 | - uses: actions/checkout@v2.3.4 33 | - id: version 34 | name: Compute Version 35 | run: | 36 | #!/usr/bin/env bash 37 | 38 | set -euo pipefail 39 | 40 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 43 | VERSION=${BASH_REMATCH[1]} 44 | else 45 | VERSION=$(git rev-parse --short HEAD) 46 | fi 47 | 48 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 49 | echo "Selected ${VERSION} from 50 | * ref: ${GITHUB_REF} 51 | * sha: ${GITHUB_SHA} 52 | " 53 | - name: Create Action 54 | run: | 55 | #!/usr/bin/env bash 56 | 57 | set -euo pipefail 58 | 59 | echo "::group::Building ${TARGET}:${VERSION}" 60 | docker build \ 61 | --file Dockerfile \ 62 | --build-arg "SOURCE=${SOURCE}" \ 63 | --tag "${TARGET}:${VERSION}" \ 64 | . 65 | echo "::endgroup::" 66 | 67 | if [[ "${PUSH}" == "true" ]]; then 68 | echo "::group::Pushing ${TARGET}:${VERSION}" 69 | docker push "${TARGET}:${VERSION}" 70 | echo "::endgroup::" 71 | else 72 | echo "Skipping push" 73 | fi 74 | env: 75 | PUSH: ${{ github.event_name != 'pull_request' }} 76 | SOURCE: registry/verify-namespace-owner/cmd 77 | TARGET: ghcr.io/buildpacks/actions/registry/verify-namespace-owner 78 | VERSION: ${{ steps.version.outputs.version }} 79 | -------------------------------------------------------------------------------- /.github/workflows/action-registry-yank-entry.yml: -------------------------------------------------------------------------------- 1 | name: Action registry-yank-entry 2 | "on": 3 | pull_request: 4 | paths: 5 | - internal/** 6 | - registry/yank-entry/** 7 | - registry/internal/** 8 | push: 9 | branches: 10 | - main 11 | - test 12 | paths: 13 | - internal/** 14 | - registry/yank-entry/** 15 | - registry/internal/** 16 | release: 17 | types: 18 | - published 19 | jobs: 20 | create-action: 21 | name: Create Action 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - if: ${{ github.event_name != 'pull_request' || ! github.event.pull_request.head.repo.fork }} 26 | name: Docker login ghcr.io 27 | uses: docker/login-action@v3.0.0 28 | with: 29 | password: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 30 | registry: ghcr.io 31 | username: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} 32 | - uses: actions/checkout@v2.3.4 33 | - id: version 34 | name: Compute Version 35 | run: | 36 | #!/usr/bin/env bash 37 | 38 | set -euo pipefail 39 | 40 | if [[ ${GITHUB_REF} =~ refs/tags/v([0-9]+\.[0-9]+\.[0-9]+) ]]; then 41 | VERSION=${BASH_REMATCH[1]} 42 | elif [[ ${GITHUB_REF} =~ refs/heads/(.+) ]]; then 43 | VERSION=${BASH_REMATCH[1]} 44 | else 45 | VERSION=$(git rev-parse --short HEAD) 46 | fi 47 | 48 | echo "version=${VERSION}" >> "$GITHUB_OUTPUT" 49 | echo "Selected ${VERSION} from 50 | * ref: ${GITHUB_REF} 51 | * sha: ${GITHUB_SHA} 52 | " 53 | - name: Create Action 54 | run: | 55 | #!/usr/bin/env bash 56 | 57 | set -euo pipefail 58 | 59 | echo "::group::Building ${TARGET}:${VERSION}" 60 | docker build \ 61 | --file Dockerfile \ 62 | --build-arg "SOURCE=${SOURCE}" \ 63 | --tag "${TARGET}:${VERSION}" \ 64 | . 65 | echo "::endgroup::" 66 | 67 | if [[ "${PUSH}" == "true" ]]; then 68 | echo "::group::Pushing ${TARGET}:${VERSION}" 69 | docker push "${TARGET}:${VERSION}" 70 | echo "::endgroup::" 71 | else 72 | echo "Skipping push" 73 | fi 74 | env: 75 | PUSH: ${{ github.event_name != 'pull_request' }} 76 | SOURCE: registry/yank-entry/cmd 77 | TARGET: ghcr.io/buildpacks/actions/registry/yank-entry 78 | VERSION: ${{ steps.version.outputs.version }} 79 | -------------------------------------------------------------------------------- /.github/workflows/pb-minimal-labels.yml: -------------------------------------------------------------------------------- 1 | name: Minimal Labels 2 | "on": 3 | pull_request: 4 | types: 5 | - synchronize 6 | - reopened 7 | - labeled 8 | - unlabeled 9 | jobs: 10 | semver: 11 | name: Minimal Semver Labels 12 | runs-on: 13 | - ubuntu-latest 14 | steps: 15 | - uses: mheap/github-action-required-labels@v5 16 | with: 17 | count: 1 18 | labels: semver:major, semver:minor, semver:patch 19 | mode: exactly 20 | type: 21 | name: Minimal Type Labels 22 | runs-on: 23 | - ubuntu-latest 24 | steps: 25 | - uses: mheap/github-action-required-labels@v5 26 | with: 27 | count: 1 28 | labels: type:bug, type:dependency-upgrade, type:documentation, type:enhancement, type:question, type:task 29 | mode: exactly 30 | -------------------------------------------------------------------------------- /.github/workflows/pb-synchronize-labels.yml: -------------------------------------------------------------------------------- 1 | name: Synchronize Labels 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - .github/labels.yml 8 | jobs: 9 | synchronize: 10 | name: Synchronize Labels 11 | runs-on: 12 | - ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: micnncim/action-label-syncer@v1 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /.github/workflows/pb-tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | "on": 3 | merge_group: 4 | types: 5 | - checks_requested 6 | branches: 7 | - main 8 | pull_request: {} 9 | push: 10 | branches: 11 | - main 12 | jobs: 13 | unit: 14 | name: Unit Test 15 | runs-on: 16 | - ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/cache@v4 20 | with: 21 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 22 | path: ${{ env.HOME }}/go/pkg/mod 23 | restore-keys: ${{ runner.os }}-go- 24 | - uses: actions/setup-go@v5 25 | with: 26 | go-version: "1.23" 27 | - name: Install richgo 28 | run: | 29 | #!/usr/bin/env bash 30 | 31 | set -euo pipefail 32 | 33 | echo "Installing richgo ${RICHGO_VERSION}" 34 | 35 | mkdir -p "${HOME}"/bin 36 | echo "${HOME}/bin" >> "${GITHUB_PATH}" 37 | 38 | curl \ 39 | --location \ 40 | --show-error \ 41 | --silent \ 42 | "https://github.com/kyoh86/richgo/releases/download/v${RICHGO_VERSION}/richgo_${RICHGO_VERSION}_linux_amd64.tar.gz" \ 43 | | tar -C "${HOME}"/bin -xz richgo 44 | env: 45 | RICHGO_VERSION: 0.3.10 46 | - name: Run Tests 47 | run: | 48 | #!/usr/bin/env bash 49 | 50 | set -euo pipefail 51 | 52 | richgo test ./... 53 | env: 54 | RICHGO_FORCE_COLOR: "1" 55 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-draft-release.yml: -------------------------------------------------------------------------------- 1 | name: Update Draft Release 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | update: 8 | name: Update Draft Release 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - id: release-drafter 13 | uses: release-drafter/release-drafter@v5 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-go.yml: -------------------------------------------------------------------------------- 1 | name: Update Go 2 | "on": 3 | schedule: 4 | - cron: 26 2 * * 1 5 | workflow_dispatch: {} 6 | jobs: 7 | update: 8 | name: Update Go 9 | runs-on: 10 | - ubuntu-latest 11 | steps: 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version: "1.23" 15 | - uses: actions/checkout@v4 16 | - name: Update Go Version & Modules 17 | id: update-go 18 | run: | 19 | #!/usr/bin/env bash 20 | 21 | set -euo pipefail 22 | 23 | if [ -z "${GO_VERSION:-}" ]; then 24 | echo "No go version set" 25 | exit 1 26 | fi 27 | 28 | OLD_GO_VERSION=$(grep -P '^go \d\.\d+' go.mod | cut -d ' ' -f 2 | cut -d '.' -f 1-2) 29 | 30 | go mod edit -go="$GO_VERSION" 31 | go mod tidy 32 | go get -u -t ./... 33 | go mod tidy 34 | 35 | git add go.mod go.sum 36 | git checkout -- . 37 | 38 | if [ "$OLD_GO_VERSION" == "$GO_VERSION" ]; then 39 | COMMIT_TITLE="Bump Go Modules" 40 | COMMIT_BODY="Bumps Go modules used by the project. See the commit for details on what modules were updated." 41 | COMMIT_SEMVER="semver:patch" 42 | else 43 | COMMIT_TITLE="Bump Go from ${OLD_GO_VERSION} to ${GO_VERSION}" 44 | COMMIT_BODY="Bumps Go from ${OLD_GO_VERSION} to ${GO_VERSION} and update Go modules used by the project. See the commit for details on what modules were updated." 45 | COMMIT_SEMVER="semver:minor" 46 | fi 47 | 48 | echo "commit-title=${COMMIT_TITLE}" >> "$GITHUB_OUTPUT" 49 | echo "commit-body=${COMMIT_BODY}" >> "$GITHUB_OUTPUT" 50 | echo "commit-semver=${COMMIT_SEMVER}" >> "$GITHUB_OUTPUT" 51 | env: 52 | GO_VERSION: "1.23" 53 | - uses: peter-evans/create-pull-request@v6 54 | with: 55 | author: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} <${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}@users.noreply.github.com> 56 | body: |- 57 | ${{ steps.update-go.outputs.commit-body }} 58 | 59 |
60 | Release Notes 61 | ${{ steps.pipeline.outputs.release-notes }} 62 |
63 | branch: update/go 64 | commit-message: |- 65 | ${{ steps.update-go.outputs.commit-title }} 66 | 67 | ${{ steps.update-go.outputs.commit-body }} 68 | delete-branch: true 69 | labels: ${{ steps.update-go.outputs.commit-semver }}, type:task 70 | signoff: true 71 | title: ${{ steps.update-go.outputs.commit-title }} 72 | token: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 73 | -------------------------------------------------------------------------------- /.github/workflows/pb-update-pipeline.yml: -------------------------------------------------------------------------------- 1 | name: Update Pipeline 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - .github/pipeline-descriptor.yml 8 | schedule: 9 | - cron: 0 5 * * 1-5 10 | workflow_dispatch: {} 11 | jobs: 12 | update: 13 | name: Update Pipeline 14 | runs-on: 15 | - ubuntu-latest 16 | steps: 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: "1.23" 20 | - name: Install octo 21 | run: | 22 | #!/usr/bin/env bash 23 | 24 | set -euo pipefail 25 | 26 | go install -ldflags="-s -w" github.com/paketo-buildpacks/pipeline-builder/cmd/octo@latest 27 | - uses: actions/checkout@v4 28 | - name: Update Pipeline 29 | id: pipeline 30 | run: | 31 | #!/usr/bin/env bash 32 | 33 | set -euo pipefail 34 | 35 | if [[ -f .github/pipeline-version ]]; then 36 | OLD_VERSION=$(cat .github/pipeline-version) 37 | else 38 | OLD_VERSION="0.0.0" 39 | fi 40 | 41 | rm .github/workflows/pb-*.yml || true 42 | octo --descriptor "${DESCRIPTOR}" 43 | 44 | PAYLOAD=$(gh api /repos/paketo-buildpacks/pipeline-builder/releases/latest) 45 | 46 | NEW_VERSION=$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.name') 47 | echo "${NEW_VERSION}" > .github/pipeline-version 48 | 49 | RELEASE_NOTES=$( 50 | gh api \ 51 | -F text="$(jq -n -r --argjson PAYLOAD "${PAYLOAD}" '$PAYLOAD.body')" \ 52 | -F mode="gfm" \ 53 | -F context="paketo-buildpacks/pipeline-builder" \ 54 | -X POST /markdown 55 | ) 56 | 57 | git add .github/ 58 | git add .gitignore 59 | 60 | if [ -f scripts/build.sh ]; then 61 | git add scripts/build.sh 62 | fi 63 | 64 | git checkout -- . 65 | 66 | echo "old-version=${OLD_VERSION}" >> "$GITHUB_OUTPUT" 67 | echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT" 68 | 69 | DELIMITER=$(openssl rand -hex 16) # roughly the same entropy as uuid v4 used in https://github.com/actions/toolkit/blob/b36e70495fbee083eb20f600eafa9091d832577d/packages/core/src/file-command.ts#L28 70 | printf "release-notes<<%s\n%s\n%s\n" "${DELIMITER}" "${RELEASE_NOTES}" "${DELIMITER}" >> "${GITHUB_OUTPUT}" # see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings 71 | env: 72 | DESCRIPTOR: .github/pipeline-descriptor.yml 73 | GITHUB_TOKEN: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 74 | - uses: peter-evans/create-pull-request@v6 75 | with: 76 | author: ${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }} <${{ secrets.IMPLEMENTATION_GITHUB_USERNAME }}@users.noreply.github.com> 77 | body: |- 78 | Bumps pipeline from `${{ steps.pipeline.outputs.old-version }}` to `${{ steps.pipeline.outputs.new-version }}`. 79 | 80 |
81 | Release Notes 82 | ${{ steps.pipeline.outputs.release-notes }} 83 |
84 | branch: update/pipeline 85 | commit-message: |- 86 | Bump pipeline from ${{ steps.pipeline.outputs.old-version }} to ${{ steps.pipeline.outputs.new-version }} 87 | 88 | Bumps pipeline from ${{ steps.pipeline.outputs.old-version }} to ${{ steps.pipeline.outputs.new-version }}. 89 | delete-branch: true 90 | labels: semver:patch, type:task 91 | signoff: true 92 | title: Bump pipeline from ${{ steps.pipeline.outputs.old-version }} to ${{ steps.pipeline.outputs.new-version }} 93 | token: ${{ secrets.IMPLEMENTATION_GITHUB_TOKEN }} 94 | -------------------------------------------------------------------------------- /.github/workflows/update-pack-version.yml: -------------------------------------------------------------------------------- 1 | name: update-pack-version 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: 7 | - pack-release 8 | 9 | jobs: 10 | update-pack-version: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Retrieve latest pack version 15 | id: version 16 | run: | 17 | NEW_VERSION=$(curl -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/buildpacks/pack/releases/latest | jq .tag_name -r | cut -c 2-) 18 | echo "new_version=${NEW_VERSION}" >> ${GITHUB_OUTPUT} 19 | - name: Update setup-pack/action.yml with the new Pack version 20 | run: | 21 | sed -i -z "s/default: '[0-9]\{1,\}.[0-9]\{1,\}.[0-9]\{1,\}'/default: '${{ steps.version.outputs.new_version }}'/" setup-pack/action.yml 22 | - name: Create pull request 23 | uses: peter-evans/create-pull-request@v5 24 | with: 25 | token: ${{ secrets.DISTRIBUTION_GITHUB_TOKEN }} 26 | commit-message: Update default Pack version to v${{ steps.version.outputs.new_version }} 27 | title: Update default Pack version to v${{ steps.version.outputs.new_version }} 28 | body: | 29 | Updates the `setup-pack` action's default `pack-version` to the latest Pack release. 30 | 31 | Release notes: 32 | https://github.com/buildpacks/pack/releases/tag/v${{ steps.version.outputs.new_version }} 33 | labels: | 34 | semver:patch 35 | type:dependency-upgrade 36 | branch: update-version 37 | base: main 38 | signoff: true 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2020 the original author or authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | bin/ 16 | linux/ 17 | dependencies/ 18 | package/ 19 | scratch/ 20 | 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 as build-stage 2 | 3 | WORKDIR /src 4 | ENV GO111MODULE=on CGO_ENABLED=0 5 | 6 | COPY . . 7 | 8 | ARG SOURCE 9 | RUN go build \ 10 | -trimpath \ 11 | -ldflags "-s -w -extldflags '-static'" \ 12 | -tags netgo \ 13 | -o /bin/action \ 14 | $SOURCE/main.go 15 | 16 | RUN strip /bin/action 17 | 18 | FROM scratch 19 | COPY --from=build-stage /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 20 | COPY --from=build-stage /bin/action /bin/action 21 | ENTRYPOINT ["/bin/action"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2020 The Cloud Native Buildpacks Authors 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | github-actions 2 | 3 | Copyright (c) the original author or authors. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions 2 | 3 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4748/badge)](https://bestpractices.coreinfrastructure.org/projects/4748) 4 | 5 | `github-actions` is a collection of end-user [GitHub Actions][gha] that integrate with Cloud Native Buildpacks projects. 6 | 7 | [gha]: https://docs.github.com/en/free-pro-team@latest/actions 8 | 9 | - [GitHub Actions](#github-actions) 10 | - [Buildpack](#buildpack) 11 | - [Compute Metadata Action](#compute-metadata-action) 12 | - [Buildpackage](#buildpackage) 13 | - [Verify Metadata Action](#verify-metadata-action) 14 | - [Registry](#registry) 15 | - [Add Entry Action](#add-entry-action) 16 | - [Compute Registry Metadata Action](#compute-registry-metadata-action) 17 | - [Request Add Entry Action](#request-add-entry-action) 18 | - [Request Yank Entry Action](#request-yank-entry-action) 19 | - [Verify Namespace Owner Action](#verify-namespace-owner-action) 20 | - [Yank Entry Action](#yank-entry-action) 21 | - [Setup pack CLI Action](#setup-pack-cli-action) 22 | - [Setup Tools Action](#setup-tools-action) 23 | - [License](#license) 24 | 25 | ## Buildpack 26 | 27 | ### Compute Metadata Action 28 | The `buildpack/compute-metadata` action parses a `buildpack.toml` and exposes the contents of the `[buildpack]` block as output parameters. 29 | 30 | ```yaml 31 | uses: docker://ghcr.io/buildpacks/actions/buildpack/compute-metadata 32 | ``` 33 | 34 | #### Inputs 35 | | Parameter | Description 36 | | :-------- | :---------- 37 | | `path` | Optional path to `buildpack.toml`. Defaults to `/buildpack.toml` 38 | 39 | #### Outputs 40 | | Parameter | Description 41 | | :-------- | :---------- 42 | | `id` | The contents of `buildpack.id` 43 | | `name` | The contents of `buildpack.name` 44 | | `version` | The contents of `buildpack.version` 45 | | `homepage` | The contents of `buildpack.homepage` 46 | 47 | ## Buildpackage 48 | 49 | ### Verify Metadata Action 50 | The `buildpackage/verify-metadata` action parses the metadata on a buildpackage and verifies that the `id` and `version` match expected values. 51 | 52 | ```yaml 53 | uses: docker://ghcr.io/buildpacks/actions/buildpackage/verify-metadata 54 | with: 55 | id: test-buildpack 56 | version: "1.0.0" 57 | address: ghcr.io/example/test-buildpack@sha256:04ba2d17480910bd340f0305d846b007148dafd64bc6fc2626870c174b7c7de7 58 | ``` 59 | 60 | #### Inputs 61 | | Parameter | Description 62 | | :-------- | :---------- 63 | | `id` | The expected `id` for the buildpackage 64 | | `version` | The expected `version` for the buildpackage 65 | | `address` | The digest-style address of the buildpackage to verify 66 | 67 | ## Registry 68 | [bri]: https://github.com/buildpacks/registry-index 69 | 70 | ### Add Entry Action 71 | The `registry/add-entry` adds an entry to the [Buildpack Registry Index][bri]. 72 | 73 | ```yaml 74 | uses: docker://ghcr.io/buildpacks/actions/registry/add-entry 75 | with: 76 | token: ${{ secrets.BOT_TOKEN }} 77 | owner: ${{ env.INDEX_OWNER }} 78 | repository: ${{ env.INDEX_REPOSITORY }} 79 | namespace: ${{ steps.metadata.outputs.namespace }} 80 | name: ${{ steps.metadata.outputs.name }} 81 | version: ${{ steps.metadata.outputs.version }} 82 | address: ${{ steps.metadata.outputs.address }} 83 | ``` 84 | 85 | #### Inputs 86 | | Parameter | Description 87 | | :-------- | :---------- 88 | | `token` | A GitHub token with permissions to commit to the registry index repository. 89 | | `owner` | The owner name of the registry index repository. 90 | | `repository` | The repository name of the registry index repository. 91 | | `namespace` | The namespace of the buildpack to register. 92 | | `name` | The name of the buildpack to register. 93 | | `version` | The version of the buildpack to register. 94 | | `address` | The address of the buildpack to register. 95 | 96 | ### Compute Registry Metadata Action 97 | The `registry/compute-metadata` action parses a [`buildpacks/registry-index`][bri] issue and exposes the contents as output parameters. 98 | 99 | ```yaml 100 | uses: docker://ghcr.io/buildpacks/actions/registry/compute-metadata 101 | with: 102 | issue: ${{ toJSON(github.events.issue) }} 103 | ``` 104 | 105 | #### Inputs 106 | | Parameter | Description 107 | | :-------- | :---------- 108 | | `issue` | The GitHub issue payload. 109 | 110 | #### Outputs 111 | | Parameter | Description 112 | | :-------- | :---------- 113 | | `id` | The contents of `id` 114 | | `version` | The contents of `version` 115 | | `address` | The contents of `addr` 116 | | `namespace` | The namespace portion of `id` 117 | | `name` | The name portion of `id` 118 | 119 | ### Request Add Entry Action 120 | The `registry/request-add-entry` action adds an entry to the [Buildpack Registry Index][bri]. 121 | 122 | ```yaml 123 | uses: docker://ghcr.io/buildpacks/actions/registry/request-add-entry 124 | with: 125 | token: ${{ secrets.IMPLEMENTATION_PAT }} 126 | id: $buildpacksio/test-buildpack 127 | version: ${{ steps.deploy.outputs.version }} 128 | address: index.docker.io/buildpacksio/test-buildpack@${{ steps.deploy.outputs.digest }} 129 | ``` 130 | 131 | #### Inputs 132 | | Parameter | Description 133 | | :-------- | :---------- 134 | | `token` | A GitHub token with `public_repo` scope to open an issue against [`buildpacks/registry-index`][bri]. 135 | | `id` | A buildpack id that your user is allowed to manage. This is must be in `{namespace}/{name}` format. 136 | | `version` | The version of the buildpack that is being added to the registry. 137 | | `address` | The Docker URI of the buildpack artifact. This is must be in `{host}/{repo}@{digest}` form. 138 | 139 | ### Request Yank Entry Action 140 | The `registry/request-yank-entry` action yanks an entry from the [Buildpack Registry Index][bri]. 141 | 142 | ```yaml 143 | uses: docker://ghcr.io/buildpacks/actions/registry/request-yank-entry 144 | with: 145 | token: ${{ secrets.IMPLEMENTATION_PAT }} 146 | id: buildpacksio/test-buildpack 147 | version: ${{ steps.deploy.outputs.version }} 148 | ``` 149 | 150 | #### Inputs 151 | | Parameter | Description 152 | | :-------- | :---------- 153 | | `token` | A GitHub token with `public_repo` scope to open an issue against [`buildpacks/registry-index`][bri]. 154 | | `id` | A buildpack id that your user is allowed to manage. This is must be in `{namespace}/{name}` format. 155 | | `version` | The version of the buildpack that is being added to the registry. 156 | 157 | ### Verify Namespace Owner Action 158 | The `registry/verify-namespace-owner` action verifies that a user is an owner of a namespace in the [Buildpack Registry Index][bri]. 159 | 160 | ```yaml 161 | uses: docker://ghcr.io/buildpacks/actions/registry/verify-namespace-owner 162 | with: 163 | token: ${{ secrets.BOT_TOKEN }} 164 | owner: ${{ env.NAMESPACES_OWNER }} 165 | repository: ${{ env.NAMESPACES_REPOSITORY }} 166 | namespace: ${{ steps.metadata.outputs.namespace }} 167 | user: ${{ toJSON(github.event.issue.user) }} 168 | add-if-missing: true 169 | ``` 170 | 171 | #### Inputs 172 | | Parameter | Description 173 | | :-------- | :---------- 174 | | `token` | A GitHub token with permissions to commit to the registry namespaces repository. 175 | | `owner` | The owner name of the registry namespaces repository. 176 | | `repository` | The repository name of the registry namespaces repository. 177 | | `namespace` | The namespace to check ownership for. 178 | | `user` | The Github user payload. 179 | | `add-if-missing` | Whether to add the current user as the owner of the namespace if that namespace does not exist. (Optional. Default `false`) 180 | 181 | ### Yank Entry Action 182 | The `registry/yank-entry` action yanks an entry from the [Buildpack Registry Index][bri]. 183 | 184 | ```yaml 185 | uses: docker://ghcr.io/buildpacks/actions/registry/yank-entry 186 | with: 187 | token: ${{ secrets.BOT_TOKEN }} 188 | owner: ${{ env.INDEX_OWNER }} 189 | repository: ${{ env.INDEX_REPOSITORY }} 190 | namespace: ${{ steps.metadata.outputs.namespace }} 191 | name: ${{ steps.metadata.outputs.name }} 192 | version: ${{ steps.metadata.outputs.version }} 193 | ``` 194 | 195 | #### Inputs 196 | | Parameter | Description 197 | | :-------- | :---------- 198 | | `token` | A GitHub token with permissions to commit to the registry index repository. 199 | | `owner` | The owner name of the registry index repository. 200 | | `repository` | The repository name of the registry index repository. 201 | | `namespace` | The namespace of the buildpack to register. 202 | | `name` | The name of the buildpack to register. 203 | | `version` | The version of the buildpack to register. 204 | 205 | ## Setup pack CLI Action 206 | The `setup-pack` action adds [`pack`][pack] to the environment. 207 | 208 | [pack]: https://github.com/buildpacks/pack 209 | 210 | ```yaml 211 | uses: buildpacks/github-actions/setup-pack@v5.0.0 212 | ``` 213 | 214 | #### Inputs 215 | | Parameter | Description 216 | | :-------- | :---------- 217 | | `pack-version` | Optional version of [`pack`][pack] to install. Defaults to latest release. 218 | 219 | ## Setup Tools Action 220 | The `setup-tools` action adds [crane][crane] and [`yj`][yj] to the environment. 221 | 222 | [crane]: https://github.com/google/go-containerregistry/tree/master/cmd/crane 223 | [yj]: https://github.com/sclevine/yj 224 | 225 | ```yaml 226 | uses: buildpacks/github-actions/setup-tools@v5.0.0 227 | ``` 228 | 229 | #### Inputs 230 | | Parameter | Description 231 | | :-------- | :---------- 232 | | `crane-version` | Optional version of [`crane`][crane] to install. Defaults to `0.12.1`. 233 | | `yj-version` | Optional version of [`yj`][yj] to install. Defaults to `5.1.0`. 234 | 235 | ## License 236 | This library is released under version 2.0 of the [Apache License][a]. 237 | 238 | [a]: https://www.apache.org/licenses/LICENSE-2.0 239 | -------------------------------------------------------------------------------- /buildpack/compute-metadata/cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/buildpacks/github-actions/buildpack/compute-metadata" 24 | "github.com/buildpacks/github-actions/internal/toolkit" 25 | ) 26 | 27 | func main() { 28 | if err := metadata.ComputeMetadata(&toolkit.DefaultToolkit{}); err != nil { 29 | fmt.Println(err) 30 | os.Exit(1) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /buildpack/compute-metadata/compute_metadata.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package metadata 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | 23 | "github.com/buildpacks/libcnb" 24 | "github.com/pelletier/go-toml" 25 | 26 | "github.com/buildpacks/github-actions/internal/toolkit" 27 | ) 28 | 29 | func ComputeMetadata(tk toolkit.Toolkit) error { 30 | c := parseConfig(tk) 31 | 32 | b, err := ioutil.ReadFile(c.Path) 33 | if err != nil { 34 | return toolkit.FailedErrorf("unable to read %s", c.Path) 35 | } 36 | 37 | var bp libcnb.Buildpack 38 | if err := toml.Unmarshal(b, &bp); err != nil { 39 | return toolkit.FailedErrorf("unable to unmarshal %s", c.Path) 40 | } 41 | 42 | fmt.Printf(`Metadata: 43 | ID: %s 44 | Name: %s 45 | Version: %s 46 | Homepage: %s 47 | `, bp.Info.ID, bp.Info.Name, bp.Info.Version, bp.Info.Homepage) 48 | 49 | tk.SetOutput("id", bp.Info.ID) 50 | tk.SetOutput("name", bp.Info.Name) 51 | tk.SetOutput("version", bp.Info.Version) 52 | tk.SetOutput("homepage", bp.Info.Homepage) 53 | 54 | return nil 55 | } 56 | 57 | type config struct { 58 | Path string 59 | } 60 | 61 | func parseConfig(tk toolkit.Toolkit) config { 62 | c := config{Path: "buildpack.toml"} 63 | 64 | if s, ok := tk.GetInput("path"); ok { 65 | c.Path = s 66 | } 67 | 68 | return c 69 | } 70 | -------------------------------------------------------------------------------- /buildpack/compute-metadata/compute_metadata_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package metadata_test 18 | 19 | import ( 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/gomega" 24 | "github.com/sclevine/spec" 25 | "github.com/sclevine/spec/report" 26 | 27 | "github.com/buildpacks/github-actions/buildpack/compute-metadata" 28 | "github.com/buildpacks/github-actions/internal/toolkit" 29 | ) 30 | 31 | func TestComputeMetadata(t *testing.T) { 32 | spec.Run(t, "compute-metadata", func(t *testing.T, context spec.G, it spec.S) { 33 | var ( 34 | Expect = NewWithT(t).Expect 35 | 36 | tk = &toolkit.MockToolkit{} 37 | ) 38 | 39 | it.Before(func() { 40 | tk.On("GetInput", "path").Return(filepath.Join("testdata", "buildpack.toml"), true) 41 | }) 42 | 43 | it("computes metadata", func() { 44 | tk.On("SetOutput", "id", "test-id") 45 | tk.On("SetOutput", "name", "test-name") 46 | tk.On("SetOutput", "version", "test-version") 47 | tk.On("SetOutput", "homepage", "test-homepage") 48 | 49 | Expect(metadata.ComputeMetadata(tk)).To(Succeed()) 50 | }) 51 | }, spec.Report(report.Terminal{})) 52 | } 53 | -------------------------------------------------------------------------------- /buildpack/compute-metadata/testdata/buildpack.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2020 the original author or authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [buildpack] 16 | id = "test-id" 17 | name = "test-name" 18 | version = "test-version" 19 | homepage = "test-homepage" 20 | -------------------------------------------------------------------------------- /buildpackage/verify-metadata/cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/google/go-containerregistry/pkg/v1/remote" 24 | 25 | "github.com/buildpacks/github-actions/buildpackage/verify-metadata" 26 | "github.com/buildpacks/github-actions/internal/toolkit" 27 | ) 28 | 29 | func main() { 30 | if err := metadata.VerifyMetadata(&toolkit.DefaultToolkit{}, remote.Image); err != nil { 31 | fmt.Println(err) 32 | os.Exit(1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /buildpackage/verify-metadata/image_function.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package metadata 18 | 19 | import ( 20 | "github.com/google/go-containerregistry/pkg/name" 21 | "github.com/google/go-containerregistry/pkg/v1" 22 | "github.com/google/go-containerregistry/pkg/v1/remote" 23 | ) 24 | 25 | //go:generate mockery --all --output=./internal/mocks --case=underscore 26 | 27 | type ImageFunction func(name.Reference, ...remote.Option) (v1.Image, error) 28 | -------------------------------------------------------------------------------- /buildpackage/verify-metadata/internal/mocks/image_function.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.4.0-beta. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | name "github.com/google/go-containerregistry/pkg/name" 7 | remote "github.com/google/go-containerregistry/pkg/v1/remote" 8 | mock "github.com/stretchr/testify/mock" 9 | 10 | v1 "github.com/google/go-containerregistry/pkg/v1" 11 | ) 12 | 13 | // ImageFunction is an autogenerated mock type for the ImageFunction type 14 | type ImageFunction struct { 15 | mock.Mock 16 | } 17 | 18 | // Execute provides a mock function with given fields: _a0, _a1 19 | func (_m *ImageFunction) Execute(_a0 name.Reference, _a1 ...remote.Option) (v1.Image, error) { 20 | _va := make([]interface{}, len(_a1)) 21 | for _i := range _a1 { 22 | _va[_i] = _a1[_i] 23 | } 24 | var _ca []interface{} 25 | _ca = append(_ca, _a0) 26 | _ca = append(_ca, _va...) 27 | ret := _m.Called(_ca...) 28 | 29 | var r0 v1.Image 30 | if rf, ok := ret.Get(0).(func(name.Reference, ...remote.Option) v1.Image); ok { 31 | r0 = rf(_a0, _a1...) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).(v1.Image) 35 | } 36 | } 37 | 38 | var r1 error 39 | if rf, ok := ret.Get(1).(func(name.Reference, ...remote.Option) error); ok { 40 | r1 = rf(_a0, _a1...) 41 | } else { 42 | r1 = ret.Error(1) 43 | } 44 | 45 | return r0, r1 46 | } 47 | -------------------------------------------------------------------------------- /buildpackage/verify-metadata/verify_metadata.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package metadata 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "strings" 23 | 24 | "github.com/google/go-containerregistry/pkg/name" 25 | 26 | "github.com/buildpacks/github-actions/internal/toolkit" 27 | ) 28 | 29 | const MetadataLabel = "io.buildpacks.buildpackage.metadata" 30 | 31 | func VerifyMetadata(tk toolkit.Toolkit, imageFn ImageFunction) error { 32 | c, err := parseConfig(tk) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | ref, err := name.ParseReference(c.Address) 38 | if err != nil { 39 | return toolkit.FailedErrorf("unable to parse address %s as image reference", c.Address) 40 | } 41 | 42 | if _, ok := ref.(name.Digest); !ok { 43 | return toolkit.FailedErrorf("address %s must be in digest form /@sh256:", c.Address) 44 | } 45 | 46 | image, err := imageFn(ref) 47 | if err != nil { 48 | return toolkit.FailedErrorf("unable to retrieve image %s", c.Address) 49 | } 50 | 51 | configFile, err := image.ConfigFile() 52 | if err != nil { 53 | return toolkit.FailedErrorf("unable to retrieve config file\n%w", err) 54 | } 55 | 56 | raw, ok := configFile.Config.Labels[MetadataLabel] 57 | if !ok { 58 | return toolkit.FailedErrorf("unable to retrieve %s label", MetadataLabel) 59 | } 60 | 61 | var m metadata 62 | if err := json.Unmarshal([]byte(raw), &m); err != nil { 63 | return toolkit.FailedErrorf("unable to unmarshal %s label", MetadataLabel) 64 | } 65 | 66 | if c.ID != m.ID { 67 | return toolkit.FailedErrorf("invalid id in buildpackage: expected %s, found %s", c.ID, m.ID) 68 | } 69 | 70 | if c.Version != m.Version { 71 | return toolkit.FailedErrorf("invalid version in buildpackage: expected %s, found %s", c.Version, m.Version) 72 | 73 | } 74 | 75 | var stacks []string 76 | for _, s := range m.Stacks { 77 | stacks = append(stacks, s.ID) 78 | } 79 | 80 | fmt.Printf(`Verified %s 81 | ID: %s 82 | Version: %s 83 | Homepage: %s 84 | Stacks: %s 85 | `, c.Address, m.ID, m.Version, m.Homepage, strings.Join(stacks, ", ")) 86 | 87 | return nil 88 | } 89 | 90 | type config struct { 91 | ID string 92 | Version string 93 | Address string 94 | } 95 | 96 | func parseConfig(tk toolkit.Toolkit) (config, error) { 97 | var ( 98 | c config 99 | ok bool 100 | ) 101 | 102 | c.ID, ok = tk.GetInput("id") 103 | if !ok { 104 | return config{}, toolkit.FailedError("id must be specified") 105 | } 106 | 107 | c.Version, ok = tk.GetInput("version") 108 | if !ok { 109 | return config{}, toolkit.FailedError("version must be specified") 110 | } 111 | 112 | c.Address, ok = tk.GetInput("address") 113 | if !ok { 114 | return config{}, toolkit.FailedError("address must be specified") 115 | } 116 | 117 | return c, nil 118 | } 119 | 120 | type metadata struct { 121 | ID string 122 | Version string 123 | Homepage string 124 | Stacks []stack 125 | } 126 | 127 | type stack struct { 128 | ID string 129 | } 130 | -------------------------------------------------------------------------------- /buildpackage/verify-metadata/verify_metadata_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package metadata_test 18 | 19 | import ( 20 | "testing" 21 | 22 | v1 "github.com/google/go-containerregistry/pkg/v1" 23 | "github.com/google/go-containerregistry/pkg/v1/fake" 24 | . "github.com/onsi/gomega" 25 | "github.com/sclevine/spec" 26 | "github.com/sclevine/spec/report" 27 | "github.com/stretchr/testify/mock" 28 | 29 | "github.com/buildpacks/github-actions/buildpackage/verify-metadata" 30 | "github.com/buildpacks/github-actions/buildpackage/verify-metadata/internal/mocks" 31 | "github.com/buildpacks/github-actions/internal/toolkit" 32 | ) 33 | 34 | func TestVerifyMetadata(t *testing.T) { 35 | spec.Run(t, "verify-metadata", func(t *testing.T, context spec.G, it spec.S) { 36 | var ( 37 | Expect = NewWithT(t).Expect 38 | 39 | f = &mocks.ImageFunction{} 40 | i = &fake.FakeImage{} 41 | 42 | tk = &toolkit.MockToolkit{} 43 | ) 44 | 45 | it.Before(func() { 46 | tk.On("GetInput", "id").Return("test-id", true) 47 | tk.On("GetInput", "version").Return("test-version", true) 48 | }) 49 | 50 | it("fails if address is not a digest image reference", func() { 51 | tk.On("GetInput", "address").Return("test-host/test-repository:test-version", true) 52 | 53 | Expect(metadata.VerifyMetadata(tk, f.Execute)). 54 | To(MatchError("::error ::address test-host/test-repository:test-version must be in digest form /@sh256:")) 55 | }) 56 | 57 | context("valid address", func() { 58 | it.Before(func() { 59 | tk.On("GetInput", "address").Return("host/repository@sha256:04ba2d17480910bd340f0305d846b007148dafd64bc6fc2626870c174b7c7de7", true) 60 | f.On("Execute", mock.Anything).Return(i, nil) 61 | }) 62 | 63 | it("fails if io.buildpacks.buildpackage.metadata is not on image", func() { 64 | i.ConfigFileReturns(&v1.ConfigFile{ 65 | Config: v1.Config{ 66 | Labels: map[string]string{}, 67 | }, 68 | }, nil) 69 | 70 | Expect(metadata.VerifyMetadata(tk, f.Execute)). 71 | To(MatchError("::error ::unable to retrieve io.buildpacks.buildpackage.metadata label")) 72 | }) 73 | 74 | it("fails if id does not match", func() { 75 | i.ConfigFileReturns(&v1.ConfigFile{ 76 | Config: v1.Config{ 77 | Labels: map[string]string{metadata.MetadataLabel: `{ "id": "another-id", "version": "test-version" }`}, 78 | }, 79 | }, nil) 80 | 81 | Expect(metadata.VerifyMetadata(tk, f.Execute)). 82 | To(MatchError("::error ::invalid id in buildpackage: expected test-id, found another-id")) 83 | }) 84 | 85 | it("fails if version does not match", func() { 86 | i.ConfigFileReturns(&v1.ConfigFile{ 87 | Config: v1.Config{ 88 | Labels: map[string]string{metadata.MetadataLabel: `{ "id": "test-id", "version": "another-version" }`}, 89 | }, 90 | }, nil) 91 | 92 | Expect(metadata.VerifyMetadata(tk, f.Execute)). 93 | To(MatchError("::error ::invalid version in buildpackage: expected test-version, found another-version")) 94 | }) 95 | 96 | it("passes if version and id match", func() { 97 | i.ConfigFileReturns(&v1.ConfigFile{ 98 | Config: v1.Config{ 99 | Labels: map[string]string{metadata.MetadataLabel: `{ "id": "test-id", "version": "test-version" }`}, 100 | }, 101 | }, nil) 102 | 103 | Expect(metadata.VerifyMetadata(tk, f.Execute)).To(Succeed()) 104 | }) 105 | 106 | }) 107 | 108 | }, spec.Report(report.Terminal{})) 109 | } 110 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/buildpacks/github-actions 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.8 6 | 7 | require ( 8 | github.com/buildpacks/libcnb v1.30.4 9 | github.com/google/go-containerregistry v0.20.3 10 | github.com/google/go-github/v39 v39.2.0 11 | github.com/onsi/gomega v1.37.0 12 | github.com/pelletier/go-toml v1.9.5 13 | github.com/sclevine/spec v1.4.0 14 | github.com/stretchr/testify v1.10.0 15 | golang.org/x/oauth2 v0.30.0 16 | gopkg.in/retry.v1 v1.0.3 17 | ) 18 | 19 | require ( 20 | github.com/BurntSushi/toml v1.5.0 // indirect 21 | github.com/Masterminds/semver/v3 v3.3.1 // indirect 22 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/docker/cli v28.1.1+incompatible // indirect 25 | github.com/docker/distribution v2.8.3+incompatible // indirect 26 | github.com/docker/docker-credential-helpers v0.9.3 // indirect 27 | github.com/google/go-cmp v0.7.0 // indirect 28 | github.com/google/go-querystring v1.1.0 // indirect 29 | github.com/klauspost/compress v1.18.0 // indirect 30 | github.com/mitchellh/go-homedir v1.1.0 // indirect 31 | github.com/opencontainers/go-digest v1.0.0 // indirect 32 | github.com/opencontainers/image-spec v1.1.1 // indirect 33 | github.com/pkg/errors v0.9.1 // indirect 34 | github.com/pmezard/go-difflib v1.0.0 // indirect 35 | github.com/sirupsen/logrus v1.9.3 // indirect 36 | github.com/stretchr/objx v0.5.2 // indirect 37 | github.com/vbatts/tar-split v0.12.1 // indirect 38 | golang.org/x/crypto v0.38.0 // indirect 39 | golang.org/x/net v0.40.0 // indirect 40 | golang.org/x/sync v0.14.0 // indirect 41 | golang.org/x/sys v0.33.0 // indirect 42 | golang.org/x/text v0.25.0 // indirect 43 | gopkg.in/yaml.v3 v3.0.1 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 2 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 3 | github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= 4 | github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= 5 | github.com/buildpacks/libcnb v1.30.4 h1:Jp6cJxYsZQgqix+lpRdSpjHt5bv5yCJqgkw9zWmS6xU= 6 | github.com/buildpacks/libcnb v1.30.4/go.mod h1:vjEDAlK3/Rf67AcmBzphXoqIlbdFgBNUK5d8wjreJbY= 7 | github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= 8 | github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k= 13 | github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 14 | github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= 15 | github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 16 | github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= 17 | github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= 18 | github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= 19 | github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= 20 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 21 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 22 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 23 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 24 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 25 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 27 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 30 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 31 | github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= 32 | github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= 33 | github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ= 34 | github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= 35 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 36 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 37 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= 38 | github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 39 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 40 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 41 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 42 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 43 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 44 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 45 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 46 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 47 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 48 | github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= 49 | github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= 50 | github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= 51 | github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= 52 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 53 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 54 | github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= 55 | github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= 56 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 57 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 58 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 59 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 60 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 61 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 62 | github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a h1:3QH7VyOaaiUHNrA9Se4YQIRkDTCw1EJls9xTUCaCeRM= 63 | github.com/rogpeppe/clock v0.0.0-20190514195947-2896927a307a/go.mod h1:4r5QyqhjIWCcK8DO4KMclc5Iknq5qVBAlbYYzAbUScQ= 64 | github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= 65 | github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= 66 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 67 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 68 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 69 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 70 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 71 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 72 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 73 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 74 | github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= 75 | github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= 76 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 77 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 78 | golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 79 | golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= 80 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 81 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 82 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 83 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 84 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 85 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 86 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 87 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 88 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 89 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 90 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 91 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 92 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 94 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 95 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 96 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 97 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 98 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 99 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 100 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 101 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 102 | golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= 103 | golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= 104 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 105 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 106 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 107 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 108 | gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs= 109 | gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g= 110 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 111 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 112 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 113 | gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= 114 | gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= 115 | -------------------------------------------------------------------------------- /internal/toolkit/mock_toolkit.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v2.4.0-beta. DO NOT EDIT. 2 | 3 | package toolkit 4 | 5 | import mock "github.com/stretchr/testify/mock" 6 | 7 | // MockToolkit is an autogenerated mock type for the Toolkit type 8 | type MockToolkit struct { 9 | mock.Mock 10 | } 11 | 12 | // AddMask provides a mock function with given fields: mask 13 | func (_m *MockToolkit) AddMask(mask string) { 14 | _m.Called(mask) 15 | } 16 | 17 | // AddPath provides a mock function with given fields: paths 18 | func (_m *MockToolkit) AddPath(paths ...string) error { 19 | _va := make([]interface{}, len(paths)) 20 | for _i := range paths { 21 | _va[_i] = paths[_i] 22 | } 23 | var _ca []interface{} 24 | _ca = append(_ca, _va...) 25 | ret := _m.Called(_ca...) 26 | 27 | var r0 error 28 | if rf, ok := ret.Get(0).(func(...string) error); ok { 29 | r0 = rf(paths...) 30 | } else { 31 | r0 = ret.Error(0) 32 | } 33 | 34 | return r0 35 | } 36 | 37 | // Debug provides a mock function with given fields: a 38 | func (_m *MockToolkit) Debug(a ...interface{}) { 39 | var _ca []interface{} 40 | _ca = append(_ca, a...) 41 | _m.Called(_ca...) 42 | } 43 | 44 | // Debugf provides a mock function with given fields: format, a 45 | func (_m *MockToolkit) Debugf(format string, a ...interface{}) { 46 | var _ca []interface{} 47 | _ca = append(_ca, format) 48 | _ca = append(_ca, a...) 49 | _m.Called(_ca...) 50 | } 51 | 52 | // EndGroup provides a mock function with given fields: 53 | func (_m *MockToolkit) EndGroup() { 54 | _m.Called() 55 | } 56 | 57 | // Error provides a mock function with given fields: a 58 | func (_m *MockToolkit) Error(a ...interface{}) { 59 | var _ca []interface{} 60 | _ca = append(_ca, a...) 61 | _m.Called(_ca...) 62 | } 63 | 64 | // Errorc provides a mock function with given fields: context 65 | func (_m *MockToolkit) Errorc(context MessageContext) { 66 | _m.Called(context) 67 | } 68 | 69 | // Errorf provides a mock function with given fields: format, a 70 | func (_m *MockToolkit) Errorf(format string, a ...interface{}) { 71 | var _ca []interface{} 72 | _ca = append(_ca, format) 73 | _ca = append(_ca, a...) 74 | _m.Called(_ca...) 75 | } 76 | 77 | // ExportVariable provides a mock function with given fields: name, value 78 | func (_m *MockToolkit) ExportVariable(name string, value string) error { 79 | ret := _m.Called(name, value) 80 | 81 | var r0 error 82 | if rf, ok := ret.Get(0).(func(string, string) error); ok { 83 | r0 = rf(name, value) 84 | } else { 85 | r0 = ret.Error(0) 86 | } 87 | 88 | return r0 89 | } 90 | 91 | // GetInput provides a mock function with given fields: name 92 | func (_m *MockToolkit) GetInput(name string) (string, bool) { 93 | ret := _m.Called(name) 94 | 95 | var r0 string 96 | if rf, ok := ret.Get(0).(func(string) string); ok { 97 | r0 = rf(name) 98 | } else { 99 | r0 = ret.Get(0).(string) 100 | } 101 | 102 | var r1 bool 103 | if rf, ok := ret.Get(1).(func(string) bool); ok { 104 | r1 = rf(name) 105 | } else { 106 | r1 = ret.Get(1).(bool) 107 | } 108 | 109 | return r0, r1 110 | } 111 | 112 | // GetInputList provides a mock function with given fields: name 113 | func (_m *MockToolkit) GetInputList(name string) ([]string, bool) { 114 | ret := _m.Called(name) 115 | 116 | var r0 []string 117 | if rf, ok := ret.Get(0).(func(string) []string); ok { 118 | r0 = rf(name) 119 | } else { 120 | if ret.Get(0) != nil { 121 | r0 = ret.Get(0).([]string) 122 | } 123 | } 124 | 125 | var r1 bool 126 | if rf, ok := ret.Get(1).(func(string) bool); ok { 127 | r1 = rf(name) 128 | } else { 129 | r1 = ret.Get(1).(bool) 130 | } 131 | 132 | return r0, r1 133 | } 134 | 135 | // GetState provides a mock function with given fields: name 136 | func (_m *MockToolkit) GetState(name string) (string, bool) { 137 | ret := _m.Called(name) 138 | 139 | var r0 string 140 | if rf, ok := ret.Get(0).(func(string) string); ok { 141 | r0 = rf(name) 142 | } else { 143 | r0 = ret.Get(0).(string) 144 | } 145 | 146 | var r1 bool 147 | if rf, ok := ret.Get(1).(func(string) bool); ok { 148 | r1 = rf(name) 149 | } else { 150 | r1 = ret.Get(1).(bool) 151 | } 152 | 153 | return r0, r1 154 | } 155 | 156 | // IsDebug provides a mock function with given fields: 157 | func (_m *MockToolkit) IsDebug() bool { 158 | ret := _m.Called() 159 | 160 | var r0 bool 161 | if rf, ok := ret.Get(0).(func() bool); ok { 162 | r0 = rf() 163 | } else { 164 | r0 = ret.Get(0).(bool) 165 | } 166 | 167 | return r0 168 | } 169 | 170 | // SetOutput provides a mock function with given fields: name, value 171 | func (_m *MockToolkit) SetOutput(name string, value string) { 172 | _m.Called(name, value) 173 | } 174 | 175 | // SetState provides a mock function with given fields: name, value 176 | func (_m *MockToolkit) SetState(name string, value string) { 177 | _m.Called(name, value) 178 | } 179 | 180 | // StartGroup provides a mock function with given fields: title 181 | func (_m *MockToolkit) StartGroup(title string) { 182 | _m.Called(title) 183 | } 184 | 185 | // Warning provides a mock function with given fields: a 186 | func (_m *MockToolkit) Warning(a ...interface{}) { 187 | var _ca []interface{} 188 | _ca = append(_ca, a...) 189 | _m.Called(_ca...) 190 | } 191 | 192 | // Warningc provides a mock function with given fields: context 193 | func (_m *MockToolkit) Warningc(context MessageContext) { 194 | _m.Called(context) 195 | } 196 | 197 | // Warningf provides a mock function with given fields: format, a 198 | func (_m *MockToolkit) Warningf(format string, a ...interface{}) { 199 | var _ca []interface{} 200 | _ca = append(_ca, format) 201 | _ca = append(_ca, a...) 202 | _m.Called(_ca...) 203 | } 204 | -------------------------------------------------------------------------------- /internal/toolkit/toolkit.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package toolkit 18 | 19 | import ( 20 | "crypto/rand" 21 | "encoding/hex" 22 | "errors" 23 | "fmt" 24 | "io" 25 | "os" 26 | "strconv" 27 | "strings" 28 | "sync" 29 | ) 30 | 31 | //go:generate mockery --all --inpackage --case=underscore 32 | 33 | type Toolkit interface { 34 | AddPath(paths ...string) error 35 | ExportVariable(name string, value string) error 36 | 37 | GetInput(name string) (string, bool) 38 | GetInputList(name string) ([]string, bool) 39 | SetOutput(name string, value string) 40 | GetState(name string) (string, bool) 41 | SetState(name string, value string) 42 | AddMask(mask string) 43 | 44 | StartGroup(title string) 45 | EndGroup() 46 | 47 | IsDebug() bool 48 | Debug(a ...interface{}) 49 | Debugf(format string, a ...interface{}) 50 | Warning(a ...interface{}) 51 | Warningc(context MessageContext) 52 | Warningf(format string, a ...interface{}) 53 | Error(a ...interface{}) 54 | Errorc(context MessageContext) 55 | Errorf(format string, a ...interface{}) 56 | } 57 | 58 | type MessageContext struct { 59 | File string 60 | Line string 61 | Column string 62 | Message string 63 | } 64 | 65 | func (m *MessageContext) String() string { 66 | var s []string 67 | if m.File != "" { 68 | s = append(s, fmt.Sprintf("file=%s", m.File)) 69 | } 70 | if m.Line != "" { 71 | s = append(s, fmt.Sprintf("line=%s", m.Line)) 72 | } 73 | if m.Column != "" { 74 | s = append(s, fmt.Sprintf("col=%s", m.Column)) 75 | } 76 | 77 | return fmt.Sprintf("%s::%s", strings.Join(s, ","), escape(m.Message)) 78 | } 79 | 80 | func FailedError(a ...interface{}) error { 81 | return errors.New(errorString(a...)) 82 | } 83 | 84 | func FailedErrorc(context MessageContext) error { 85 | return errors.New(errorStringc(context)) 86 | } 87 | 88 | func FailedErrorf(format string, a ...interface{}) error { 89 | return errors.New(errorStringf(format, a...)) 90 | } 91 | 92 | type DefaultToolkit struct { 93 | once sync.Once 94 | 95 | Environment map[string]string 96 | Writer io.Writer 97 | Delemiter string 98 | } 99 | 100 | func (d *DefaultToolkit) AddPath(paths ...string) error { 101 | d.once.Do(d.init) 102 | 103 | path, ok := d.Environment["GITHUB_PATH"] 104 | if !ok { 105 | return FailedError("$GITHUB_PATH must be set") 106 | } 107 | 108 | f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 109 | if err != nil { 110 | return FailedErrorf("unable to open %s\n%w", path, err) 111 | } 112 | defer f.Close() 113 | 114 | for _, p := range paths { 115 | _, _ = fmt.Fprintln(f, p) 116 | } 117 | 118 | return nil 119 | } 120 | 121 | func (d *DefaultToolkit) ExportVariable(name string, value string) error { 122 | return d.export("GITHUB_ENV", name, value) 123 | } 124 | 125 | func (d *DefaultToolkit) export(env string, name string, value string) error { 126 | d.once.Do(d.init) 127 | 128 | path, ok := d.Environment[env] 129 | if !ok { 130 | return FailedErrorf("$%s must be set", env) 131 | } 132 | 133 | f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 134 | if err != nil { 135 | return FailedErrorf("unable to open %s\n%w", path, err) 136 | } 137 | defer f.Close() 138 | 139 | if strings.ContainsRune(value, '\n') { 140 | if _, err := fmt.Fprintln(f, fmt.Sprintf("%s<<%s\n%s\n%s", name, d.Delemiter, value, d.Delemiter)); err != nil { 141 | return FailedError("unable to write variable") 142 | } 143 | } else { 144 | _, _ = fmt.Fprintln(f, fmt.Sprintf("%s=%s", name, value)) 145 | } 146 | 147 | return nil 148 | } 149 | 150 | func (d *DefaultToolkit) GetInput(name string) (string, bool) { 151 | d.once.Do(d.init) 152 | s, ok := d.Environment[fmt.Sprintf("INPUT_%s", strings.ToUpper(name))] 153 | return s, ok 154 | } 155 | 156 | func (d *DefaultToolkit) GetInputList(name string) ([]string, bool) { 157 | d.once.Do(d.init) 158 | s, ok := d.Environment[fmt.Sprintf("INPUT_%s", strings.ToUpper(name))] 159 | ss := strings.Split(s, ",") 160 | return ss, ok 161 | } 162 | 163 | func (d *DefaultToolkit) SetOutput(name string, value string) { 164 | err := d.export("GITHUB_OUTPUT", name, value) 165 | if err != nil { 166 | panic(err) 167 | } 168 | } 169 | 170 | func (d *DefaultToolkit) GetState(name string) (string, bool) { 171 | d.once.Do(d.init) 172 | s, ok := d.Environment[fmt.Sprintf("STATE_%s", strings.ToUpper(name))] 173 | return s, ok 174 | } 175 | 176 | func (d *DefaultToolkit) SetState(name string, value string) { 177 | err := d.export("GITHUB_STATE", name, value) 178 | if err != nil { 179 | panic(err) 180 | } 181 | } 182 | 183 | func (d *DefaultToolkit) AddMask(mask string) { 184 | d.once.Do(d.init) 185 | _, _ = fmt.Fprintf(d.Writer, "::add-mask::%s\n", escape(mask)) 186 | } 187 | 188 | func (d *DefaultToolkit) StartGroup(title string) { 189 | d.once.Do(d.init) 190 | _, _ = fmt.Fprintf(d.Writer, "::group::%s\n", title) 191 | } 192 | 193 | func (d *DefaultToolkit) EndGroup() { 194 | d.once.Do(d.init) 195 | _, _ = fmt.Fprintln(d.Writer, "::endgroup::") 196 | } 197 | 198 | func (d *DefaultToolkit) IsDebug() bool { 199 | d.once.Do(d.init) 200 | 201 | t, err := strconv.ParseBool(d.Environment["RUNNER_DEBUG"]) 202 | if err != nil { 203 | return false 204 | } 205 | 206 | return t 207 | } 208 | 209 | func (d *DefaultToolkit) Debug(a ...interface{}) { 210 | d.once.Do(d.init) 211 | _, _ = fmt.Fprintf(d.Writer, "::debug::%s\n", escape(fmt.Sprint(a...))) 212 | } 213 | 214 | func (d *DefaultToolkit) Debugf(format string, a ...interface{}) { 215 | d.once.Do(d.init) 216 | _, _ = fmt.Fprintf(d.Writer, "::debug::%s\n", escape(fmt.Sprintf(format, a...))) 217 | } 218 | 219 | func (d *DefaultToolkit) Error(a ...interface{}) { 220 | d.once.Do(d.init) 221 | _, _ = fmt.Fprintln(d.Writer, errorString(a...)) 222 | } 223 | 224 | func (d *DefaultToolkit) Errorc(context MessageContext) { 225 | d.once.Do(d.init) 226 | _, _ = fmt.Fprintln(d.Writer, errorStringc(context)) 227 | } 228 | 229 | func (d *DefaultToolkit) Errorf(format string, a ...interface{}) { 230 | d.once.Do(d.init) 231 | _, _ = fmt.Fprintln(d.Writer, errorStringf(format, a...)) 232 | } 233 | 234 | func (d *DefaultToolkit) Warning(a ...interface{}) { 235 | d.once.Do(d.init) 236 | _, _ = fmt.Fprintf(d.Writer, "::warning ::%s\n", escape(fmt.Sprint(a...))) 237 | } 238 | 239 | func (d *DefaultToolkit) Warningc(context MessageContext) { 240 | d.once.Do(d.init) 241 | _, _ = fmt.Fprintln(d.Writer, "::warning", escape(context.String())) 242 | } 243 | 244 | func (d *DefaultToolkit) Warningf(format string, a ...interface{}) { 245 | d.once.Do(d.init) 246 | _, _ = fmt.Fprintf(d.Writer, "::warning ::%s\n", escape(fmt.Sprintf(format, a...))) 247 | } 248 | 249 | func (d *DefaultToolkit) init() { 250 | if d.Environment == nil { 251 | d.Environment = make(map[string]string) 252 | 253 | for _, s := range os.Environ() { 254 | t := strings.SplitN(s, "=", 2) 255 | d.Environment[t[0]] = t[1] 256 | } 257 | } 258 | 259 | if d.Writer == nil { 260 | d.Writer = os.Stdout 261 | } 262 | 263 | if d.Delemiter == "" { 264 | data := make([]byte, 16) // roughly the same entropy as uuid v4 used in https://github.com/actions/toolkit/blob/b36e70495fbee083eb20f600eafa9091d832577d/packages/core/src/file-command.ts#L28 265 | _, err := rand.Read(data) 266 | if err != nil { 267 | panic(fmt.Errorf("could not generate random delimiter: %w", err)) 268 | } 269 | d.Delemiter = hex.EncodeToString(data) 270 | } 271 | } 272 | 273 | func errorString(a ...interface{}) string { 274 | return fmt.Sprintf("::error ::%s", escape(fmt.Sprint(a...))) 275 | } 276 | 277 | func errorStringc(context MessageContext) string { 278 | return fmt.Sprintf("::error %s", context.String()) 279 | } 280 | 281 | func errorStringf(format string, a ...interface{}) string { 282 | return fmt.Sprintf("::error ::%s", escape(fmt.Errorf(format, a...).Error())) 283 | } 284 | 285 | func escape(s string) string { 286 | return strings.ReplaceAll(s, "\n", "%0A") 287 | } 288 | -------------------------------------------------------------------------------- /internal/toolkit/toolkit_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018-2020 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package toolkit_test 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "os" 23 | "testing" 24 | 25 | . "github.com/onsi/gomega" 26 | "github.com/sclevine/spec" 27 | "github.com/sclevine/spec/report" 28 | 29 | "github.com/buildpacks/github-actions/internal/toolkit" 30 | ) 31 | 32 | func TestToolkit(t *testing.T) { 33 | spec.Run(t, "Toolkit", func(t *testing.T, context spec.G, it spec.S) { 34 | var ( 35 | Expect = NewWithT(t).Expect 36 | ) 37 | 38 | context("MessageContext", func() { 39 | 40 | it("renders string", func() { 41 | mc := toolkit.MessageContext{Message: "test-message-1\ntest-message-2"} 42 | Expect(mc.String()).To(Equal("::test-message-1%0Atest-message-2")) 43 | 44 | mc.File = "test-file" 45 | Expect(mc.String()).To(Equal("file=test-file::test-message-1%0Atest-message-2")) 46 | 47 | mc.Line = "test-line" 48 | Expect(mc.String()).To(Equal("file=test-file,line=test-line::test-message-1%0Atest-message-2")) 49 | 50 | mc.Column = "test-column" 51 | Expect(mc.String()).To(Equal("file=test-file,line=test-line,col=test-column::test-message-1%0Atest-message-2")) 52 | }) 53 | 54 | }) 55 | 56 | context("FailedError", func() { 57 | 58 | it("returns failed error", func() { 59 | Expect(toolkit.FailedError("test-message-1", "test-message-2\ntest-message-3")). 60 | To(MatchError("::error ::test-message-1test-message-2%0Atest-message-3")) 61 | }) 62 | 63 | it("returns failed errorc", func() { 64 | Expect(toolkit.FailedErrorc(toolkit.MessageContext{ 65 | File: "test-file", 66 | Line: "test-line", 67 | Column: "test-column", 68 | Message: "test-message-1 test-message-2\ntest-message-3", 69 | })). 70 | To(MatchError("::error file=test-file,line=test-line,col=test-column::test-message-1 test-message-2%0Atest-message-3")) 71 | }) 72 | 73 | it("returns failed errorf", func() { 74 | Expect(toolkit.FailedErrorf("%s %s\n%s", "test-message-1", "test-message-2", "test-message-3")). 75 | To(MatchError("::error ::test-message-1 test-message-2%0Atest-message-3")) 76 | }) 77 | 78 | }) 79 | 80 | context("DefaultToolkit", func() { 81 | 82 | var ( 83 | b = &bytes.Buffer{} 84 | tk = toolkit.DefaultToolkit{Writer: b, Delemiter: "EOF"} 85 | ) 86 | 87 | it("adds path", func() { 88 | f, err := os.CreateTemp("", "github-path") 89 | Expect(err).NotTo(HaveOccurred()) 90 | _, err = fmt.Fprintln(f, "test-value") 91 | Expect(err).NotTo(HaveOccurred()) 92 | Expect(f.Close()).To(Succeed()) 93 | 94 | tk.Environment = map[string]string{"GITHUB_PATH": f.Name()} 95 | 96 | Expect(tk.AddPath("test-path-1", "test-path-2")).To(Succeed()) 97 | 98 | b, err := os.ReadFile(f.Name()) 99 | Expect(err).NotTo(HaveOccurred()) 100 | Expect(string(b)).To(Equal("test-value\ntest-path-1\ntest-path-2\n")) 101 | }) 102 | 103 | it("exports variable", func() { 104 | f, err := os.CreateTemp("", "github-env") 105 | Expect(err).NotTo(HaveOccurred()) 106 | _, err = fmt.Fprintln(f, "TEST_KEY=test-value") 107 | Expect(err).NotTo(HaveOccurred()) 108 | Expect(f.Close()).To(Succeed()) 109 | 110 | tk.Environment = map[string]string{"GITHUB_ENV": f.Name()} 111 | 112 | Expect(tk.ExportVariable("TEST_NAME_1", "test-value-1")).To(Succeed()) 113 | Expect(tk.ExportVariable("TEST_NAME_2", "test-value-2\ntest-value-3")).To(Succeed()) 114 | 115 | b, err := os.ReadFile(f.Name()) 116 | Expect(err).NotTo(HaveOccurred()) 117 | Expect(string(b)).To(Equal("TEST_KEY=test-value\nTEST_NAME_1=test-value-1\nTEST_NAME_2<> "${GITHUB_ENV}" 23 | 24 | PLATFORM="linux" 25 | if [ $(arch) = "aarch64" ]; then 26 | PLATFORM="linux-arm64" 27 | fi 28 | 29 | PACK_VERSION=${{ inputs.pack-version }} 30 | echo "Installing pack ${PACK_VERSION}" 31 | curl \ 32 | --show-error \ 33 | --silent \ 34 | --location \ 35 | --fail \ 36 | --retry 3 \ 37 | --connect-timeout 5 \ 38 | --max-time 60 \ 39 | "https://github.com/buildpacks/pack/releases/download/v${PACK_VERSION}/pack-v${PACK_VERSION}-${PLATFORM}.tgz" \ 40 | | tar -C "${HOME}/bin" -xz pack 41 | -------------------------------------------------------------------------------- /setup-tools/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup tools' 2 | description: 'Install the tools crane and yq, and add them to $PATH' 3 | author: 'Cloud Native Buildpacks' 4 | 5 | inputs: 6 | crane-version: 7 | description: 'The version of crane to install' 8 | required: false 9 | default: '0.19.1' 10 | yj-version: 11 | description: 'The version of yj to install' 12 | required: false 13 | default: '5.1.0' 14 | 15 | runs: 16 | using: "composite" 17 | steps: 18 | - name: Install additional buildpack management tools 19 | shell: bash 20 | run: | 21 | #!/usr/bin/env bash 22 | 23 | set -euo pipefail 24 | 25 | mkdir -p "${HOME}"/bin 26 | echo "PATH=${HOME}/bin:${PATH}" >> "${GITHUB_ENV}" 27 | 28 | CRANE_PLATFORM="Linux_x86_64" 29 | if [ $(arch) = "aarch64" ]; then 30 | CRANE_PLATFORM="Linux_arm64" 31 | fi 32 | CRANE_VERSION=${{ inputs.crane-version }} 33 | echo "Installing crane ${CRANE_VERSION}" 34 | curl \ 35 | --show-error \ 36 | --silent \ 37 | --location \ 38 | --fail \ 39 | --retry 3 \ 40 | --connect-timeout 5 \ 41 | --max-time 60 \ 42 | "https://github.com/google/go-containerregistry/releases/download/v${CRANE_VERSION}/go-containerregistry_${CRANE_PLATFORM}.tar.gz" \ 43 | | tar -C "${HOME}/bin" -xz crane 44 | 45 | YJ_VERSION=${{ inputs.yj-version }} 46 | echo "Installing yj ${YJ_VERSION}" 47 | YJ_DOWNLOAD_FILENAME="yj-linux" 48 | if [[ "${YJ_VERSION}" < "5.1.0" ]]; then 49 | YJ_PLATFORM="" 50 | else 51 | YJ_PLATFORM="-amd64" 52 | fi 53 | if [ $(arch) = "aarch64" ]; then 54 | YJ_PLATFORM="-arm64" 55 | fi 56 | curl \ 57 | --show-error \ 58 | --silent \ 59 | --location \ 60 | --fail \ 61 | --retry 3 \ 62 | --connect-timeout 5 \ 63 | --max-time 60 \ 64 | --output "${HOME}/bin/yj" \ 65 | "https://github.com/sclevine/yj/releases/download/v${YJ_VERSION}/${YJ_DOWNLOAD_FILENAME}${YJ_PLATFORM}" 66 | chmod +x "${HOME}"/bin/yj 67 | --------------------------------------------------------------------------------