diff --git a/.github/workflows/__shared-ci.yml b/.github/workflows/__shared-ci.yml index d8205cad..a1f53735 100644 --- a/.github/workflows/__shared-ci.yml +++ b/.github/workflows/__shared-ci.yml @@ -59,6 +59,12 @@ jobs: uses: ./.github/workflows/__test-workflow-docker-build-images-caching.yml secrets: inherit + test-workflow-docker-build-images-multi-registry: + name: Test docker build images - Multi registry inputs + needs: linter + uses: ./.github/workflows/__test-workflow-docker-build-images-multi-registry.yml + secrets: inherit + test-workflow-docker-build-images-platforms-and-signing: name: Test docker build images - Platforms and Signing needs: linter diff --git a/.github/workflows/__test-workflow-docker-build-images-multi-registry.yml b/.github/workflows/__test-workflow-docker-build-images-multi-registry.yml new file mode 100644 index 00000000..ff696bfe --- /dev/null +++ b/.github/workflows/__test-workflow-docker-build-images-multi-registry.yml @@ -0,0 +1,88 @@ +--- +name: Test for "docker-build-images" workflow - Multi registry inputs +run-name: Test for "docker-build-images" workflow - Multi registry inputs + +on: # yamllint disable-line rule:truthy + workflow_call: + +permissions: + contents: read + issues: read + packages: write + pull-requests: read + id-token: write + +jobs: + act-build-images-multi-registry: + name: Act - Build images with structured registry inputs + uses: ./.github/workflows/docker-build-images.yml + secrets: + oci-registry-password: | + {"push":"${{ secrets.GITHUB_TOKEN }}","pull:private":"${{ secrets.GITHUB_TOKEN }}"} + build-secret-github-app-key: ${{ secrets.CI_BOT_APP_PRIVATE_KEY }} + with: + cache-type: "registry" + sign: false + oci-registry: | + {"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"} + oci-registry-username: | + {"push":"${{ github.repository_owner }}","pull:private":"${{ github.repository_owner }}"} + images: | + [ + { + "name": "test-multi-registry-inputs", + "context": ".", + "dockerfile": "./tests/application/Dockerfile", + "build-args": { "BUILD_RUN_ID": "${{ github.run_id }}" }, + "target": "prod", + "platforms": ["linux/amd64"] + } + ] + + assert-multi-registry: + name: Assert - Build images with structured registry inputs + needs: act-build-images-multi-registry + runs-on: ubuntu-latest + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Assert built image output and pullability + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + BUILT_IMAGES: ${{ needs.act-build-images-multi-registry.outputs.built-images }} + EXPECTED_IMAGE: ghcr.io/${{ github.repository }}/test-multi-registry-inputs + with: + script: | + const assert = require("assert"); + const sha = `${{ github.sha }}`; + + const builtImages = JSON.parse(process.env.BUILT_IMAGES); + const builtImage = builtImages["test-multi-registry-inputs"]; + + assert(builtImage, `"built-images" output does not contain "test-multi-registry-inputs" image`); + assert.equal(builtImage.registry, "ghcr.io", `"registry" output is not valid`); + assert.match(builtImage.digest, /^sha256:[0-9a-f]{64}$/, `"digest" output is not valid`); + + const expectedTag = `${{ github.event_name }}` === "pull_request" + ? `pr-${{ github.event.pull_request.number }}-${sha.substring(0, 7)}` + : `${{ github.ref_name }}`; + + const expectedImage = `${process.env.EXPECTED_IMAGE}:${expectedTag}@${builtImage.digest}`; + assert.equal(builtImage.images[0], expectedImage, `"image" output is not valid`); + + await exec.exec("docker", ["pull", expectedImage]); + + - name: Assert registry cache usage with structured inputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + EXPECTED_CACHE_IMAGE: ghcr.io/${{ github.repository }}/test-multi-registry-inputs/cache + EXPECTED_CACHE_TAG: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref_name }} + with: + script: | + const cacheImage = `${process.env.EXPECTED_CACHE_IMAGE}:${process.env.EXPECTED_CACHE_TAG}-linux-amd64`; + await exec.exec("docker", ["manifest", "inspect", cacheImage]); diff --git a/.github/workflows/docker-build-images.md b/.github/workflows/docker-build-images.md index fbdb13c5..29311ac3 100644 --- a/.github/workflows/docker-build-images.md +++ b/.github/workflows/docker-build-images.md @@ -73,11 +73,18 @@ jobs: # Default: `["ubuntu-latest"]` runs-on: '["ubuntu-latest"]' - # OCI registry where to pull and push images + # OCI registry configuration used to pull, push and cache images. + # Accepts either a registry hostname string or a JSON object with + # `pull`, `pull:`, `push` and `cache` keys. + # Example: + # `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` # Default: `ghcr.io` oci-registry: ghcr.io - # Username used to log against the OCI registry. + # Username configuration used to log against OCI registries. + # Accepts either a single username string or a JSON object using the same keys as `oci-registry`. + # Example: + # `{"pull:private":"${{ github.repository_owner }}","push":"${{ github.repository_owner }}"}` # See https://github.com/docker/login-action#usage. # # Default: `${{ github.repository_owner }}` @@ -165,8 +172,10 @@ jobs: | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | -------------------------------- | | **`runs-on`** | Runner to use. JSON array of runners. | **false** | **string** | `["ubuntu-latest"]` | | | See . | | | | -| **`oci-registry`** | OCI registry where to pull and push images | **false** | **string** | `ghcr.io` | -| **`oci-registry-username`** | Username used to log against the OCI registry. | **false** | **string** | `${{ github.repository_owner }}` | +| **`oci-registry`** | OCI registry configuration used to pull, push and cache images. | **false** | **string** | `ghcr.io` | +| | Accepts a single registry hostname or a JSON object with `pull`, `pull:`, `push` and `cache` keys. | | | | +| **`oci-registry-username`** | Username configuration used to log against OCI registries. | **false** | **string** | `${{ github.repository_owner }}` | +| | Accepts a single username or a JSON object using the same keys as `oci-registry`. | | | | | | See . | | | | | **`images`** | Images to build parameters. | **true** | **string** | - | | | JSON array of objects. | | | | @@ -193,17 +202,48 @@ jobs: ## Secrets -| **Secret** | **Description** | **Required** | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------ | -| **`oci-registry-password`** | Password or GitHub token (`packages:read` and `packages:write` scopes) used to log against the OCI registry. | **true** | -| | See . | | -| **`build-secrets`** | List of secrets to expose to the build. | **false** | -| | See . | | -| **`build-secret-github-app-key`** | GitHub App private key to generate GitHub token to be passed as build secret env. | **false** | -| | See . | | +| **Secret** | **Description** | **Required** | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------ | +| **`oci-registry-password`** | Password or GitHub token (`packages:read` and `packages:write` scopes) configuration used to log against OCI registries. | **true** | +| | Accepts a single password/token or a JSON object using the same keys as `oci-registry`. | | +| | See . | | +| **`build-secrets`** | List of secrets to expose to the build. | **false** | +| | See . | | +| **`build-secret-github-app-key`** | GitHub App private key to generate GitHub token to be passed as build secret env. | **false** | +| | See . | | +## Multiple registries + +The default single-registry format still works: + +```yaml +with: + oci-registry: ghcr.io + oci-registry-username: ${{ github.repository_owner }} +secrets: + oci-registry-password: ${{ github.token }} +``` + +To configure distinct pull, push and cache registries, pass JSON objects: + +```yaml +with: + oci-registry: | + {"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"} + oci-registry-username: | + {"pull:private":"${{ github.repository_owner }}","push":"${{ github.repository_owner }}"} +secrets: + oci-registry-password: | + {"pull:private":"${{ github.token }}","push":"${{ github.token }}"} +``` + +Registry credentials are resolved by role using the same keys as `oci-registry`. +`pull` is the default pull registry, while `pull:` can be repeated for additional pull registries. +When no pull registry is provided, the push registry is also used for pulls. +Optional pull registries without credentials are skipped, which is useful for public registries such as Docker Hub. + ### Images entry parameters | **Parameter** | **Description** | **Default** | **Required** | diff --git a/.github/workflows/docker-build-images.yml b/.github/workflows/docker-build-images.yml index aa7883c3..efed5b16 100644 --- a/.github/workflows/docker-build-images.yml +++ b/.github/workflows/docker-build-images.yml @@ -15,13 +15,24 @@ on: # yamllint disable-line rule:truthy default: '["ubuntu-latest"]' required: false oci-registry: - description: "OCI registry where to pull and push images" + description: | + OCI registry configuration used to pull, push and cache images. + Accepts either a registry hostname string (default format) or a JSON object. + JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` + JSON object keys: + - `pull`: registry used to pull public or default base images + - `pull:`: additional pull registry + - `push`: registry used for published images + - `cache`: registry used when `cache-type` is `registry` + If no `pull` key is provided, the `push` registry is also used for pulls. type: string default: "ghcr.io" required: false oci-registry-username: description: | - Username used to log against the OCI registry. + Username configuration used to log against OCI registries. + Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. + JSON example: `{"pull:private":"$\{{ github.repository_owner }}","push":"$\{{ github.repository_owner }}"}` See https://github.com/docker/login-action#usage. type: string default: ${{ github.repository_owner }} @@ -103,7 +114,9 @@ on: # yamllint disable-line rule:truthy secrets: oci-registry-password: description: | - Password or GitHub token (`packages:read` and `packages:write` scopes) used to log against the OCI registry. + Password or GitHub token (`packages:read` and `packages:write` scopes) configuration used to log against OCI registries. + Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. + JSON example: `{"pull:private":"$\{{ github.token }}","push":"$\{{ github.token }}"}` See https://github.com/docker/login-action#usage. required: true build-secrets: diff --git a/README.md b/README.md index 95fee4e4..eee61dd7 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ _Actions that operate on OCI images across their build, metadata, and lifecycle #### - [Prune pull requests image tags](actions/docker/prune-pull-requests-image-tags/README.md) +#### - [Setup](actions/docker/setup/README.md) + #### - [Sign images](actions/docker/sign-images/README.md) ### Helm diff --git a/actions/docker/build-image/README.md b/actions/docker/build-image/README.md index 52a7bb62..deba1257 100644 --- a/actions/docker/build-image/README.md +++ b/actions/docker/build-image/README.md @@ -48,19 +48,29 @@ permissions: ```yaml - uses: hoverkraft-tech/ci-github-container/actions/docker/build-image@a0bab9151cc074af9f6c8204ab42a48d2d570379 # 0.30.6 with: - # OCI registry where to pull and push images + # OCI registry configuration used to pull, push and cache images. + # Accepts either a registry hostname string or a JSON object with + # `pull`, `pull:`, `push` and `cache` keys. + # Example: + # `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` # This input is required. # Default: `ghcr.io` oci-registry: ghcr.io - # Username used to log against the OCI registry. + # Username configuration used to log against OCI registries. + # Accepts either a single username string or a JSON object using the same keys as `oci-registry`. + # Example: + # `{"pull:private":"${{ github.repository_owner }}","push":"${{ github.repository_owner }}"}` # See https://github.com/docker/login-action#usage. # # This input is required. # Default: `${{ github.repository_owner }}` oci-registry-username: ${{ github.repository_owner }} - # Password or personal access token used to log against the OCI registry. + # Password or personal access token configuration used to log against OCI registries. + # Accepts either a single password/token string or a JSON object using the same keys as `oci-registry`. + # Example: + # `{"pull:private":"${{ github.token }}","push":"${{ github.token }}"}` # Can be passed in using `secrets.GITHUB_TOKEN`. # See https://github.com/docker/login-action#usage. # @@ -137,10 +147,13 @@ permissions: | **Input** | **Description** | **Required** | **Default** | | --------------------------- | -------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------- | -| **`oci-registry`** | OCI registry where to pull and push images | **true** | `ghcr.io` | -| **`oci-registry-username`** | Username used to log against the OCI registry. | **true** | `${{ github.repository_owner }}` | +| **`oci-registry`** | OCI registry configuration used to pull, push and cache images. | **true** | `ghcr.io` | +| | Accepts a single registry hostname or a JSON object with `pull`, `pull:`, `push` and `cache` keys. | | | +| **`oci-registry-username`** | Username configuration used to log against OCI registries. | **true** | `${{ github.repository_owner }}` | +| | Accepts a single username or a JSON object using the same keys as `oci-registry`. | | | | | See . | | | -| **`oci-registry-password`** | Password or personal access token used to log against the OCI registry. | **true** | `${{ github.token }}` | +| **`oci-registry-password`** | Password or personal access token configuration used to log against OCI registries. | **true** | `${{ github.token }}` | +| | Accepts a single password/token or a JSON object using the same keys as `oci-registry`. | | | | | Can be passed in using `secrets.GITHUB_TOKEN`. | | | | | See . | | | | **`repository`** | Repository name. | **false** | `${{ github.repository }}` | @@ -191,6 +204,32 @@ permissions: +## Multiple registries + +The default single-registry format still works: + +```yaml +oci-registry: ghcr.io +oci-registry-username: ${{ github.repository_owner }} +oci-registry-password: ${{ github.token }} +``` + +To configure distinct pull, push and cache registries, pass JSON objects: + +```yaml +oci-registry: | + {"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"} +oci-registry-username: | + {"pull:private":"${{ github.repository_owner }}","push":"${{ github.repository_owner }}"} +oci-registry-password: | + {"pull:private":"${{ github.token }}","push":"${{ github.token }}"} +``` + +Registry credentials are resolved by role using the same keys as `oci-registry`. +`pull` is the default pull registry, while `pull:` can be repeated for additional pull registries. +When no pull registry is provided, the push registry is also used for pulls. +Optional pull registries without credentials are skipped, which is useful for public registries such as Docker Hub. + diff --git a/actions/docker/build-image/action.yml b/actions/docker/build-image/action.yml index fa827c3c..c66ae5b3 100644 --- a/actions/docker/build-image/action.yml +++ b/actions/docker/build-image/action.yml @@ -12,18 +12,32 @@ branding: inputs: oci-registry: - description: "OCI registry where to pull and push images" + description: | + OCI registry configuration used to pull, push and cache images. + Accepts either a registry hostname string (default format) or a JSON object. + JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` + JSON object keys: + - `pull`: registry used to pull public or default base images + - `pull:`: additional pull registry + - `push`: registry used for published images + - `cache`: registry used when `cache-type` is `registry` + If no `pull` key is provided, the `push` registry is also used for pulls. default: "ghcr.io" required: true oci-registry-username: description: | - Username used to log against the OCI registry. + Username configuration used to log against OCI registries. + Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. + JSON example: + `{"pull:private":"$\{{ github.repository_owner }}","push":"$\{{ github.repository_owner }}"}` See https://github.com/docker/login-action#usage. default: ${{ github.repository_owner }} required: true oci-registry-password: description: | - Password or personal access token used to log against the OCI registry. + Password or personal access token configuration used to log against OCI registries. + Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. + JSON example: `{"pull:private":"$\{{ github.token }}","push":"$\{{ github.token }}"}` Can be passed in using `secrets.GITHUB_TOKEN`. See https://github.com/docker/login-action#usage. default: ${{ github.token }} @@ -128,10 +142,22 @@ runs: # FIXME: workaround until will be merged: https://github.com/actions/runner/pull/1684 run: mkdir -p ./self-actions/ && cp -r $GITHUB_ACTION_PATH/../../* ./self-actions/ + - id: slugify-platform + uses: hoverkraft-tech/ci-github-common/actions/slugify@f5847cb398fe65d53794e6aba98ebdfa0801f691 # 0.32.0 + with: + value: ${{ inputs.platform }} + + - id: docker-setup + uses: ./self-actions/docker/setup + with: + oci-registry: ${{ inputs.oci-registry }} + oci-registry-username: ${{ inputs.oci-registry-username }} + oci-registry-password: ${{ inputs.oci-registry-password }} + - id: metadata uses: ./self-actions/docker/get-image-metadata with: - oci-registry: ${{ inputs.oci-registry }} + oci-registry: ${{ steps.docker-setup.outputs.push-registry }} repository: ${{ inputs.repository }} image: ${{ inputs.image }} tag: ${{ inputs.tag }} @@ -141,22 +167,26 @@ runs: run: | rm -fr ./self-actions - - id: slugify-platform - uses: hoverkraft-tech/ci-github-common/actions/slugify@f5847cb398fe65d53794e6aba98ebdfa0801f691 # 0.32.0 - with: - value: ${{ inputs.platform }} - - id: get-docker-config uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + CACHE_REGISTRY: ${{ steps.docker-setup.outputs.cache-registry }} + CACHE_TYPE_INPUT: ${{ inputs.cache-type }} + CONTEXT_INPUT: ${{ inputs.context }} + DOCKERFILE_INPUT: ${{ inputs.dockerfile }} + METADATA_IMAGE: ${{ steps.metadata.outputs.image }} + MULTI_PLATFORM_INPUT: ${{ inputs.multi-platform }} + PLATFORM_INPUT: ${{ inputs.platform }} + SLUGIFIED_PLATFORM: ${{ steps.slugify-platform.outputs.result }} with: script: | const fs = require('fs'); const path = require('path'); const workspace = process.env.GITHUB_WORKSPACE || process.cwd(); - const rawContext = `${{ inputs.context }}`.trim(); + const rawContext = (process.env.CONTEXT_INPUT || '').trim(); const contextPath = rawContext.length > 0 ? rawContext : '.'; - const rawDockerfile = `${{ inputs.dockerfile }}`.trim(); + const rawDockerfile = (process.env.DOCKERFILE_INPUT || '').trim(); const dockerfileName = rawDockerfile.length > 0 ? rawDockerfile : 'Dockerfile'; const dockerfilePath = path.resolve(workspace, contextPath, dockerfileName); @@ -168,13 +198,19 @@ runs: const resolvedDockerfilePath = fs.realpathSync(dockerfilePath); core.setOutput('dockerfile-path', resolvedDockerfilePath); - const slugifiedPlatform = `${{ steps.slugify-platform.outputs.result }}`; + const slugifiedPlatform = process.env.SLUGIFIED_PLATFORM || ''; const tagSuffix = `-${slugifiedPlatform}`; core.setOutput('cache-flavor', `suffix=${tagSuffix}`); - const cacheType = `${{ inputs.cache-type }}`.trim(); - const metadataImage = `${{ steps.metadata.outputs.image }}`; - const cacheImage = cacheType === 'registry' ? `${metadataImage}/cache` : metadataImage; + const cacheType = (process.env.CACHE_TYPE_INPUT || '').trim(); + const metadataImage = process.env.METADATA_IMAGE || ''; + const cacheRegistry = (process.env.CACHE_REGISTRY || '').trim(); + if (cacheType === 'registry' && !cacheRegistry.length) { + return core.setFailed('Cache registry is required when cache-type is set to "registry".'); + } + const metadataImageWithoutRegistry = metadataImage.replace(/^[^\/]+\//, ''); + const cacheBaseImage = cacheRegistry.length ? `${cacheRegistry}/${metadataImageWithoutRegistry}` : metadataImage; + const cacheImage = cacheType === 'registry' ? `${cacheBaseImage}/cache` : metadataImage; core.setOutput('cache-image', cacheImage); try { @@ -184,14 +220,14 @@ runs: // docker not available on runner } - const multiplatformInput = `${{ inputs.multi-platform }}`.trim().toLowerCase(); + const multiplatformInput = (process.env.MULTI_PLATFORM_INPUT || '').trim().toLowerCase(); const isMultiplatform = !(multiplatformInput.length === 0 || multiplatformInput === 'false'); if (isMultiplatform) { core.debug('Multi-platform build is enabled.'); core.setOutput('multi-platform', true); } - const platform = `${{ inputs.platform }}`.trim(); + const platform = (process.env.PLATFORM_INPUT || '').trim(); if (platform.length === 0) { return core.setFailed('Input "platform" is required.'); } @@ -232,23 +268,11 @@ runs: } } - - if: steps.get-docker-config.outputs.docker-exists != 'true' - uses: docker/setup-docker-action@1a6edb0ba9ac496f6850236981f15d8f9a82254d # v5.0.0 - - if: steps.get-docker-config.outputs.platform-exists != 'true' uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 with: platforms: ${{ inputs.platform }} - - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - id: setup-buildx - with: - # FIXME: upgrade version when available (https://github.com/docker/buildx/releases) - version: v0.31.1 - # FIXME: upgrade version when available (https://hub.docker.com/r/moby/buildkit) - driver-opts: | - image=moby/buildkit:v0.27.0 - # Caching setup - id: cache-arguments uses: int128/docker-build-cache-config-action@3a4a4fababc091be29633e5a2b3bbf523996802a # v1.47.0 @@ -268,18 +292,11 @@ runs: - name: Restore Docker cache mounts uses: reproducible-containers/buildkit-cache-dance@1b8ab18fbda5ad3646e3fcc9ed9dd41ce2f297b4 # v3.3.2 with: - builder: ${{ steps.setup-buildx.outputs.name }} + builder: ${{ steps.docker-setup.outputs.buildx-name }} cache-dir: cache-mount dockerfile: ${{ steps.get-docker-config.outputs.dockerfile-path }} skip-extraction: ${{ steps.cache.outputs.cache-hit }} - - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 - with: - registry: ${{ inputs.oci-registry }} - username: ${{ inputs.oci-registry-username }} - password: ${{ inputs.oci-registry-password }} # jscpd:ignore-end - - id: build uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: diff --git a/actions/docker/create-images-manifests/README.md b/actions/docker/create-images-manifests/README.md index 5fba646e..d5f66dcd 100644 --- a/actions/docker/create-images-manifests/README.md +++ b/actions/docker/create-images-manifests/README.md @@ -47,17 +47,28 @@ permissions: ````yaml - uses: hoverkraft-tech/ci-github-container/actions/docker/create-images-manifests@a0bab9151cc074af9f6c8204ab42a48d2d570379 # 0.30.6 with: - # OCI registry where to pull and push images + # OCI registry configuration used to pull, push and cache images. + # Accepts either a registry hostname string or a JSON object with + # `pull`, `pull:`, `push` and `cache` keys. + # Example: + # `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` # This input is required. # Default: `ghcr.io` oci-registry: ghcr.io - # Username used to log against the OCI registry. See https://github.com/docker/login-action#usage. + # Username configuration used to log against OCI registries. + # Accepts either a single username string or a JSON object using the same keys as `oci-registry`. + # Example: + # `{"pull:private":"${{ github.repository_owner }}","push":"${{ github.repository_owner }}"}` + # See https://github.com/docker/login-action#usage. # This input is required. # Default: `${{ github.repository_owner }}` oci-registry-username: ${{ github.repository_owner }} - # Password or personal access token used to log against the OCI registry. + # Password or personal access token configuration used to log against OCI registries. + # Accepts either a single password/token string or a JSON object using the same keys as `oci-registry`. + # Example: + # `{"pull:private":"${{ github.token }}","push":"${{ github.token }}"}` # Can be passed in using `secrets.GITHUB_TOKEN`. # See https://github.com/docker/login-action#usage. # @@ -102,9 +113,12 @@ permissions: | **Input** | **Description** | **Required** | **Default** | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------- | -| **`oci-registry`** | OCI registry where to pull and push images | **true** | `ghcr.io` | -| **`oci-registry-username`** | Username used to log against the OCI registry. See . | **true** | `${{ github.repository_owner }}` | -| **`oci-registry-password`** | Password or personal access token used to log against the OCI registry. | **true** | `${{ github.token }}` | +| **`oci-registry`** | OCI registry configuration used to pull, push and cache images. | **true** | `ghcr.io` | +| | Accepts a single registry hostname or a JSON object with `pull`, `pull:`, `push` and `cache` keys. | | | +| **`oci-registry-username`** | Username configuration used to log against OCI registries. See . | **true** | `${{ github.repository_owner }}` | +| | Accepts a single username or a JSON object using the same keys as `oci-registry`. | | | +| **`oci-registry-password`** | Password or personal access token configuration used to log against OCI registries. | **true** | `${{ github.token }}` | +| | Accepts a single password/token or a JSON object using the same keys as `oci-registry`. | | | | | Can be passed in using `secrets.GITHUB_TOKEN`. | | | | | See . | | | | **`built-images`** | Built images data. | **true** | - | @@ -126,6 +140,29 @@ permissions: +## Multiple registries + +The default single-registry format still works: + +```yaml +oci-registry: ghcr.io +oci-registry-username: ${{ github.repository_owner }} +oci-registry-password: ${{ github.token }} +``` + +To configure distinct pull, push and cache registries, pass JSON objects: + +```yaml +oci-registry: | + {"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"} +oci-registry-username: | + {"pull:private":"${{ github.repository_owner }}","push":"${{ github.repository_owner }}"} +oci-registry-password: | + {"pull:private":"${{ github.token }}","push":"${{ github.token }}"} +``` + +Registry credentials are resolved by role using the same keys as `oci-registry`. + diff --git a/actions/docker/create-images-manifests/action.yml b/actions/docker/create-images-manifests/action.yml index 8853bb08..8b81ee99 100644 --- a/actions/docker/create-images-manifests/action.yml +++ b/actions/docker/create-images-manifests/action.yml @@ -12,17 +12,30 @@ branding: inputs: oci-registry: - description: "OCI registry where to pull and push images" + description: | + OCI registry configuration used to pull, push and cache images. + Accepts either a registry hostname string (default format) or a JSON object. + JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` + JSON object keys: + - `pull`: registry used to pull public or default base images + - `pull:`: additional pull registry + - `push`: registry used for published images + - `cache`: registry used when `cache-type` is `registry` default: "ghcr.io" required: true oci-registry-username: - description: Username used to log against the OCI registry. + description: | + Username configuration used to log against OCI registries. + Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. + JSON example: `{"pull:private":"$\{{ github.repository_owner }}","push":"$\{{ github.repository_owner }}"}` See https://github.com/docker/login-action#usage. default: ${{ github.repository_owner }} required: true oci-registry-password: description: | - Password or personal access token used to log against the OCI registry. + Password or personal access token configuration used to log against OCI registries. + Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. + JSON example: `{"pull:private":"$\{{ github.token }}","push":"$\{{ github.token }}"}` Can be passed in using `secrets.GITHUB_TOKEN`. See https://github.com/docker/login-action#usage. default: ${{ github.token }} @@ -83,19 +96,21 @@ outputs: runs: using: "composite" steps: - - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - with: - # FIXME: upgrade version when available (https://github.com/docker/buildx/releases) - version: v0.31.1 - # FIXME: upgrade version when available (https://hub.docker.com/r/moby/buildkit) - driver-opts: | - image=moby/buildkit:v0.27.0 + - shell: bash + # FIXME: workaround until will be merged: https://github.com/actions/runner/pull/1684 + run: mkdir -p ./self-actions/ && cp -r $GITHUB_ACTION_PATH/../../* ./self-actions/ - - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + - id: docker-setup + uses: ./self-actions/docker/setup with: - registry: ${{ inputs.oci-registry }} - username: ${{ inputs.oci-registry-username }} - password: ${{ inputs.oci-registry-password }} + oci-registry: ${{ inputs.oci-registry }} + oci-registry-username: ${{ inputs.oci-registry-username }} + oci-registry-password: ${{ inputs.oci-registry-password }} + built-images: ${{ inputs.built-images }} + + - shell: bash + # FIXME: workaround until will be merged: https://github.com/actions/runner/pull/1684 + run: rm -fr ./self-actions - id: create-images-manifests name: Create images manifests and push diff --git a/actions/docker/setup/README.md b/actions/docker/setup/README.md new file mode 100644 index 00000000..cc6f87d3 --- /dev/null +++ b/actions/docker/setup/README.md @@ -0,0 +1,124 @@ + + +# ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItcGFja2FnZSIgY29sb3I9ImJsdWUiPjxsaW5lIHgxPSIxNi41IiB5MT0iOS40IiB4Mj0iNy41IiB5Mj0iNC4yMSI+PC9saW5lPjxwYXRoIGQ9Ik0yMSAxNlY4YTIgMiAwIDAgMC0xLTEuNzNsLTctNGEyIDIgMCAwIDAtMiAwbC03IDRBMiAyIDAgMCAwIDMgOHY4YTIgMiAwIDAgMCAxIDEuNzNsNyA0YTIgMiAwIDAgMCAyIDBsNy00QTIgMiAwIDAgMCAyMSAxNnoiPjwvcGF0aD48cG9seWxpbmUgcG9pbnRzPSIzLjI3IDYuOTYgMTIgMTIuMDEgMjAuNzMgNi45NiI+PC9wb2x5bGluZT48bGluZSB4MT0iMTIiIHkxPSIyMi4wOCIgeDI9IjEyIiB5Mj0iMTIiPjwvbGluZT48L3N2Zz4=) GitHub Action: Docker - Setup + +
+ Docker - Setup +
+ +--- + + + + +[![Marketplace](https://img.shields.io/badge/Marketplace-docker------setup-blue?logo=github-actions)](https://github.com/marketplace/actions/docker---setup) +[![Release](https://img.shields.io/github/v/release/hoverkraft-tech/ci-github-container)](https://github.com/hoverkraft-tech/ci-github-container/releases) +[![License](https://img.shields.io/github/license/hoverkraft-tech/ci-github-container)](http://choosealicense.com/licenses/mit/) +[![Stars](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-container?style=social)](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-container?style=social) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/hoverkraft-tech/ci-github-container/blob/main/CONTRIBUTING.md) + + + + +## Overview + +Shared action to configure Docker tooling and OCI registry authentication. + + + + +## Usage + +```yaml +- uses: hoverkraft-tech/ci-github-container/actions/docker/setup@b6cc2773aa9e7d7362b9e16b9032fefd399e7df6 # copilot/add-multiple-registry-support + with: + # OCI registry configuration used to pull, push and cache images. + # Accepts either a registry hostname string (default format) or a JSON object. + # JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` + # + # This input is required. + # Default: `ghcr.io` + oci-registry: ghcr.io + + # Username configuration used to log against OCI registries. + # Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. + oci-registry-username: "" + + # Password or personal access token configuration used to log against OCI registries. + # Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. + oci-registry-password: "" + + # Optional built images payload used to resolve manifest publication registries. + # When provided, registry authentication targets are inferred from the built image data. + built-images: "" + + # Whether to install and configure Docker Buildx. + # + # Default: `true` + setup-buildx: true +``` + + + + +## Inputs + +| **Input** | **Description** | **Required** | **Default** | +| --------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`oci-registry`** | OCI registry configuration used to pull, push and cache images. | **true** | `ghcr.io` | +| | Accepts either a registry hostname string (default format) or a JSON object. | | | +| | JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` | | | +| **`oci-registry-username`** | Username configuration used to log against OCI registries. | **false** | - | +| | Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. | | | +| **`oci-registry-password`** | Password or personal access token configuration used to log against OCI registries. | **false** | - | +| | Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. | | | +| **`built-images`** | Optional built images payload used to resolve manifest publication registries. | **false** | - | +| | When provided, registry authentication targets are inferred from the built image data. | | | +| **`setup-buildx`** | Whether to install and configure Docker Buildx. | **false** | `true` | + + + + + + +## Outputs + +| **Output** | **Description** | +| --------------------- | -------------------------------------------------- | +| **`push-registry`** | Registry used for published images/manifests. | +| **`cache-registry`** | Registry used for registry-backed build cache. | +| **`pull-registries`** | JSON array of registries used to pull base images. | +| **`buildx-name`** | Docker Buildx builder name. | + + + + + + +## Contributing + +Contributions are welcome! Please see the [contributing guidelines](https://github.com/hoverkraft-tech/ci-github-container/blob/main/CONTRIBUTING.md) for more details. + + + + + + +## License + +This project is licensed under the MIT License. + +SPDX-License-Identifier: MIT + +Copyright © 2026 hoverkraft + +For more details, see the [license](http://choosealicense.com/licenses/mit/). + + + + +--- + +This documentation was automatically generated by [CI Dokumentor](https://github.com/hoverkraft-tech/ci-dokumentor). + + diff --git a/actions/docker/setup/action.yml b/actions/docker/setup/action.yml new file mode 100644 index 00000000..fb1ee056 --- /dev/null +++ b/actions/docker/setup/action.yml @@ -0,0 +1,394 @@ +--- +name: "Docker - Setup" +description: | + Shared action to configure Docker tooling and OCI registry authentication. +author: hoverkraft +branding: + icon: package + color: blue + +inputs: + oci-registry: + description: | + OCI registry configuration used to pull, push and cache images. + Accepts either a registry hostname string (default format) or a JSON object. + JSON example: `{"pull":"docker.io","pull:private":"ghcr.io","push":"ghcr.io"}` + default: "ghcr.io" + required: true + oci-registry-username: + description: | + Username configuration used to log against OCI registries. + Accepts either a single username string (default format) or a JSON object using the same keys as `oci-registry`. + required: false + oci-registry-password: + description: | + Password or personal access token configuration used to log against OCI registries. + Accepts either a single password/token string (default format) or a JSON object using the same keys as `oci-registry`. + required: false + built-images: + description: | + Optional built images payload used to resolve manifest publication registries. + When provided, registry authentication targets are inferred from the built image data. + required: false + setup-buildx: + description: | + Whether to install and configure Docker Buildx. + default: true + required: false + +outputs: + push-registry: + description: "Registry used for published images/manifests." + value: ${{ steps.resolve-oci-registries.outputs.push-registry }} + cache-registry: + description: "Registry used for registry-backed build cache." + value: ${{ steps.resolve-oci-registries.outputs.cache-registry }} + pull-registries: + description: "JSON array of registries used to pull base images." + value: ${{ steps.resolve-oci-registries.outputs.pull-registries }} + buildx-name: + description: "Docker Buildx builder name." + value: ${{ steps.setup-buildx.outputs.name }} + +runs: + using: "composite" + steps: + - id: resolve-oci-registries + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + BUILT_IMAGES_INPUT: ${{ inputs.built-images }} + REGISTRY_INPUT: ${{ inputs.oci-registry }} + REGISTRY_PASSWORD_INPUT: ${{ inputs.oci-registry-password }} + REGISTRY_USERNAME_INPUT: ${{ inputs.oci-registry-username }} + with: + script: | + function parseJsonObjectInput(inputName, rawValue) { + const value = `${rawValue}`.trim(); + if (!value.length) { + return null; + } + + if (!value.startsWith('{')) { + return value; + } + + let parsedValue; + try { + parsedValue = JSON.parse(value); + } catch (error) { + throw new Error(`"${inputName}" input is not a valid JSON object: ${error}`); + } + + if (parsedValue === null || Array.isArray(parsedValue) || typeof parsedValue !== 'object') { + throw new Error(`"${inputName}" input must be a string or a JSON object`); + } + + return parsedValue; + } + + function normalizeString(value, fieldName) { + if (typeof value !== 'string') { + throw new Error(`"${fieldName}" must be a string`); + } + + const trimmedValue = value.trim(); + if (!trimmedValue.length) { + throw new Error(`"${fieldName}" must not be empty`); + } + + return trimmedValue; + } + + function isPullRole(role) { + return role === 'pull' || role.startsWith('pull:'); + } + + function normalizeRoleMapInput(inputName, rawValue) { + const parsedValue = parseJsonObjectInput(inputName, rawValue); + + if (parsedValue === null) { + return {}; + } + + if (typeof parsedValue === 'string') { + return { scalar: normalizeString(parsedValue, inputName) }; + } + + return Object.entries(parsedValue).reduce((roleMap, [key, value]) => { + if (key !== 'push' && key !== 'cache' && !isPullRole(key)) { + throw new Error(`"${inputName}.${key}" is not supported`); + } + + roleMap[key] = normalizeString(value, `${inputName}.${key}`); + return roleMap; + }, {}); + } + + function resolveCredentialByRole(credentialMap, role, registry, pushRegistry) { + const defaultCredential = credentialMap.scalar ?? ''; + + if (role === 'push') { + return credentialMap.push ?? defaultCredential; + } + + if (role === 'cache') { + return credentialMap.cache ?? credentialMap.push ?? defaultCredential; + } + + if (!isPullRole(role)) { + return defaultCredential; + } + + if (credentialMap[role] !== undefined) { + return credentialMap[role]; + } + + if (credentialMap.pull !== undefined) { + return credentialMap.pull; + } + + if (registry === pushRegistry && credentialMap.push !== undefined) { + return credentialMap.push; + } + + return defaultCredential; + } + + function resolvePushCredential(credentialMap) { + return credentialMap.push ?? credentialMap.scalar ?? ''; + } + + function extractRegistryFromBuiltImage(builtImage) { + if (builtImage?.registry) { + return normalizeString( + builtImage.registry, + `built-images.${builtImage.name || 'unknown'}.registry`, + ); + } + + const imageReference = builtImage?.images?.[0]; + if (typeof imageReference === 'string') { + const match = imageReference.trim().match(/^([^\/]+)\//); + if (match) { + return match[1]; + } + } + + return ''; + } + + function createRegistryAuthList(registryLogins) { + return registryLogins.reduce((registryAuthList, registryLogin) => { + const { registry, username, password, required } = registryLogin; + + if ((username && !password) || (!username && password)) { + throw new Error(`Credentials for registry "${registry}" must define both username and password`); + } + + if (!username && !password) { + if (required) { + throw new Error(`Credentials for registry "${registry}" are required`); + } + + core.info(`Skipping Docker login for optional registry "${registry}" because no credentials were provided.`); + return registryAuthList; + } + + registryAuthList.push({ + registry, + username, + password, + }); + + return registryAuthList; + }, []); + } + + function toRegistryAuthYaml(registryAuthList) { + return registryAuthList + .map(registryAuth => { + const lines = [ + `- registry: ${JSON.stringify(registryAuth.registry)}`, + ` username: ${JSON.stringify(registryAuth.username)}`, + ` password: ${JSON.stringify(registryAuth.password)}`, + ]; + + return lines.join('\n'); + }) + .join('\n'); + } + + function setOutputs({ pushRegistry = '', cacheRegistry = '', pullRegistries = [], registryAuth = [] }) { + core.setOutput('push-registry', pushRegistry); + core.setOutput('cache-registry', cacheRegistry); + core.setOutput('pull-registries', JSON.stringify(pullRegistries)); + core.setOutput('registry-auth', toRegistryAuthYaml(registryAuth)); + core.setOutput('has-registry-auth', registryAuth.length ? 'true' : 'false'); + } + + const registryInputName = ['oci', 'registry'].join('-'); + const registryUsernameInputName = [registryInputName, 'username'].join('-'); + const registryPasswordInputName = [registryInputName, 'password'].join('-'); + + const builtImagesInput = `${process.env.BUILT_IMAGES_INPUT || ''}`.trim(); + if (builtImagesInput.length) { + let builtImages; + try { + builtImages = JSON.parse(builtImagesInput); + } catch (error) { + throw new Error(`"built-images" input is not a valid JSON: ${error}`); + } + + const registries = [ + ...new Set( + Object.values(builtImages) + .map(extractRegistryFromBuiltImage) + .filter(Boolean), + ), + ]; + + if (!registries.length) { + const registryInput = normalizeRoleMapInput(registryInputName, `${process.env.REGISTRY_INPUT || ''}`); + if (registryInput.scalar) { + registries.push(registryInput.scalar); + } else { + const [, firstPullRegistryValue] = Object.entries(registryInput).find(([key]) => isPullRole(key)) ?? []; + const pushRegistry = registryInput.push ?? registryInput.cache ?? firstPullRegistryValue ?? ''; + if (!pushRegistry.length) { + throw new Error('Unable to resolve any OCI registry to authenticate against'); + } + + registries.push(pushRegistry); + } + } + + const usernameByRole = normalizeRoleMapInput(registryUsernameInputName, `${process.env.REGISTRY_USERNAME_INPUT || ''}`); + const passwordByRole = normalizeRoleMapInput(registryPasswordInputName, `${process.env.REGISTRY_PASSWORD_INPUT || ''}`); + const username = resolvePushCredential(usernameByRole); + const password = resolvePushCredential(passwordByRole); + + const registryAuth = createRegistryAuthList( + registries.map(registry => ({ + registry, + username, + password, + required: true, + })), + ); + + setOutputs({ + pushRegistry: registries[0] ?? '', + cacheRegistry: registries[0] ?? '', + registryAuth, + }); + return; + } + + const registryInput = normalizeRoleMapInput(registryInputName, `${process.env.REGISTRY_INPUT || ''}`); + + let pushRegistry = ''; + let cacheRegistry = ''; + let hasExplicitCacheRegistry = false; + let pullRegistryEntries = []; + let pullRegistries = []; + + if (registryInput.scalar) { + pushRegistry = registryInput.scalar; + cacheRegistry = pushRegistry; + pullRegistries = [pushRegistry]; + } else { + pushRegistry = registryInput.push ?? ''; + hasExplicitCacheRegistry = typeof registryInput.cache === 'string'; + cacheRegistry = registryInput.cache ?? pushRegistry; + pullRegistryEntries = Object.entries(registryInput) + .filter(([key]) => isPullRole(key)) + .map(([role, registry]) => ({ role, registry })); + + if (!pushRegistry.length) { + throw new Error(`"oci-registry.push" is required when "oci-registry" uses the JSON object format`); + } + + if (!pullRegistryEntries.length) { + pullRegistryEntries = [{ role: 'pull', registry: pushRegistry }]; + } + + pullRegistries = pullRegistryEntries.map(({ registry }) => registry); + } + + const registryEntries = [ + { role: 'push', registry: pushRegistry, required: true }, + ...pullRegistryEntries.map(pullRegistryEntry => ({ ...pullRegistryEntry, required: false })), + ]; + + if (hasExplicitCacheRegistry) { + registryEntries.push({ role: 'cache', registry: cacheRegistry, required: true }); + } + + const usernameByRole = normalizeRoleMapInput(registryUsernameInputName, `${process.env.REGISTRY_USERNAME_INPUT || ''}`); + const passwordByRole = normalizeRoleMapInput(registryPasswordInputName, `${process.env.REGISTRY_PASSWORD_INPUT || ''}`); + + const registryLoginsByRegistry = new Map(); + for (const registryEntry of registryEntries) { + const { role, registry, required } = registryEntry; + const username = resolveCredentialByRole(usernameByRole, role, registry, pushRegistry); + const password = resolveCredentialByRole(passwordByRole, role, registry, pushRegistry); + + const existingRegistryLogin = registryLoginsByRegistry.get(registry); + if (existingRegistryLogin) { + const hasDifferentUsername = existingRegistryLogin.username && username && existingRegistryLogin.username !== username; + const hasDifferentPassword = existingRegistryLogin.password && password && existingRegistryLogin.password !== password; + if (hasDifferentUsername || hasDifferentPassword) { + throw new Error(`Conflicting credentials configured for registry "${registry}"`); + } + + if (!existingRegistryLogin.username && username) { + existingRegistryLogin.username = username; + } + + if (!existingRegistryLogin.password && password) { + existingRegistryLogin.password = password; + } + existingRegistryLogin.required ||= required; + continue; + } + + registryLoginsByRegistry.set(registry, { + registry, + username, + password, + required, + }); + } + + const registryAuth = createRegistryAuthList([...registryLoginsByRegistry.values()]); + setOutputs({ + pushRegistry, + cacheRegistry, + pullRegistries, + registryAuth, + }); + + - id: detect-docker + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const dockerPath = await io.which('docker', false); + core.setOutput('exists', dockerPath ? 'true' : 'false'); + + - if: steps.detect-docker.outputs.exists != 'true' + uses: docker/setup-docker-action@1a6edb0ba9ac496f6850236981f15d8f9a82254d # v5.0.0 + + - if: inputs.setup-buildx != 'false' + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + id: setup-buildx + with: + # FIXME: upgrade version when available (https://github.com/docker/buildx/releases) + version: v0.31.1 + # FIXME: upgrade version when available (https://hub.docker.com/r/moby/buildkit) + driver-opts: | + image=moby/buildkit:v0.27.0 + + - if: steps.resolve-oci-registries.outputs.has-registry-auth == 'true' + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: + registry-auth: ${{ steps.resolve-oci-registries.outputs.registry-auth }} diff --git a/actions/helm/generate-docs/package-lock.json b/actions/helm/generate-docs/package-lock.json index fb84004e..4e8672f8 100644 --- a/actions/helm/generate-docs/package-lock.json +++ b/actions/helm/generate-docs/package-lock.json @@ -1026,6 +1026,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -1461,6 +1462,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz", "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -1779,6 +1781,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.5.4.tgz", "integrity": "sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==", "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -1891,6 +1894,7 @@ "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.21.0.tgz", "integrity": "sha512-DzzmbqfMW3EzHsunP66x556oZDzjcdjjlL2bHG4PubwnL58ZPAfz07px4GqteZkoCGnBYi779Y2mg7+vgNCwbw==", "license": "MIT", + "peer": true, "dependencies": { "globby": "16.1.0", "js-yaml": "4.1.1", @@ -4168,6 +4172,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }