Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
95ec569dd4 build(deps): bump the aws-sdk-dependencies group with 2 updates
Bumps the aws-sdk-dependencies group with 2 updates: [@aws-sdk/client-ecr](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr) and [@aws-sdk/client-ecr-public](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr-public).


Updates `@aws-sdk/client-ecr` from 3.890.0 to 3.958.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.958.0/clients/client-ecr)

Updates `@aws-sdk/client-ecr-public` from 3.890.0 to 3.958.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr-public/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.958.0/clients/client-ecr-public)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-ecr"
  dependency-version: 3.958.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws-sdk-dependencies
- dependency-name: "@aws-sdk/client-ecr-public"
  dependency-version: 3.958.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: aws-sdk-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 20:45:40 +00:00
36 changed files with 19240 additions and 11002 deletions

3
.eslintignore Normal file
View File

@@ -0,0 +1,3 @@
/dist/**
/coverage/**
/node_modules/**

24
.eslintrc.json Normal file
View File

@@ -0,0 +1,24 @@
{
"env": {
"node": true,
"es6": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jest/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"jest",
"prettier"
]
}

View File

@@ -4,12 +4,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
cooldown:
default-days: 2
groups:
crazy-max-dot-github:
patterns:
- "crazy-max/.github/*"
labels: labels:
- "dependencies" - "dependencies"
- "bot" - "bot"
@@ -17,10 +11,6 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
cooldown:
default-days: 2
exclude:
- "@docker/actions-toolkit"
versioning-strategy: "increase" versioning-strategy: "increase"
groups: groups:
aws-sdk-dependencies: aws-sdk-dependencies:

View File

@@ -1,8 +1,5 @@
name: ci name: ci
permissions:
contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
@@ -22,7 +19,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Stop docker name: Stop docker
run: | run: |
@@ -46,7 +43,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: ./ uses: ./
@@ -63,7 +60,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: ./ uses: ./
@@ -73,7 +70,7 @@ jobs:
password: ${{ secrets.GHCR_PAT }} password: ${{ secrets.GHCR_PAT }}
- -
name: DinD name: DinD
uses: docker://docker:29.3@sha256:4d90f1f6c400315c2dba96d3ec93c01e64198395cbba04f79d12adce4f737029 uses: docker://docker
with: with:
entrypoint: docker entrypoint: docker
args: pull ghcr.io/docker-ghactiontest/test args: pull ghcr.io/docker-ghactiontest/test
@@ -88,7 +85,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to ACR name: Login to ACR
uses: ./ uses: ./
@@ -108,7 +105,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to Docker Hub name: Login to Docker Hub
uses: ./ uses: ./
@@ -127,7 +124,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to ECR name: Login to ECR
uses: ./ uses: ./
@@ -147,10 +144,10 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Configure AWS Credentials name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0 uses: aws-actions/configure-aws-credentials@v5
with: with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -172,7 +169,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to Public ECR name: Login to Public ECR
continue-on-error: ${{ matrix.os == 'windows-latest' }} continue-on-error: ${{ matrix.os == 'windows-latest' }}
@@ -195,10 +192,10 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Configure AWS Credentials name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0 uses: aws-actions/configure-aws-credentials@v5
with: with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@@ -210,7 +207,7 @@ jobs:
with: with:
registry: public.ecr.aws registry: public.ecr.aws
ghcr: github-container:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
@@ -221,7 +218,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: ./ uses: ./
@@ -241,7 +238,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to GitLab name: Login to GitLab
uses: ./ uses: ./
@@ -261,7 +258,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to Google Artifact Registry name: Login to Google Artifact Registry
uses: ./ uses: ./
@@ -281,7 +278,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to Google Container Registry name: Login to Google Container Registry
uses: ./ uses: ./
@@ -295,7 +292,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to registries name: Login to registries
uses: ./ uses: ./
@@ -318,7 +315,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to registries name: Login to registries
uses: ./ uses: ./
@@ -339,7 +336,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Login to registries name: Login to registries
id: login id: login
@@ -359,125 +356,3 @@ jobs:
echo "::error::Should have failed" echo "::error::Should have failed"
exit 1 exit 1
fi fi
scope-dockerhub:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Login to Docker Hub
uses: ./
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
scope: '@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done
scope-dockerhub-repo:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Login to Docker Hub
uses: ./
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
scope: 'docker/buildx-bin@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done
scope-ghcr:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Login to GitHub Container Registry
uses: ./
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
scope: '@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done
scope-ghcr-repo:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Login to GitHub Container Registry
uses: ./
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
scope: 'docker/login-action@push'
-
name: Print config.json files
shell: bash
run: |
shopt -s globstar nullglob
for file in ~/.docker/**/config.json; do
echo "## ${file}"
jq '(.auths[]?.auth) |= "REDACTED"' "$file"
echo ""
done

View File

@@ -1,46 +1,50 @@
name: codeql name: codeql
permissions:
contents: read
on: on:
push: push:
branches: branches:
- 'master' - 'master'
- 'releases/v*' - 'releases/v*'
paths:
- '.github/workflows/codeql.yml'
- 'dist/**'
- 'src/**'
pull_request: pull_request:
paths:
- '.github/workflows/codeql.yml'
- 'dist/**'
- 'src/**'
env: permissions:
NODE_VERSION: "24" actions: read
contents: read
security-events: write
jobs: jobs:
analyze: analyze:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: strategy:
contents: read fail-fast: false
security-events: write matrix:
language:
- javascript-typescript
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
-
name: Enable corepack
run: |
corepack enable
yarn --version
-
name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- -
name: Initialize CodeQL name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 uses: github/codeql-action/init@v4
with: with:
languages: javascript-typescript languages: ${{ matrix.language }}
build-mode: none config: |
paths:
- src
-
name: Autobuild
uses: github/codeql-action/autobuild@v4
- -
name: Perform CodeQL Analysis name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 uses: github/codeql-action/analyze@v4
with: with:
category: "/language:javascript-typescript" category: "/language:${{matrix.language}}"

View File

@@ -4,14 +4,14 @@ permissions:
contents: read contents: read
on: on:
pull_request_target: # zizmor: ignore[dangerous-triggers] safe to use without checkout pull_request_target:
types: types:
- opened - opened
- reopened - reopened
jobs: jobs:
run: run:
uses: crazy-max/.github/.github/workflows/pr-assign-author.yml@64a0bfaf6e6bb1c448d6e4c42b11034ee7094f16 # v1.7.1 uses: crazy-max/.github/.github/workflows/pr-assign-author.yml@1b673f36fad86812f538c1df9794904038a23cbf
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write

View File

@@ -1,12 +1,5 @@
name: publish name: publish
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: on:
release: release:
types: types:
@@ -22,7 +15,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Publish name: Publish
uses: actions/publish-immutable-action@4bc8754ffc40f27910afb20287dbbbb675a4e978 # v0.0.4 uses: actions/publish-immutable-action@v0.0.4

View File

@@ -1,8 +1,5 @@
name: test name: test
permissions:
contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
@@ -20,16 +17,16 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Test name: Test
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0 uses: docker/bake-action@v6
with: with:
source: . source: .
targets: test targets: test
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 uses: codecov/codecov-action@v5
with: with:
files: ./coverage/clover.xml files: ./coverage/clover.xml
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -1,56 +0,0 @@
name: update-dist
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
types:
- opened
- synchronize
jobs:
update-dist:
if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == github.event.pull_request.head.repo.full_name
runs-on: ubuntu-latest
steps:
-
name: GitHub auth token from GitHub App
id: docker-read-app
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
app-id: ${{ secrets.GHACTIONS_REPO_WRITE_APP_ID }}
private-key: ${{ secrets.GHACTIONS_REPO_WRITE_APP_PRIVATE_KEY }}
owner: docker
-
name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
token: ${{ steps.docker-read-app.outputs.token }}
-
name: Build
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
source: .
targets: build
-
name: Commit and push dist
run: |
if [ -n "$(git status --porcelain -- dist)" ]; then
(
set -x
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add dist
git commit -m "chore: update generated content"
git push
)
else
echo "No changes in dist"
fi

View File

@@ -1,8 +1,5 @@
name: validate name: validate
permissions:
contents: read
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.ref }} group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true cancel-in-progress: true
@@ -18,15 +15,15 @@ jobs:
prepare: prepare:
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:
matrix: ${{ steps.generate.outputs.matrix }} targets: ${{ steps.generate.outputs.targets }}
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v6
- -
name: Generate matrix name: List targets
id: generate id: generate
uses: docker/bake-action/subaction/matrix@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0 uses: docker/bake-action/subaction/list-targets@v6
with: with:
target: validate target: validate
@@ -37,10 +34,10 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: ${{ fromJson(needs.prepare.outputs.matrix) }} target: ${{ fromJson(needs.prepare.outputs.targets) }}
steps: steps:
- -
name: Validate name: Validate
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0 uses: docker/bake-action@v6
with: with:
targets: ${{ matrix.target }} targets: ${{ matrix.target }}

View File

@@ -1,29 +0,0 @@
name: zizmor
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
workflow_dispatch:
push:
branches:
- 'master'
- 'releases/v*'
tags:
- 'v*'
pull_request:
jobs:
zizmor:
uses: crazy-max/.github/.github/workflows/zizmor.yml@64a0bfaf6e6bb1c448d6e4c42b11034ee7094f16 # v1.7.1
permissions:
contents: read
security-events: write
with:
min-severity: medium
min-confidence: medium
persona: pedantic

View File

@@ -6,5 +6,6 @@
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",
"bracketSpacing": false, "bracketSpacing": false,
"arrowParens": "avoid" "arrowParens": "avoid",
"parser": "typescript"
} }

View File

@@ -25,7 +25,6 @@ ___
* [Quay.io](#quayio) * [Quay.io](#quayio)
* [DigitalOcean](#digitalocean-container-registry) * [DigitalOcean](#digitalocean-container-registry)
* [Authenticate to multiple registries](#authenticate-to-multiple-registries) * [Authenticate to multiple registries](#authenticate-to-multiple-registries)
* [Set scopes for the authentication token](#set-scopes-for-the-authentication-token)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [Contributing](#contributing) * [Contributing](#contributing)
@@ -51,7 +50,7 @@ jobs:
steps: steps:
- -
name: Login to Docker Hub name: Login to Docker Hub
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -76,7 +75,7 @@ jobs:
steps: steps:
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -104,7 +103,7 @@ jobs:
steps: steps:
- -
name: Login to GitLab name: Login to GitLab
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: registry.gitlab.com registry: registry.gitlab.com
username: ${{ vars.GITLAB_USERNAME }} username: ${{ vars.GITLAB_USERNAME }}
@@ -135,7 +134,7 @@ jobs:
steps: steps:
- -
name: Login to ACR name: Login to ACR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <registry-name>.azurecr.io registry: <registry-name>.azurecr.io
username: ${{ vars.AZURE_CLIENT_ID }} username: ${{ vars.AZURE_CLIENT_ID }}
@@ -183,7 +182,7 @@ jobs:
service_account: <service_account> service_account: <service_account>
- -
name: Login to GCR name: Login to GCR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: gcr.io registry: gcr.io
username: oauth2accesstoken username: oauth2accesstoken
@@ -216,7 +215,7 @@ jobs:
steps: steps:
- -
name: Login to GCR name: Login to GCR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: gcr.io registry: gcr.io
username: _json_key username: _json_key
@@ -254,7 +253,7 @@ jobs:
service_account: <service_account> service_account: <service_account>
- -
name: Login to GAR name: Login to GAR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <location>-docker.pkg.dev registry: <location>-docker.pkg.dev
username: oauth2accesstoken username: oauth2accesstoken
@@ -291,7 +290,7 @@ jobs:
steps: steps:
- -
name: Login to GAR name: Login to GAR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <location>-docker.pkg.dev registry: <location>-docker.pkg.dev
username: _json_key username: _json_key
@@ -320,7 +319,7 @@ jobs:
steps: steps:
- -
name: Login to ECR name: Login to ECR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com
username: ${{ vars.AWS_ACCESS_KEY_ID }} username: ${{ vars.AWS_ACCESS_KEY_ID }}
@@ -343,7 +342,7 @@ jobs:
steps: steps:
- -
name: Login to ECR name: Login to ECR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com
username: ${{ vars.AWS_ACCESS_KEY_ID }} username: ${{ vars.AWS_ACCESS_KEY_ID }}
@@ -377,7 +376,7 @@ jobs:
aws-region: <region> aws-region: <region>
- -
name: Login to ECR name: Login to ECR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com
``` ```
@@ -404,7 +403,7 @@ jobs:
steps: steps:
- -
name: Login to Public ECR name: Login to Public ECR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: public.ecr.aws registry: public.ecr.aws
username: ${{ vars.AWS_ACCESS_KEY_ID }} username: ${{ vars.AWS_ACCESS_KEY_ID }}
@@ -438,7 +437,7 @@ jobs:
steps: steps:
- -
name: Login to OCIR name: Login to OCIR
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: <region>.ocir.io registry: <region>.ocir.io
username: ${{ vars.OCI_USERNAME }} username: ${{ vars.OCI_USERNAME }}
@@ -465,7 +464,7 @@ jobs:
steps: steps:
- -
name: Login to Quay.io name: Login to Quay.io
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: quay.io registry: quay.io
username: ${{ vars.QUAY_USERNAME }} username: ${{ vars.QUAY_USERNAME }}
@@ -489,7 +488,7 @@ jobs:
steps: steps:
- -
name: Login to DigitalOcean Container Registry name: Login to DigitalOcean Container Registry
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: registry.digitalocean.com registry: registry.digitalocean.com
username: ${{ vars.DIGITALOCEAN_USERNAME }} username: ${{ vars.DIGITALOCEAN_USERNAME }}
@@ -514,13 +513,13 @@ jobs:
steps: steps:
- -
name: Login to Docker Hub name: Login to Docker Hub
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@@ -528,8 +527,8 @@ jobs:
``` ```
You can also use the `registry-auth` input for raw authentication to You can also use the `registry-auth` input for raw authentication to
registries, defined as YAML objects. Each object have the same attributes as registries, defined as YAML objects. Each object can contain `registry`,
current inputs (except `logout`): `username`, `password` and `ecr` keys similar to current inputs:
> [!WARNING] > [!WARNING]
> We don't recommend using this method, it's better to use the action multiple > We don't recommend using this method, it's better to use the action multiple
@@ -548,7 +547,7 @@ jobs:
steps: steps:
- -
name: Login to registries name: Login to registries
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry-auth: | registry-auth: |
- username: ${{ vars.DOCKERHUB_USERNAME }} - username: ${{ vars.DOCKERHUB_USERNAME }}
@@ -558,60 +557,6 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
``` ```
### Set scopes for the authentication token
The `scope` input allows limiting registry credentials to a specific repository
or namespace scope when building images with Buildx.
This is useful in GitHub Actions to avoid overriding the Docker Hub
authentication token embedded in GitHub-hosted runners, which is used for
pulling images without rate limits. By scoping credentials, you can
authenticate only where needed (typically for pushing), while keeping
unauthenticated pulls for base images.
When `scope` is set, credentials are written to the Buildx configuration
instead of the global Docker configuration. This means:
* Authentication applies only to the specified scope
* The default Docker Hub credentials remain available for pulls
* Credentials are used only by Buildx during the build
> [!IMPORTANT]
> Credentials written to the Buildx configuration are only accessible by Buildx.
> They are not available to `docker pull`, `docker push`, or any other Docker
> CLI commands outside Buildx.
> [!NOTE]
> This feature requires Buildx version 0.31.0 or later.
```yaml
name: ci
on:
push:
branches: main
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Login to Docker Hub (scoped)
uses: docker/login-action@v4
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
scope: 'myorg/myimage@push'
-
name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: myorg/myimage:latest
```
In this example, base images are pulled using the embedded GitHub-hosted runner
credentials, while authenticated access is used only to push `myorg/myimage`.
## Customizing ## Customizing
### inputs ### inputs
@@ -623,13 +568,13 @@ The following inputs can be used as `step.with` keys:
| `registry` | String | `docker.io` | Server address of Docker registry. If not set then will default to Docker Hub | | `registry` | String | `docker.io` | Server address of Docker registry. If not set then will default to Docker Hub |
| `username` | String | | Username for authenticating to the Docker registry | | `username` | String | | Username for authenticating to the Docker registry |
| `password` | String | | Password or personal access token for authenticating the Docker registry | | `password` | String | | Password or personal access token for authenticating the Docker registry |
| `scope` | String | | Scope for the authentication token |
| `ecr` | String | `auto` | Specifies whether the given registry is ECR (`auto`, `true` or `false`) | | `ecr` | String | `auto` | Specifies whether the given registry is ECR (`auto`, `true` or `false`) |
| `logout` | Bool | `true` | Log out from the Docker registry at the end of a job | | `logout` | Bool | `true` | Log out from the Docker registry at the end of a job |
| `registry-auth` | YAML | | Raw authentication to registries, defined as YAML objects | | `registry-auth` | YAML | | Raw authentication to registries, defined as YAML objects |
> [!NOTE] > [!NOTE]
> The `registry-auth` input cannot be used with other inputs except `logout`. > The `registry-auth` input is mutually exclusive with `registry`, `username`,
> `password` and `ecr` inputs.
## Contributing ## Contributing

View File

@@ -1,7 +1,7 @@
import {beforeEach, describe, expect, test, vi} from 'vitest'; import {beforeEach, describe, expect, jest, test} from '@jest/globals';
import {AuthorizationData} from '@aws-sdk/client-ecr'; import {AuthorizationData} from '@aws-sdk/client-ecr';
import * as aws from '../src/aws.js'; import * as aws from '../src/aws';
describe('isECR', () => { describe('isECR', () => {
test.each([ test.each([
@@ -11,7 +11,6 @@ describe('isECR', () => {
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', true], ['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', true],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', true], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', true],
['012345678901.dkr-ecr.eu-north-1.on.aws', true], ['012345678901.dkr-ecr.eu-north-1.on.aws', true],
['012345678901.dkr.ecr.eusc-de-east-1.amazonaws.eu', true],
['public.ecr.aws', true], ['public.ecr.aws', true],
['ecr-public.aws.com', true] ['ecr-public.aws.com', true]
])('given registry %p', async (registry, expected) => { ])('given registry %p', async (registry, expected) => {
@@ -27,7 +26,6 @@ describe('isPubECR', () => {
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', false], ['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', false],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', false], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', false],
['012345678901.dkr-ecr.eu-north-1.on.aws', false], ['012345678901.dkr-ecr.eu-north-1.on.aws', false],
['012345678901.dkr.ecr.eusc-de-east-1.amazonaws.eu', false],
['public.ecr.aws', true], ['public.ecr.aws', true],
['ecr-public.aws.com', true] ['ecr-public.aws.com', true]
])('given registry %p', async (registry, expected) => { ])('given registry %p', async (registry, expected) => {
@@ -41,7 +39,6 @@ describe('getRegion', () => {
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', 'cn-north-1'], ['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', 'cn-north-1'],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-1'], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-1'],
['012345678901.dkr-ecr.eu-north-1.on.aws', 'eu-north-1'], ['012345678901.dkr-ecr.eu-north-1.on.aws', 'eu-north-1'],
['012345678901.dkr.ecr.eusc-de-east-1.amazonaws.eu', 'eusc-de-east-1'],
['public.ecr.aws', 'us-east-1'] ['public.ecr.aws', 'us-east-1']
])('given registry %p', async (registry, expected) => { ])('given registry %p', async (registry, expected) => {
expect(aws.getRegion(registry)).toEqual(expected); expect(aws.getRegion(registry)).toEqual(expected);
@@ -55,7 +52,6 @@ describe('getAccountIDs', () => {
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', '012345678901,012345678910,023456789012', ['012345678901', '012345678910', '023456789012']], ['012345678901.dkr.ecr.eu-west-3.amazonaws.com', '012345678901,012345678910,023456789012', ['012345678901', '012345678910', '023456789012']],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', '012345678910,023456789012', ['390948362332', '012345678910', '023456789012']], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', '012345678910,023456789012', ['390948362332', '012345678910', '023456789012']],
['876820548815.dkr-ecr.eu-north-1.on.aws', '012345678910,023456789012', ['876820548815', '012345678910', '023456789012']], ['876820548815.dkr-ecr.eu-north-1.on.aws', '012345678910,023456789012', ['876820548815', '012345678910', '023456789012']],
['012345678901.dkr.ecr.eusc-de-east-1.amazonaws.eu', '012345678910,023456789012', ['012345678901', '012345678910', '023456789012']],
['public.ecr.aws', undefined, []] ['public.ecr.aws', undefined, []]
])('given registry %p', async (registry, accountIDsEnv, expected) => { ])('given registry %p', async (registry, accountIDsEnv, expected) => {
if (accountIDsEnv) { if (accountIDsEnv) {
@@ -65,28 +61,26 @@ describe('getAccountIDs', () => {
}); });
}); });
const mockEcrGetAuthToken = vi.fn(); const mockEcrGetAuthToken = jest.fn();
const mockEcrPublicGetAuthToken = vi.fn(); const mockEcrPublicGetAuthToken = jest.fn();
vi.mock('@aws-sdk/client-ecr', () => { jest.mock('@aws-sdk/client-ecr', () => {
class ECR {
getAuthorizationToken = mockEcrGetAuthToken;
}
return { return {
ECR ECR: jest.fn(() => ({
getAuthorizationToken: mockEcrGetAuthToken
}))
}; };
}); });
vi.mock('@aws-sdk/client-ecr-public', () => { jest.mock('@aws-sdk/client-ecr-public', () => {
class ECRPUBLIC {
getAuthorizationToken = mockEcrPublicGetAuthToken;
}
return { return {
ECRPUBLIC ECRPUBLIC: jest.fn(() => ({
getAuthorizationToken: mockEcrPublicGetAuthToken
}))
}; };
}); });
describe('getRegistriesData', () => { describe('getRegistriesData', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); jest.clearAllMocks();
delete process.env.AWS_ACCOUNT_IDS; delete process.env.AWS_ACCOUNT_IDS;
}); });
// prettier-ignore // prettier-ignore

View File

@@ -1,17 +1,6 @@
import {afterEach, expect, test} from 'vitest'; import {expect, test} from '@jest/globals';
import * as path from 'path';
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js'; import {getInputs} from '../src/context';
import {getAuthList, getInputs} from '../src/context.js';
afterEach(() => {
for (const key of Object.keys(process.env)) {
if (key.startsWith('INPUT_')) {
delete process.env[key];
}
}
});
test('with password and username getInputs does not throw error', async () => { test('with password and username getInputs does not throw error', async () => {
process.env['INPUT_USERNAME'] = 'dbowie'; process.env['INPUT_USERNAME'] = 'dbowie';
@@ -21,15 +10,3 @@ test('with password and username getInputs does not throw error', async () => {
getInputs(); getInputs();
}).not.toThrow(); }).not.toThrow();
}); });
test('getAuthList uses the default Docker Hub registry when computing scoped config dir', async () => {
process.env['INPUT_USERNAME'] = 'dbowie';
process.env['INPUT_PASSWORD'] = 'groundcontrol';
process.env['INPUT_SCOPE'] = 'myscope';
process.env['INPUT_LOGOUT'] = 'false';
const [auth] = getAuthList(getInputs());
expect(auth).toMatchObject({
registry: 'docker.io',
configDir: path.join(Buildx.configDir, 'config', 'registry-1.docker.io', 'myscope')
});
});

View File

@@ -1,11 +1,16 @@
import {expect, test, vi} from 'vitest'; import {expect, jest, test} from '@jest/globals';
import * as path from 'path';
import {Docker} from '@docker/actions-toolkit/lib/docker/docker.js'; import {loginStandard, logout} from '../src/docker';
import {loginStandard, logout} from '../src/docker.js'; import {Docker} from '@docker/actions-toolkit/lib/docker/docker';
process.env['RUNNER_TEMP'] = path.join(__dirname, 'runner');
test('loginStandard calls exec', async () => { test('loginStandard calls exec', async () => {
const execSpy = vi.spyOn(Docker, 'getExecOutput').mockImplementation(async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const execSpy = jest.spyOn(Docker, 'getExecOutput').mockImplementation(async () => {
return { return {
exitCode: expect.any(Number), exitCode: expect.any(Number),
stdout: expect.any(Function), stdout: expect.any(Function),
@@ -33,7 +38,9 @@ test('loginStandard calls exec', async () => {
}); });
test('logout calls exec', async () => { test('logout calls exec', async () => {
const execSpy = vi.spyOn(Docker, 'getExecOutput').mockImplementation(async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const execSpy = jest.spyOn(Docker, 'getExecOutput').mockImplementation(async () => {
return { return {
exitCode: expect.any(Number), exitCode: expect.any(Number),
stdout: expect.any(Function), stdout: expect.any(Function),
@@ -43,7 +50,7 @@ test('logout calls exec', async () => {
const registry = 'https://ghcr.io'; const registry = 'https://ghcr.io';
await logout(registry, ''); await logout(registry);
expect(execSpy).toHaveBeenCalledTimes(1); expect(execSpy).toHaveBeenCalledTimes(1);
const callfunc = execSpy.mock.calls[0]; const callfunc = execSpy.mock.calls[0];

View File

@@ -1,12 +0,0 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-login-action-'));
process.env = Object.assign({}, process.env, {
TEMP: tmpDir,
GITHUB_REPOSITORY: 'docker/login-action',
RUNNER_TEMP: path.join(tmpDir, 'runner-temp'),
RUNNER_TOOL_CACHE: path.join(tmpDir, 'runner-tool-cache')
});

View File

@@ -19,9 +19,6 @@ inputs:
ecr: ecr:
description: 'Specifies whether the given registry is ECR (auto, true or false)' description: 'Specifies whether the given registry is ECR (auto, true or false)'
required: false required: false
scope:
description: 'Scope for the authentication token'
required: false
logout: logout:
description: 'Log out from the Docker registry at the end of a job' description: 'Log out from the Docker registry at the end of a job'
default: 'true' default: 'true'
@@ -31,6 +28,6 @@ inputs:
required: false required: false
runs: runs:
using: 'node24' using: 'node20'
main: 'dist/index.cjs' main: 'dist/index.js'
post: 'dist/index.cjs' post: 'dist/index.js'

View File

@@ -1,13 +1,12 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG NODE_VERSION=24 ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine AS base FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git rsync RUN apk add --no-cache cpio findutils git
WORKDIR /src WORKDIR /src
RUN --mount=type=bind,target=.,rw \ RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache <<EOT --mount=type=cache,target=/src/.yarn/cache <<EOT
set -e
corepack enable corepack enable
yarn --version yarn --version
yarn config set --home enableTelemetry 0 yarn config set --home enableTelemetry 0
@@ -35,27 +34,18 @@ RUN --mount=type=bind,target=.,rw <<EOT
EOT EOT
FROM deps AS build FROM deps AS build
RUN --mount=target=/context \ RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \ --mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules <<EOT --mount=type=cache,target=/src/node_modules \
set -e yarn run build && mkdir /out && cp -Rf dist /out/
rsync -a /context/. .
rm -rf dist
yarn run build
mkdir /out
cp -r dist /out
EOT
FROM scratch AS build-update FROM scratch AS build-update
COPY --from=build /out / COPY --from=build /out /
FROM build AS build-validate FROM build AS build-validate
RUN --mount=target=/context \ RUN --mount=type=bind,target=.,rw <<EOT
--mount=target=.,type=tmpfs <<EOT
set -e set -e
rsync -a /context/. .
git add -A git add -A
rm -rf dist
cp -rf /out/* . cp -rf /out/* .
if [ -n "$(git status --porcelain -- dist)" ]; then if [ -n "$(git status --porcelain -- dist)" ]; then
echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"' echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"'
@@ -68,7 +58,8 @@ FROM deps AS format
RUN --mount=type=bind,target=.,rw \ RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \ --mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \ --mount=type=cache,target=/src/node_modules \
yarn run format && mkdir /out && find . -name '*.ts' -not -path './node_modules/*' -not -path './.yarn/*' | cpio -pdm /out yarn run format \
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' -not -path './.yarn/*' | cpio -pdm /out
FROM scratch AS format-update FROM scratch AS format-update
COPY --from=format /out / COPY --from=format /out /
@@ -85,7 +76,7 @@ ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \ RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \ --mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \ --mount=type=cache,target=/src/node_modules \
yarn run test --coverage --coverage.reportsDirectory=/tmp/coverage yarn run test --coverage --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage FROM scratch AS test-coverage
COPY --from=test /tmp/coverage / COPY --from=test /tmp/coverage /

236
dist/index.cjs generated vendored

File diff suppressed because one or more lines are too long

7
dist/index.cjs.map generated vendored

File diff suppressed because one or more lines are too long

103
dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/index.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

20002
dist/licenses.txt generated vendored

File diff suppressed because it is too large Load Diff

1
dist/sourcemap-register.js generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,52 +0,0 @@
import {defineConfig} from 'eslint/config';
import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import vitest from '@vitest/eslint-plugin';
import globals from 'globals';
import eslintConfigPrettier from 'eslint-config-prettier/flat';
import eslintPluginPrettier from 'eslint-plugin-prettier';
export default defineConfig([
{
ignores: ['.yarn/**/*', 'coverage/**/*', 'dist/**/*']
},
js.configs.recommended,
...tseslint.configs['flat/recommended'],
eslintConfigPrettier,
{
languageOptions: {
globals: {
...globals.node
}
}
},
{
files: ['__tests__/**'],
...vitest.configs.recommended,
languageOptions: {
globals: {
...globals.node,
...vitest.environments.env.globals
}
},
rules: {
...vitest.configs.recommended.rules,
'vitest/no-conditional-expect': 'error',
'vitest/no-disabled-tests': 0
}
},
{
plugins: {
prettier: eslintPluginPrettier
},
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-require-imports': [
'error',
{
allowAsImport: true
}
]
}
}
]);

30
jest.config.ts Normal file
View File

@@ -0,0 +1,30 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-login-action-')).split(path.sep).join(path.posix.sep);
process.env = Object.assign({}, process.env, {
TEMP: tmpDir,
GITHUB_REPOSITORY: 'docker/login-action',
RUNNER_TEMP: path.join(tmpDir, 'runner-temp').split(path.sep).join(path.posix.sep),
RUNNER_TOOL_CACHE: path.join(tmpDir, 'runner-tool-cache').split(path.sep).join(path.posix.sep)
}) as {
[key: string]: string;
};
module.exports = {
clearMocks: true,
testEnvironment: 'node',
moduleFileExtensions: ['js', 'ts'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest'
},
moduleNameMapper: {
'^csv-parse/sync': '<rootDir>/node_modules/csv-parse/dist/cjs/sync.cjs'
},
collectCoverageFrom: ['src/**/{!(main.ts),}.ts'],
coveragePathIgnorePatterns: ['lib/', 'node_modules/', '__tests__/'],
verbose: true
};

View File

@@ -1,14 +1,16 @@
{ {
"name": "docker-login", "name": "docker-login",
"description": "GitHub Action to login against a Docker registry", "description": "GitHub Action to login against a Docker registry",
"type": "module",
"main": "src/main.ts", "main": "src/main.ts",
"scripts": { "scripts": {
"build": "esbuild src/main.ts --bundle --platform=node --target=node24 --format=cjs --outfile=dist/index.cjs --sourcemap --minify && yarn run license", "build": "ncc build --source-map --minify --license licenses.txt",
"lint": "eslint --max-warnings=0 .", "lint": "yarn run prettier && yarn run eslint",
"format": "eslint --fix .", "format": "yarn run prettier:fix && yarn run eslint:fix",
"test": "vitest run", "eslint": "eslint --max-warnings=0 .",
"license": "generate-license-file --input package.json --output dist/licenses.txt --overwrite --ci --no-spinner --eol lf" "eslint:fix": "eslint --fix .",
"prettier": "prettier --check \"./**/*.ts\"",
"prettier:fix": "prettier --write \"./**/*.ts\"",
"test": "jest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -23,30 +25,28 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"packageManager": "yarn@4.9.2", "packageManager": "yarn@4.9.2",
"dependencies": { "dependencies": {
"@actions/core": "^3.0.0", "@actions/core": "^1.11.1",
"@aws-sdk/client-ecr": "^3.1020.0", "@aws-sdk/client-ecr": "^3.965.0",
"@aws-sdk/client-ecr-public": "^3.1020.0", "@aws-sdk/client-ecr-public": "^3.965.0",
"@docker/actions-toolkit": "^0.86.0", "@docker/actions-toolkit": "^0.63.0",
"http-proxy-agent": "^9.0.0", "http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^9.0.0", "https-proxy-agent": "^7.0.6",
"js-yaml": "^4.1.1" "js-yaml": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.3",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/node": "^24.11.0", "@types/node": "^20.19.9",
"@typescript-eslint/eslint-plugin": "^8.56.1", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^8.56.1", "@typescript-eslint/parser": "^7.18.0",
"@vitest/coverage-v8": "^4.0.18", "@vercel/ncc": "^0.38.3",
"@vitest/eslint-plugin": "^1.6.9", "eslint": "^8.57.1",
"esbuild": "^0.28.0", "eslint-config-prettier": "^9.1.2",
"eslint": "^9.39.3", "eslint-plugin-jest": "^28.14.0",
"eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-prettier": "^5.5.5", "jest": "^29.7.0",
"generate-license-file": "^4.1.1", "prettier": "^3.6.2",
"globals": "^17.3.0", "ts-jest": "^29.4.1",
"prettier": "^3.8.1", "ts-node": "^10.9.2",
"typescript": "^5.9.3", "typescript": "^5.9.2"
"vitest": "^4.0.18"
} }
} }

View File

@@ -5,7 +5,7 @@ import {NodeHttpHandler} from '@smithy/node-http-handler';
import {HttpProxyAgent} from 'http-proxy-agent'; import {HttpProxyAgent} from 'http-proxy-agent';
import {HttpsProxyAgent} from 'https-proxy-agent'; import {HttpsProxyAgent} from 'https-proxy-agent';
const ecrRegistryRegex = /^(([0-9]{12})\.(dkr\.ecr|dkr-ecr)\.(.+)\.(on\.aws|amazonaws\.(com(.cn)?|eu)))(\/([^:]+)(:.+)?)?$/; const ecrRegistryRegex = /^(([0-9]{12})\.(dkr\.ecr|dkr-ecr)\.(.+)\.(on\.aws|amazonaws\.com(.cn)?))(\/([^:]+)(:.+)?)?$/;
const ecrPublicRegistryRegex = /public\.ecr\.aws|ecr-public\.aws\.com/; const ecrPublicRegistryRegex = /public\.ecr\.aws|ecr-public\.aws\.com/;
export const isECR = (registry: string): boolean => { export const isECR = (registry: string): boolean => {

View File

@@ -1,92 +1,21 @@
import path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as yaml from 'js-yaml';
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx.js';
import {Util} from '@docker/actions-toolkit/lib/util.js';
export interface Inputs { export interface Inputs {
registry: string; registry: string;
username: string; username: string;
password: string; password: string;
scope: string;
ecr: string; ecr: string;
logout: boolean; logout: boolean;
registryAuth: string; registryAuth: string;
} }
export interface Auth {
registry: string;
username: string;
password: string;
scope: string;
ecr: string;
configDir: string;
}
export function getInputs(): Inputs { export function getInputs(): Inputs {
return { return {
registry: core.getInput('registry'), registry: core.getInput('registry'),
username: core.getInput('username'), username: core.getInput('username'),
password: core.getInput('password'), password: core.getInput('password'),
scope: core.getInput('scope'),
ecr: core.getInput('ecr'), ecr: core.getInput('ecr'),
logout: core.getBooleanInput('logout'), logout: core.getBooleanInput('logout'),
registryAuth: core.getInput('registry-auth') registryAuth: core.getInput('registry-auth')
}; };
} }
export function getAuthList(inputs: Inputs): Array<Auth> {
if (inputs.registryAuth && (inputs.registry || inputs.username || inputs.password || inputs.scope || inputs.ecr)) {
throw new Error('Cannot use registry-auth with other inputs');
}
let auths: Array<Auth> = [];
if (!inputs.registryAuth) {
const registry = inputs.registry || 'docker.io';
auths.push({
registry,
username: inputs.username,
password: inputs.password,
scope: inputs.scope,
ecr: inputs.ecr || 'auto',
configDir: scopeToConfigDir(registry, inputs.scope)
});
} else {
auths = (yaml.load(inputs.registryAuth) as Array<Auth>).map(auth => {
core.setSecret(auth.password); // redacted in workflow logs
const registry = auth.registry || 'docker.io';
return {
registry,
username: auth.username,
password: auth.password,
scope: auth.scope,
ecr: auth.ecr || 'auto',
configDir: scopeToConfigDir(registry, auth.scope)
};
});
}
if (auths.length == 0) {
throw new Error('No registry to login');
}
return auths;
}
export function scopeToConfigDir(registry: string, scope?: string): string {
if (scopeDisabled() || !scope || scope === '') {
return '';
}
let configDir = path.join(Buildx.configDir, 'config', registry === 'docker.io' ? 'registry-1.docker.io' : registry);
if (scope.startsWith('@')) {
configDir += scope;
} else {
configDir = path.join(configDir, scope);
}
return configDir;
}
function scopeDisabled(): boolean {
if (process.env.DOCKER_LOGIN_SCOPE_DISABLED) {
return Util.parseBool(process.env.DOCKER_LOGIN_SCOPE_DISABLED);
}
return false;
}

View File

@@ -1,31 +1,19 @@
import * as aws from './aws';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {Docker} from '@docker/actions-toolkit/lib/docker/docker.js'; import {Docker} from '@docker/actions-toolkit/lib/docker/docker';
import * as aws from './aws.js'; export async function login(registry: string, username: string, password: string, ecr: string): Promise<void> {
import * as context from './context.js'; if (/true/i.test(ecr) || (ecr == 'auto' && aws.isECR(registry))) {
await loginECR(registry, username, password);
export async function login(auth: context.Auth): Promise<void> {
if (/true/i.test(auth.ecr) || (auth.ecr == 'auto' && aws.isECR(auth.registry))) {
await loginECR(auth.registry, auth.username, auth.password, auth.scope);
} else { } else {
await loginStandard(auth.registry, auth.username, auth.password, auth.scope); await loginStandard(registry, username, password);
} }
} }
export async function logout(registry: string, configDir: string): Promise<void> { export async function logout(registry: string): Promise<void> {
let envs: {[key: string]: string} | undefined;
if (configDir !== '') {
envs = Object.assign({}, process.env, {
DOCKER_CONFIG: configDir
}) as {
[key: string]: string;
};
core.info(`Alternative config dir: ${configDir}`);
}
await Docker.getExecOutput(['logout', registry], { await Docker.getExecOutput(['logout', registry], {
ignoreReturnCode: true, ignoreReturnCode: true
env: envs
}).then(res => { }).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.stderr.length > 0 && res.exitCode != 0) {
core.warning(res.stderr.trim()); core.warning(res.stderr.trim());
@@ -33,7 +21,7 @@ export async function logout(registry: string, configDir: string): Promise<void>
}); });
} }
export async function loginStandard(registry: string, username: string, password: string, scope?: string): Promise<void> { export async function loginStandard(registry: string, username: string, password: string): Promise<void> {
if (!username && !password) { if (!username && !password) {
throw new Error('Username and password required'); throw new Error('Username and password required');
} }
@@ -43,39 +31,38 @@ export async function loginStandard(registry: string, username: string, password
if (!password) { if (!password) {
throw new Error('Password required'); throw new Error('Password required');
} }
await loginExec(registry, username, password, scope);
}
export async function loginECR(registry: string, username: string, password: string, scope?: string): Promise<void> { const loginArgs: Array<string> = ['login', '--password-stdin'];
core.info(`Retrieving registries data through AWS SDK...`); loginArgs.push('--username', username);
const regDatas = await aws.getRegistriesData(registry, username, password); loginArgs.push(registry);
for (const regData of regDatas) {
await loginExec(regData.registry, regData.username, regData.password, scope);
}
}
async function loginExec(registry: string, username: string, password: string, scope?: string): Promise<void> { core.info(`Logging into ${registry}...`);
let envs: {[key: string]: string} | undefined; await Docker.getExecOutput(loginArgs, {
const configDir = context.scopeToConfigDir(registry, scope);
if (configDir !== '') {
envs = Object.assign({}, process.env, {
DOCKER_CONFIG: configDir
}) as {
[key: string]: string;
};
core.info(`Logging into ${registry} (scope ${scope})...`);
} else {
core.info(`Logging into ${registry}...`);
}
await Docker.getExecOutput(['login', '--password-stdin', '--username', username, registry], {
ignoreReturnCode: true, ignoreReturnCode: true,
silent: true, silent: true,
input: Buffer.from(password), input: Buffer.from(password)
env: envs
}).then(res => { }).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim()); throw new Error(res.stderr.trim());
} }
core.info('Login Succeeded!'); core.info(`Login Succeeded!`);
}); });
} }
export async function loginECR(registry: string, username: string, password: string): Promise<void> {
core.info(`Retrieving registries data through AWS SDK...`);
const regDatas = await aws.getRegistriesData(registry, username, password);
for (const regData of regDatas) {
core.info(`Logging into ${regData.registry}...`);
await Docker.getExecOutput(['login', '--password-stdin', '--username', regData.username, regData.registry], {
ignoreReturnCode: true,
silent: true,
input: Buffer.from(regData.password)
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
}
core.info('Login Succeeded!');
});
}
}

View File

@@ -1,25 +1,53 @@
import * as yaml from 'js-yaml';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as actionsToolkit from '@docker/actions-toolkit'; import * as actionsToolkit from '@docker/actions-toolkit';
import * as context from './context.js'; import * as context from './context';
import * as docker from './docker.js'; import * as docker from './docker';
import * as stateHelper from './state-helper.js'; import * as stateHelper from './state-helper';
interface Auth {
registry: string;
username: string;
password: string;
ecr: string;
}
export async function main(): Promise<void> { export async function main(): Promise<void> {
const inputs: context.Inputs = context.getInputs(); const inputs: context.Inputs = context.getInputs();
stateHelper.setLogout(inputs.logout); stateHelper.setLogout(inputs.logout);
const auths = context.getAuthList(inputs); if (inputs.registryAuth && (inputs.registry || inputs.username || inputs.password || inputs.ecr)) {
stateHelper.setRegistries(Array.from(new Map(auths.map(auth => [`${auth.registry}|${auth.configDir}`, {registry: auth.registry, configDir: auth.configDir} as stateHelper.RegistryState])).values())); throw new Error('Cannot use registry-auth with other inputs');
}
if (auths.length === 1) { if (!inputs.registryAuth) {
await docker.login(auths[0]); stateHelper.setRegistries([inputs.registry || 'docker.io']);
await docker.login(inputs.registry || 'docker.io', inputs.username, inputs.password, inputs.ecr || 'auto');
return; return;
} }
const auths = yaml.load(inputs.registryAuth) as Auth[];
if (auths.length == 0) {
throw new Error('No registry to login');
}
const registries: string[] = [];
for (const auth of auths) { for (const auth of auths) {
await core.group(`Login to ${auth.registry}`, async () => { if (!auth.registry) {
await docker.login(auth); registries.push('docker.io');
} else {
registries.push(auth.registry);
}
if (auth.password) {
core.setSecret(auth.password);
}
}
stateHelper.setRegistries(registries.filter((value, index, self) => self.indexOf(value) === index));
for (const auth of auths) {
await core.group(`Login to ${auth.registry || 'docker.io'}`, async () => {
await docker.login(auth.registry || 'docker.io', auth.username, auth.password, auth.ecr || 'auto');
}); });
} }
} }
@@ -28,10 +56,8 @@ async function post(): Promise<void> {
if (!stateHelper.logout) { if (!stateHelper.logout) {
return; return;
} }
for (const registryState of stateHelper.registries) { for (const registry of stateHelper.registries.split(',')) {
await core.group(`Logout from ${registryState.registry}`, async () => { await docker.logout(registry);
await docker.logout(registryState.registry, registryState.configDir);
});
} }
} }

View File

@@ -1,15 +1,10 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
export const registries = process.env['STATE_registries'] ? (JSON.parse(process.env['STATE_registries']) as Array<RegistryState>) : []; export const registries = process.env['STATE_registries'] || '';
export const logout = /true/i.test(process.env['STATE_logout'] || ''); export const logout = /true/i.test(process.env['STATE_logout'] || '');
export interface RegistryState { export function setRegistries(registries: string[]) {
registry: string; core.saveState('registries', registries.join(','));
configDir: string;
}
export function setRegistries(registries: Array<RegistryState>) {
core.saveState('registries', JSON.stringify(registries));
} }
export function setLogout(logout: boolean) { export function setLogout(logout: boolean) {

View File

@@ -1,8 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"esModuleInterop": true, "esModuleInterop": true,
"target": "es6",
"module": "commonjs",
"strict": true,
"newLine": "lf", "newLine": "lf",
"outDir": "./lib", "outDir": "./lib",
"rootDir": "./src", "rootDir": "./src",
@@ -11,7 +12,10 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"useUnknownInCatchVariables": false, "useUnknownInCatchVariables": false,
}, },
"include": [ "exclude": [
"src/**/*.ts" "./__tests__/**/*",
"./lib/**/*",
"node_modules",
"jest.config.ts"
] ]
} }

View File

@@ -1,16 +0,0 @@
import {defineConfig} from 'vitest/config';
export default defineConfig({
test: {
clearMocks: true,
environment: 'node',
setupFiles: ['./__tests__/setup.unit.ts'],
include: ['**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['clover'],
include: ['src/**/*.ts'],
exclude: ['src/**/main.ts']
}
}
});

8903
yarn.lock

File diff suppressed because it is too large Load Diff