Compare commits

..

No commits in common. "master" and "v1.9.0" have entirely different histories.

51 changed files with 8790 additions and 24515 deletions

View file

@ -1,12 +1,2 @@
/coverage /coverage
/node_modules
# Dependency directories
node_modules/
jspm_packages/
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View file

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

View file

@ -1,24 +0,0 @@
{
"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"
]
}

2
.gitattributes vendored
View file

@ -1,4 +1,2 @@
/.yarn/releases/** binary
/.yarn/plugins/** binary
/dist/** linguist-generated=true /dist/** linguist-generated=true
/lib/** linguist-generated=true /lib/** linguist-generated=true

1
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1 @@
* @crazy-max

View file

@ -1,3 +0,0 @@
# Code of conduct
- [Moby community guidelines](https://github.com/moby/moby/blob/master/CONTRIBUTING.md#moby-community-guidelines)

View file

@ -1,101 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Bug Report
description: Report a bug
labels:
- status/triage
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to report a bug!
If this is a security issue please report it to the [Docker Security team](mailto:security@docker.com).
- type: checkboxes
attributes:
label: Contributing guidelines
description: >
Make sure you've read the contributing guidelines before proceeding.
options:
- label: I've read the [contributing guidelines](https://github.com/docker/login-action/blob/master/.github/CONTRIBUTING.md) and wholeheartedly agree
required: true
- type: checkboxes
attributes:
label: "I've found a bug, and:"
description: |
Make sure that your request fulfills all of the following requirements.
If one requirement cannot be satisfied, explain in detail why.
options:
- label: The documentation does not mention anything about my problem
- label: There are no open or closed issues that are related to my problem
- type: textarea
attributes:
label: Description
description: >
Provide a brief description of the bug in 1-2 sentences.
validations:
required: true
- type: textarea
attributes:
label: Expected behaviour
description: >
Describe precisely what you'd expect to happen.
validations:
required: true
- type: textarea
attributes:
label: Actual behaviour
description: >
Describe precisely what is actually happening.
validations:
required: true
- type: input
attributes:
label: Repository URL
description: >
Enter the URL of the repository where you are experiencing the
issue. If your repository is private, provide a link to a minimal
repository that reproduces the issue.
- type: input
attributes:
label: Workflow run URL
description: >
Enter the URL of the GitHub Action workflow run if public (e.g.
`https://github.com/<user>/<repo>/actions/runs/<id>`)
- type: textarea
attributes:
label: YAML workflow
description: |
Provide the YAML of the workflow that's causing the issue.
Make sure to remove any sensitive information.
render: yaml
validations:
required: true
- type: textarea
attributes:
label: Workflow logs
description: >
[Attach](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/attaching-files)
the [log file of your workflow run](https://docs.github.com/en/actions/managing-workflow-runs/using-workflow-run-logs#downloading-logs)
and make sure to remove any sensitive information.
- type: textarea
attributes:
label: BuildKit logs
description: >
If applicable, provide the [BuildKit container logs](https://docs.docker.com/build/ci/github-actions/configure-builder/#buildkit-container-logs)
render: text
- type: textarea
attributes:
label: Additional info
description: |
Provide any additional information that could be useful.

34
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
---
### Behaviour
#### Steps to reproduce this issue
1.
2.
3.
#### Expected behaviour
> Tell us what should happen
#### Actual behaviour
> Tell us what happens instead
### Configuration
* Repository URL (if public):
* Build URL (if public):
```yml
# paste your YAML workflow file here and remove sensitive data
```
### Logs
> Download the [log file of your build](https://docs.github.com/en/actions/managing-workflow-runs/using-workflow-run-logs#downloading-logs)
> and [attach it](https://docs.github.com/en/github/managing-your-work-on-github/file-attachments-on-issues-and-pull-requests) to this issue.

View file

@ -1,9 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
blank_issues_enabled: true
contact_links:
- name: Questions and Discussions
url: https://github.com/docker/login-action/discussions/new
about: Use Github Discussions to ask questions and/or open discussion topics.
- name: Documentation
url: https://docs.docker.com/build/ci/github-actions/
about: Read the documentation.

View file

@ -1,15 +0,0 @@
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
name: Feature request
description: Missing functionality? Come tell us about it!
labels:
- kind/enhancement
- status/triage
body:
- type: textarea
id: description
attributes:
label: Description
description: What is the feature you want to see?
validations:
required: true

12
.github/SECURITY.md vendored
View file

@ -1,12 +0,0 @@
# Reporting security issues
The project maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
**Please _DO NOT_ file a public issue**, instead send your report privately to
[security@docker.com](mailto:security@docker.com).
Security reports are greatly appreciated, and we will publicly thank you for it.
We also like to send gifts&mdash;if you'd like Docker swag, make sure to let
us know. We currently do not offer a paid security bounty program, but are not
ruling it out in the future.

29
.github/SUPPORT.md vendored Normal file
View file

@ -0,0 +1,29 @@
# Support [![](https://isitmaintained.com/badge/resolution/docker/login-action.svg)](https://isitmaintained.com/project/docker/login-action)
## Reporting an issue
Please do a search in [open issues](https://github.com/docker/login-action/issues?utf8=%E2%9C%93&q=) to see if the issue or feature request has already been filed.
If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment.
:+1: - upvote
:-1: - downvote
If you cannot find an existing issue that describes your bug or feature, submit an issue using the guidelines below.
## Writing good bug reports and feature requests
File a single issue per problem and feature request.
* Do not enumerate multiple bugs or feature requests in the same issue.
* Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar, but have different causes.
The more information you can provide, the more likely someone will be successful reproducing the issue and finding a fix.
You are now ready to [create a new issue](https://github.com/docker/login-action/issues/new/choose)!
## Closure policy
* Issues that don't have the information requested above (when applicable) will be closed immediately and the poster directed to the support guidelines.
* Issues that go a week without a response from original poster are subject to closure at our discretion.

View file

@ -4,23 +4,19 @@ updates:
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
time: "06:00"
timezone: "Europe/Paris"
labels: labels:
- "dependencies" - ":game_die: dependencies"
- "bot" - ":robot: bot"
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/" directory: "/"
schedule: schedule:
interval: "daily" interval: "daily"
versioning-strategy: "increase" time: "06:00"
groups: timezone: "Europe/Paris"
aws-sdk-dependencies:
patterns:
- "*aws-sdk*"
proxy-agent-dependencies:
patterns:
- "*-proxy-agent"
allow: allow:
- dependency-type: "production" - dependency-type: "production"
labels: labels:
- "dependencies" - ":game_die: dependencies"
- "bot" - ":robot: bot"

BIN
.github/ghcr-manage-actions-access.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View file

@ -1,13 +1,9 @@
name: ci name: ci
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '0 10 * * *' - cron: '0 10 * * *' # everyday at 10am
push: push:
branches: branches:
- 'master' - 'master'
@ -19,7 +15,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Stop docker name: Stop docker
run: | run: |
@ -29,30 +25,9 @@ jobs:
uses: ./ uses: ./
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
logout:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
logout:
- false
- true
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Login to GitHub Container Registry
uses: ./
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
logout: ${{ matrix.logout }}
dind: dind:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@ -60,7 +35,7 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: ./ uses: ./
@ -80,32 +55,19 @@ jobs:
docker image prune -a -f >/dev/null 2>&1 docker image prune -a -f >/dev/null 2>&1
docker pull ghcr.io/docker-ghactiontest/test docker pull ghcr.io/docker-ghactiontest/test
acr:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Login to ACR
uses: ./
with:
registry: ${{ secrets.AZURE_REGISTRY_NAME }}.azurecr.io
username: ${{ secrets.AZURE_CLIENT_ID }}
password: ${{ secrets.AZURE_CLIENT_SECRET }}
dockerhub: dockerhub:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- ubuntu-latest - ubuntu-20.04
- windows-latest - ubuntu-18.04
- ubuntu-16.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to Docker Hub name: Login to Docker Hub
uses: ./ uses: ./
@ -119,17 +81,18 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- ubuntu-latest - ubuntu-20.04
- windows-latest - ubuntu-18.04
- ubuntu-16.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to ECR name: Login to ECR
uses: ./ uses: ./
with: with:
registry: ${{ secrets.AWS_ACCOUNT_NUMBER }}.dkr.ecr.us-east-1.amazonaws.com registry: ${{ secrets.AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com
username: ${{ secrets.AWS_ACCESS_KEY_ID }} username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
@ -139,24 +102,25 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- ubuntu-latest - ubuntu-20.04
- windows-latest - ubuntu-18.04
- ubuntu-16.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Configure AWS Credentials name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v1
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 }}
aws-region: us-east-1 aws-region: ${{ secrets.AWS_REGION }}
- -
name: Login to ECR name: Login to ECR
uses: ./ uses: ./
with: with:
registry: ${{ secrets.AWS_ACCOUNT_NUMBER }}.dkr.ecr.us-east-1.amazonaws.com registry: ${{ secrets.AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com
ecr-public: ecr-public:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@ -164,81 +128,55 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- ubuntu-latest - ubuntu-20.04
- windows-latest - ubuntu-18.04
- ubuntu-16.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to Public ECR name: Login to Public ECR
continue-on-error: ${{ matrix.os == 'windows-latest' }}
uses: ./ uses: ./
with: with:
registry: public.ecr.aws registry: public.ecr.aws
username: ${{ secrets.AWS_ACCESS_KEY_ID }} username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
env:
AWS_REGION: us-east-1
ecr-public-aws-creds: github-package:
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Configure AWS Credentials name: Login to GitHub Package Registry
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
-
name: Login to Public ECR
continue-on-error: ${{ matrix.os == 'windows-latest' }}
uses: ./ uses: ./
with: with:
registry: public.ecr.aws registry: docker.pkg.github.com
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
github-container: github-container:
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: ./ uses: ./
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
gitlab: gitlab:
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to GitLab name: Login to GitLab
uses: ./ uses: ./
@ -248,17 +186,11 @@ jobs:
password: ${{ secrets.GITLAB_TOKEN }} password: ${{ secrets.GITLAB_TOKEN }}
google-artifact: google-artifact:
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to Google Artifact Registry name: Login to Google Artifact Registry
uses: ./ uses: ./
@ -268,17 +200,11 @@ jobs:
password: ${{ secrets.GAR_JSON_KEY }} password: ${{ secrets.GAR_JSON_KEY }}
google-container: google-container:
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- -
name: Login to Google Container Registry name: Login to Google Container Registry
uses: ./ uses: ./

View file

@ -1,50 +0,0 @@
name: codeql
on:
push:
branches:
- 'master'
- 'releases/v*'
paths:
- '.github/workflows/codeql.yml'
- 'dist/**'
- 'src/**'
pull_request:
paths:
- '.github/workflows/codeql.yml'
- 'dist/**'
- 'src/**'
permissions:
actions: read
contents: read
security-events: write
jobs:
analyze:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language:
- javascript-typescript
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config: |
paths:
- src
-
name: Autobuild
uses: github/codeql-action/autobuild@v3
-
name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View file

@ -1,21 +0,0 @@
name: publish
on:
release:
types:
- published
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Publish
uses: actions/publish-immutable-action@v0.0.4

View file

@ -1,28 +1,34 @@
name: test name: test
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: on:
push: push:
branches: branches:
- 'master' - 'master'
- 'releases/v*' - 'releases/v*'
pull_request: pull_request:
branches:
- 'master'
- 'releases/v*'
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Validate
uses: docker/bake-action@v1
with:
targets: validate
- -
name: Test name: Test
uses: docker/bake-action@v6 uses: docker/bake-action@v1
with: with:
targets: test targets: test
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v1
with: with:
files: ./coverage/clover.xml file: ./coverage/clover.xml
token: ${{ secrets.CODECOV_TOKEN }}

View file

@ -1,43 +0,0 @@
name: validate
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- 'master'
- 'releases/v*'
pull_request:
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.generate.outputs.targets }}
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: List targets
id: generate
uses: docker/bake-action/subaction/list-targets@v6
with:
target: validate
validate:
runs-on: ubuntu-latest
needs:
- prepare
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.prepare.outputs.targets) }}
steps:
-
name: Validate
uses: docker/bake-action@v6
with:
targets: ${{ matrix.target }}

71
.gitignore vendored
View file

@ -1,5 +1,12 @@
# https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore /.dev
node_modules/
lib
# Jetbrains
/.idea
/*.iml
# Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs # Logs
logs logs
*.log *.log
@ -7,7 +14,6 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
lerna-debug.log* lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html) # Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
@ -18,14 +24,34 @@ pids
*.seed *.seed
*.pid.lock *.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul # Coverage directory used by tools like istanbul
coverage coverage
*.lcov *.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories # Dependency directories
node_modules/
jspm_packages/ jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache # TypeScript cache
*.tsbuildinfo *.tsbuildinfo
@ -35,19 +61,36 @@ jspm_packages/
# Optional eslint cache # Optional eslint cache
.eslintcache .eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
# dotenv environment variable files # dotenv environment variables file
.env .env
.env.development.local .env.test
.env.test.local
.env.production.local
.env.local
# yarn v2 # parcel-bundler cache (https://parceljs.org/)
.yarn/cache .cache
.yarn/unplugged
.yarn/build-state.yml # next.js build output
.yarn/install-state.gz .next
.pnp.*
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/

View file

@ -1,6 +0,0 @@
# Dependency directories
node_modules/
jspm_packages/
# yarn v2
.yarn/

View file

@ -1,5 +1,5 @@
{ {
"printWidth": 240, "printWidth": 120,
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"semi": true, "semi": true,

File diff suppressed because one or more lines are too long

View file

@ -1,13 +0,0 @@
logFilters:
- code: YN0013
level: discard
- code: YN0019
level: discard
- code: YN0076
level: discard
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"

323
README.md
View file

@ -1,7 +1,7 @@
[![GitHub release](https://img.shields.io/github/release/docker/login-action.svg?style=flat-square)](https://github.com/docker/login-action/releases/latest) [![GitHub release](https://img.shields.io/github/release/docker/login-action.svg?style=flat-square)](https://github.com/docker/login-action/releases/latest)
[![GitHub marketplace](https://img.shields.io/badge/marketplace-docker--login-blue?logo=github&style=flat-square)](https://github.com/marketplace/actions/docker-login) [![GitHub marketplace](https://img.shields.io/badge/marketplace-docker--login-blue?logo=github&style=flat-square)](https://github.com/marketplace/actions/docker-login)
[![CI workflow](https://img.shields.io/github/actions/workflow/status/docker/login-action/ci.yml?branch=master&label=ci&logo=github&style=flat-square)](https://github.com/docker/login-action/actions?workflow=ci) [![CI workflow](https://img.shields.io/github/workflow/status/docker/login-action/ci?label=ci&logo=github&style=flat-square)](https://github.com/docker/login-action/actions?workflow=ci)
[![Test workflow](https://img.shields.io/github/actions/workflow/status/docker/login-action/test.yml?branch=master&label=test&logo=github&style=flat-square)](https://github.com/docker/login-action/actions?workflow=test) [![Test workflow](https://img.shields.io/github/workflow/status/docker/login-action/test?label=test&logo=github&style=flat-square)](https://github.com/docker/login-action/actions?workflow=test)
[![Codecov](https://img.shields.io/codecov/c/github/docker/login-action?logo=codecov&style=flat-square)](https://codecov.io/gh/docker/login-action) [![Codecov](https://img.shields.io/codecov/c/github/docker/login-action?logo=codecov&style=flat-square)](https://codecov.io/gh/docker/login-action)
## About ## About
@ -14,6 +14,7 @@ ___
* [Usage](#usage) * [Usage](#usage)
* [Docker Hub](#docker-hub) * [Docker Hub](#docker-hub)
* [GitHub Packages Docker Registry](#github-packages-docker-registry)
* [GitHub Container Registry](#github-container-registry) * [GitHub Container Registry](#github-container-registry)
* [GitLab](#gitlab) * [GitLab](#gitlab)
* [Azure Container Registry (ACR)](#azure-container-registry-acr) * [Azure Container Registry (ACR)](#azure-container-registry-acr)
@ -23,25 +24,23 @@ ___
* [AWS Public Elastic Container Registry (ECR)](#aws-public-elastic-container-registry-ecr) * [AWS Public Elastic Container Registry (ECR)](#aws-public-elastic-container-registry-ecr)
* [OCI Oracle Cloud Infrastructure Registry (OCIR)](#oci-oracle-cloud-infrastructure-registry-ocir) * [OCI Oracle Cloud Infrastructure Registry (OCIR)](#oci-oracle-cloud-infrastructure-registry-ocir)
* [Quay.io](#quayio) * [Quay.io](#quayio)
* [DigitalOcean](#digitalocean-container-registry)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [Contributing](#contributing) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot)
## Usage ## Usage
### Docker Hub ### Docker Hub
When authenticating to [Docker Hub](https://hub.docker.com) with GitHub Actions, To authenticate against [Docker Hub](https://hub.docker.com) it's strongly recommended to create a
use a [personal access token](https://docs.docker.com/docker-hub/access-tokens/). [personal access token](https://docs.docker.com/docker-hub/access-tokens/) as an alternative to your password.
Don't use your account password.
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -49,24 +48,55 @@ jobs:
steps: steps:
- -
name: Login to Docker Hub name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
``` ```
### GitHub Container Registry ### GitHub Packages Docker Registry
To authenticate to the [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry), > :warning: GitHub Packages Docker Registry (aka `docker.pkg.github.com`) **is deprecated** and will sunset early next
use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) > year. It's strongly advised to [migrate to GitHub Container Registry](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images)
secret. > instead.
You can configure the Docker client to use [GitHub Packages to publish and retrieve docker images](https://docs.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-docker-for-use-with-github-packages).
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Login to GitHub Packages Docker Registry
uses: docker/login-action@v1
with:
registry: docker.pkg.github.com
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
```
### GitHub Container Registry
To use the [GitHub Container Registry](https://docs.github.com/en/packages/getting-started-with-github-container-registry),
you need to [enable this feature for your personal or organization account](https://docs.github.com/en/packages/guides/enabling-improved-container-support).
To [authenticate against it](https://docs.github.com/en/packages/guides/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry),
use the [`GITHUB_TOKEN`](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) for the best
security and experience.
```yaml
name: ci
on:
push:
branches: master
jobs: jobs:
login: login:
@ -74,15 +104,16 @@ jobs:
steps: steps:
- -
name: Login to GitHub Container Registry name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
``` ```
You may need to [manage write and read access of GitHub Actions](https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio) You may need to manage write and read access of GitHub Actions for repositories in the container settings:
for repositories in the container settings.
![Manage Actions access](.github/ghcr-manage-actions-access.gif)
You can also use a [personal access token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) You can also use a [personal access token (PAT)](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token)
with the [appropriate scopes](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry). with the [appropriate scopes](https://docs.github.com/en/packages/getting-started-with-github-container-registry/migrating-to-github-container-registry-for-docker-images#authenticating-with-the-container-registry).
@ -94,7 +125,7 @@ name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -102,30 +133,25 @@ jobs:
steps: steps:
- -
name: Login to GitLab name: Login to GitLab
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: registry.gitlab.com registry: registry.gitlab.com
username: ${{ vars.GITLAB_USERNAME }} username: ${{ secrets.GITLAB_USERNAME }}
password: ${{ secrets.GITLAB_PASSWORD }} password: ${{ secrets.GITLAB_PASSWORD }}
``` ```
If you have [Two-Factor Authentication](https://gitlab.com/help/user/profile/account/two_factor_authentication)
enabled, use a [Personal Access Token](https://gitlab.com/help/user/profile/personal_access_tokens)
instead of a password.
### Azure Container Registry (ACR) ### Azure Container Registry (ACR)
[Create a service principal](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-service-principal#create-a-service-principal) [Create a service principal](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-auth-service-principal#create-a-service-principal)
with access to your container registry through the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) with access to your container registry through the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli)
and take note of the generated service principal's ID (also called _client ID_) and take note of the generated service principal's ID (also called _client ID_) and password (also called _client secret_).
and password (also called _client secret_).
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -133,10 +159,10 @@ jobs:
steps: steps:
- -
name: Login to ACR name: Login to ACR
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: <registry-name>.azurecr.io registry: <registry-name>.azurecr.io
username: ${{ vars.AZURE_CLIENT_ID }} username: ${{ secrets.AZURE_CLIENT_ID }}
password: ${{ secrets.AZURE_CLIENT_SECRET }} password: ${{ secrets.AZURE_CLIENT_SECRET }}
``` ```
@ -144,69 +170,22 @@ jobs:
### Google Container Registry (GCR) ### Google Container Registry (GCR)
> [Google Artifact Registry](#google-artifact-registry-gar) is the evolution of > [Google Artifact Registry](#google-artifact-registry-gar) is the evolution of Google Container Registry. As a
> Google Container Registry. As a fully-managed service with support for both > fully-managed service with support for both container images and non-container artifacts. If you currently use
> container images and non-container artifacts. If you currently use Google > Google Container Registry, use the information [on this page](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)
> Container Registry, use the information [on this page](https://cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)
> to learn about transitioning to Google Artifact Registry. > to learn about transitioning to Google Artifact Registry.
You can authenticate with workload identity federation or a service account. Use a service account with the ability to push to GCR and [configure access control](https://cloud.google.com/container-registry/docs/access-control).
Then create and download the JSON key for this service account and save content of `.json` file
#### Workload identity federation [as a secret](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
called `GCR_JSON_KEY` in your GitHub repo. Ensure you set the username to `_json_key`.
Configure the workload identity federation for GitHub Actions in Google Cloud,
[see here](https://github.com/google-github-actions/auth#setting-up-workload-identity-federation).
Your service account must have permission to push to GCR. Use the
`google-github-actions/auth` action to authenticate using workload identity as
shown in the following example:
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v1
with:
token_format: access_token
workload_identity_provider: <workload_identity_provider>
service_account: <service_account>
-
name: Login to GCR
uses: docker/login-action@v3
with:
registry: gcr.io
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
```
> Replace `<workload_identity_provider>` with configured workload identity
> provider. For steps to configure, [see here](https://github.com/google-github-actions/auth#setting-up-workload-identity-federation).
> Replace `<service_account>` with configured service account in workload
> identity provider which has access to push to GCR
#### Service account based authentication
Use a service account with permission to push to GCR and [configure access control](https://cloud.google.com/container-registry/docs/access-control).
Download the key for the service account as a JSON file. Save the contents of
the file [as a secret](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
named `GCR_JSON_KEY` in your GitHub repository. Set the username to `_json_key`.
```yaml
name: ci
on:
push:
branches: main
jobs: jobs:
login: login:
@ -214,7 +193,7 @@ jobs:
steps: steps:
- -
name: Login to GCR name: Login to GCR
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: gcr.io registry: gcr.io
username: _json_key username: _json_key
@ -223,65 +202,17 @@ jobs:
### Google Artifact Registry (GAR) ### Google Artifact Registry (GAR)
You can authenticate with workload identity federation or a service account. Use a service account with the ability to push to GAR and [configure access control](https://cloud.google.com/artifact-registry/docs/access-control).
Then create and download the JSON key for this service account and save content of `.json` file
#### Workload identity federation [as a secret](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
called `GAR_JSON_KEY` in your GitHub repo. Ensure you set the username to `_json_key`.
Your service account must have permission to push to GAR. Use the
`google-github-actions/auth` action to authenticate using workload identity as
shown in the following example:
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Authenticate to Google Cloud
id: auth
uses: google-github-actions/auth@v1
with:
token_format: access_token
workload_identity_provider: <workload_identity_provider>
service_account: <service_account>
-
name: Login to GAR
uses: docker/login-action@v3
with:
registry: <location>-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}
```
> Replace `<workload_identity_provider>` with configured workload identity
> provider
> Replace `<service_account>` with configured service account in workload
> identity provider which has access to push to GCR
> Replace `<location>` with the regional or multi-regional [location](https://cloud.google.com/artifact-registry/docs/repo-organize#locations)
> of the repository where the image is stored.
#### Service account based authentication
Use a service account with permission to push to GAR and [configure access control](https://cloud.google.com/artifact-registry/docs/access-control).
Download the key for the service account as a JSON file. Save the contents of
the file [as a secret](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
named `GAR_JSON_KEY` in your GitHub repository. Set the username to `_json_key`,
or `_json_key_base64` if you use a base64-encoded key.
```yaml
name: ci
on:
push:
branches: main
jobs: jobs:
login: login:
@ -289,7 +220,7 @@ jobs:
steps: steps:
- -
name: Login to GAR name: Login to GAR
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: <location>-docker.pkg.dev registry: <location>-docker.pkg.dev
username: _json_key username: _json_key
@ -301,8 +232,8 @@ jobs:
### AWS Elastic Container Registry (ECR) ### AWS Elastic Container Registry (ECR)
Use an IAM user with the ability to [push to ECR with `AmazonEC2ContainerRegistryPowerUser` managed policy for example](https://docs.aws.amazon.com/AmazonECR/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonEC2ContainerRegistryPowerUser). Use an IAM user with the ability to [push to ECR with `AmazonEC2ContainerRegistryPowerUser` managed policy for example](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr_managed_policies.html#AmazonEC2ContainerRegistryPowerUser).
Download the access keys and save them as `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [as secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository) Then create and download access keys and save `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [as secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
in your GitHub repo. in your GitHub repo.
```yaml ```yaml
@ -310,7 +241,7 @@ name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -318,22 +249,22 @@ jobs:
steps: steps:
- -
name: Login to ECR name: Login to ECR
uses: docker/login-action@v3 uses: docker/login-action@v1
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: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
``` ```
If you need to log in to Amazon ECR registries associated with other accounts, If you need to log in to Amazon ECR registries associated with other accounts, you can use the `AWS_ACCOUNT_IDS`
you can use the `AWS_ACCOUNT_IDS` environment variable: environment variable:
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -341,10 +272,10 @@ jobs:
steps: steps:
- -
name: Login to ECR name: Login to ECR
uses: docker/login-action@v3 uses: docker/login-action@v1
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: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
env: env:
AWS_ACCOUNT_IDS: 012345678910,023456789012 AWS_ACCOUNT_IDS: 012345678910,023456789012
@ -352,15 +283,15 @@ jobs:
> Only available with [AWS CLI version 1](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html) > Only available with [AWS CLI version 1](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html)
You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) action in
action in combination with this action: combination with this action:
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -368,14 +299,14 @@ jobs:
steps: steps:
- -
name: Configure AWS Credentials name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4 uses: aws-actions/configure-aws-credentials@v1
with: with:
aws-access-key-id: ${{ vars.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 }}
aws-region: <region> aws-region: <region>
- -
name: Login to ECR name: Login to ECR
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com
``` ```
@ -384,17 +315,16 @@ jobs:
### AWS Public Elastic Container Registry (ECR) ### AWS Public Elastic Container Registry (ECR)
Use an IAM user with permission to push to ECR Public, for example using [managed policies](https://docs.aws.amazon.com/AmazonECR/latest/userguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonEC2ContainerRegistryPowerUser). Use an IAM user with the ability to [push to ECR Public with `AmazonElasticContainerRegistryPublicPowerUser` managed policy for example](https://docs.aws.amazon.com/AmazonECR/latest/public/public-ecr-managed-policies.html#AmazonElasticContainerRegistryPublicPowerUser).
Download the access keys and save them as `AWS_ACCESS_KEY_ID` and Then create and download access keys and save `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [as secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
`AWS_SECRET_ACCESS_KEY` [secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository) in your GitHub repo.
in your GitHub repository.
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -402,10 +332,10 @@ jobs:
steps: steps:
- -
name: Login to Public ECR name: Login to Public ECR
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: public.ecr.aws registry: public.ecr.aws
username: ${{ vars.AWS_ACCESS_KEY_ID }} username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
env: env:
AWS_REGION: <region> AWS_REGION: <region>
@ -428,7 +358,7 @@ name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -436,10 +366,10 @@ jobs:
steps: steps:
- -
name: Login to OCIR name: Login to OCIR
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: <region>.ocir.io registry: <region>.ocir.io
username: ${{ vars.OCI_USERNAME }} username: ${{ secrets.OCI_USERNAME }}
password: ${{ secrets.OCI_TOKEN }} password: ${{ secrets.OCI_TOKEN }}
``` ```
@ -447,15 +377,14 @@ jobs:
### Quay.io ### Quay.io
Use a [Robot account](https://docs.quay.io/glossary/robot-accounts.html) with Use a [Robot account](https://docs.quay.io/glossary/robot-accounts.html) with the ability to push to a public/private Quay.io repository.
permission to push to a Quay.io repository.
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: main branches: master
jobs: jobs:
login: login:
@ -463,52 +392,38 @@ jobs:
steps: steps:
- -
name: Login to Quay.io name: Login to Quay.io
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: quay.io registry: quay.io
username: ${{ vars.QUAY_USERNAME }} username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_ROBOT_TOKEN }} password: ${{ secrets.QUAY_ROBOT_TOKEN }}
``` ```
### DigitalOcean Container Registry
Use your DigitalOcean registered email address and an API access token to authenticate.
```yaml
name: ci
on:
push:
branches: main
jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Login to DigitalOcean Container Registry
uses: docker/login-action@v3
with:
registry: registry.digitalocean.com
username: ${{ vars.DIGITALOCEAN_USERNAME }}
password: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
```
## Customizing ## Customizing
### inputs ### inputs
The following inputs can be used as `step.with` keys: Following inputs can be used as `step.with` keys
| Name | Type | Default | Description | | Name | Type | Default | Description |
|------------|--------|---------|-------------------------------------------------------------------------------| |------------------|---------|-----------------------------|------------------------------------|
| `registry` | String | | Server address of Docker registry. If not set then will default to Docker Hub | | `registry` | String | | 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 used to log against the Docker registry |
| `password` | String | | Password or personal access token for authenticating the Docker registry | | `password` | String | | Password or personal access token used to log against the Docker registry |
| `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 |
## Contributing ## Keep up-to-date with GitHub Dependabot
Want to contribute? Awesome! You can find information about contributing to Since [Dependabot](https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-github-dependabot)
this project in the [CONTRIBUTING.md](/.github/CONTRIBUTING.md) has [native GitHub Actions support](https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#package-ecosystem),
to enable it on your GitHub repo all you need to do is add the `.github/dependabot.yml` file:
```yaml
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
```

View file

@ -1,6 +1,4 @@
import {beforeEach, describe, expect, jest, test} from '@jest/globals'; import * as semver from 'semver';
import {AuthorizationData} from '@aws-sdk/client-ecr';
import * as aws from '../src/aws'; import * as aws from '../src/aws';
describe('isECR', () => { describe('isECR', () => {
@ -12,7 +10,7 @@ describe('isECR', () => {
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', true], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', true],
['public.ecr.aws', true] ['public.ecr.aws', true]
])('given registry %p', async (registry, expected) => { ])('given registry %p', async (registry, expected) => {
expect(aws.isECR(registry)).toEqual(expected); expect(await aws.isECR(registry)).toEqual(expected);
}); });
}); });
@ -25,7 +23,40 @@ describe('isPubECR', () => {
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', false], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', false],
['public.ecr.aws', true] ['public.ecr.aws', true]
])('given registry %p', async (registry, expected) => { ])('given registry %p', async (registry, expected) => {
expect(aws.isPubECR(registry)).toEqual(expected); expect(await aws.isPubECR(registry)).toEqual(expected);
});
});
describe('getCLI', () => {
it('exists', async () => {
const awsPath = await aws.getCLI();
console.log(`awsPath: ${awsPath}`);
expect(awsPath).not.toEqual('');
});
});
describe('execCLI', () => {
it('--version not empty', async () => {
const cliCmdOutput = await aws.execCLI(['--version']);
console.log(`cliCmdOutput: ${cliCmdOutput}`);
expect(cliCmdOutput).not.toEqual('');
}, 100000);
});
describe('getCLIVersion', () => {
it('valid', async () => {
const cliVersion = await aws.getCLIVersion();
console.log(`cliVersion: ${cliVersion}`);
expect(semver.valid(cliVersion)).not.toBeNull();
}, 100000);
});
describe('parseCLIVersion', () => {
test.each([
['v1', 'aws-cli/1.18.120 Python/2.7.17 Linux/5.3.0-1034-azure botocore/1.17.43', '1.18.120'],
['v2', 'aws-cli/2.0.41 Python/3.7.3 Linux/4.19.104-microsoft-standard exe/x86_64.ubuntu.18', '2.0.41']
])('given aws %p', async (version, stdout, expected) => {
expect(await aws.parseCLIVersion(stdout)).toEqual(expected);
}); });
}); });
@ -36,122 +67,33 @@ describe('getRegion', () => {
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-1'], ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-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(await aws.getRegion(registry)).toEqual(expected);
}); });
}); });
describe('getAccountIDs', () => { describe('getAccountIDs', () => {
test.each([ test.each([
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', undefined, ['012345678901']], ['012345678901.dkr.ecr.eu-west-3.amazonaws.com', undefined, ['012345678901']],
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', '012345678910,023456789012', ['012345678901', '012345678910', '023456789012']], [
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', '012345678901,012345678910,023456789012', ['012345678901', '012345678910', '023456789012']], '012345678901.dkr.ecr.eu-west-3.amazonaws.com',
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', '012345678910,023456789012', ['390948362332', '012345678910', '023456789012']], '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']
],
['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) {
process.env.AWS_ACCOUNT_IDS = accountIDsEnv; process.env.AWS_ACCOUNT_IDS = accountIDsEnv;
} }
expect(aws.getAccountIDs(registry)).toEqual(expected); expect(await aws.getAccountIDs(registry)).toEqual(expected);
});
});
const mockEcrGetAuthToken = jest.fn();
const mockEcrPublicGetAuthToken = jest.fn();
jest.mock('@aws-sdk/client-ecr', () => {
return {
ECR: jest.fn(() => ({
getAuthorizationToken: mockEcrGetAuthToken
}))
};
});
jest.mock('@aws-sdk/client-ecr-public', () => {
return {
ECRPUBLIC: jest.fn(() => ({
getAuthorizationToken: mockEcrPublicGetAuthToken
}))
};
});
describe('getRegistriesData', () => {
beforeEach(() => {
jest.clearAllMocks();
delete process.env.AWS_ACCOUNT_IDS;
});
// prettier-ignore
test.each([
[
'012345678901.dkr.ecr.aws-region-1.amazonaws.com',
'dkr.ecr.aws-region-1.amazonaws.com', undefined,
[
{
registry: '012345678901.dkr.ecr.aws-region-1.amazonaws.com',
username: '012345678901',
password: 'world'
}
]
],
[
'012345678901.dkr.ecr.eu-west-3.amazonaws.com',
'dkr.ecr.eu-west-3.amazonaws.com',
'012345678910,023456789012',
[
{
registry: '012345678901.dkr.ecr.eu-west-3.amazonaws.com',
username: '012345678901',
password: 'world'
},
{
registry: '012345678910.dkr.ecr.eu-west-3.amazonaws.com',
username: '012345678910',
password: 'world'
},
{
registry: '023456789012.dkr.ecr.eu-west-3.amazonaws.com',
username: '023456789012',
password: 'world'
}
]
],
[
'public.ecr.aws',
undefined,
undefined,
[
{
registry: 'public.ecr.aws',
username: 'AWS',
password: 'world'
}
]
]
])('given registry %p', async (registry, fqdn, accountIDsEnv, expected: aws.RegistryData[]) => {
if (accountIDsEnv) {
process.env.AWS_ACCOUNT_IDS = accountIDsEnv;
}
const accountIDs = aws.getAccountIDs(registry);
const authData: AuthorizationData[] = [];
if (accountIDs.length == 0) {
mockEcrPublicGetAuthToken.mockImplementation(() => {
return Promise.resolve({
authorizationData: {
authorizationToken: Buffer.from(`AWS:world`).toString('base64'),
}
});
});
} else {
aws.getAccountIDs(registry).forEach(accountID => {
authData.push({
authorizationToken: Buffer.from(`${accountID}:world`).toString('base64'),
proxyEndpoint: `${accountID}.${fqdn}`
});
});
mockEcrGetAuthToken.mockImplementation(() => {
return Promise.resolve({
authorizationData: authData
});
});
}
const regData = await aws.getRegistriesData(registry);
expect(regData).toEqual(expected);
}); });
}); });

View file

@ -1,12 +1,11 @@
import {expect, test} from '@jest/globals'; import osm = require('os');
import {getInputs} from '../src/context'; import {getInputs} from '../src/context';
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';
process.env['INPUT_PASSWORD'] = 'groundcontrol'; process.env['INPUT_PASSWORD'] = 'groundcontrol';
process.env['INPUT_LOGOUT'] = 'true';
expect(() => { expect(() => {
getInputs(); getInputs();
}).not.toThrow(); }).not.toThrowError();
}); });

View file

@ -1,64 +1,49 @@
import {expect, jest, test} from '@jest/globals';
import * as path from 'path';
import {loginStandard, logout} from '../src/docker'; import {loginStandard, logout} from '../src/docker';
import {Docker} from '@docker/actions-toolkit/lib/docker/docker'; import * as path from 'path';
import * as exec from '@actions/exec';
process.env['RUNNER_TEMP'] = path.join(__dirname, 'runner'); process.env['RUNNER_TEMP'] = path.join(__dirname, 'runner');
test('loginStandard calls exec', async () => { test('loginStandard calls exec', async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment const execSpy: jest.SpyInstance = jest.spyOn(exec, 'exec');
// @ts-ignore // don't let exec try to actually run the commands
const execSpy = jest.spyOn(Docker, 'getExecOutput').mockImplementation(async () => { execSpy.mockImplementation(() => {});
return {
exitCode: expect.any(Number),
stdout: expect.any(Function),
stderr: expect.any(Function)
};
});
const username = 'dbowie'; const username: string = 'dbowie';
const password = 'groundcontrol'; const password: string = 'groundcontrol';
const registry = 'https://ghcr.io'; const registry: string = 'https://ghcr.io';
await loginStandard(registry, username, password); await loginStandard(registry, username, password);
expect(execSpy).toHaveBeenCalledTimes(1); expect(execSpy).toHaveBeenCalledWith(`docker`, ['login', '--password-stdin', '--username', username, registry], {
const callfunc = execSpy.mock.calls[0];
if (callfunc && callfunc[1]) {
// we don't want to check env opt
callfunc[1].env = undefined;
}
expect(execSpy).toHaveBeenCalledWith(['login', '--password-stdin', '--username', username, registry], {
input: Buffer.from(password), input: Buffer.from(password),
silent: true, silent: true,
ignoreReturnCode: true ignoreReturnCode: true,
listeners: expect.objectContaining({
stdout: expect.any(Function),
stderr: expect.any(Function)
})
}); });
}); });
test('logout calls exec', async () => { test('logout calls exec', async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment const execSpy: jest.SpyInstance = jest.spyOn(exec, 'exec');
// @ts-ignore // don't let exec try to actually run the commands
const execSpy = jest.spyOn(Docker, 'getExecOutput').mockImplementation(async () => { execSpy.mockImplementation(() => {});
return {
exitCode: expect.any(Number),
stdout: expect.any(Function),
stderr: expect.any(Function)
};
});
const registry = 'https://ghcr.io'; const registry: string = 'https://ghcr.io';
await logout(registry); await logout(registry);
expect(execSpy).toHaveBeenCalledTimes(1); expect(execSpy).toHaveBeenCalledWith(`docker`, ['logout', registry], {
const callfunc = execSpy.mock.calls[0]; silent: false,
if (callfunc && callfunc[1]) { ignoreReturnCode: true,
// we don't want to check env opt input: Buffer.from(''),
callfunc[1].env = undefined; listeners: expect.objectContaining({
} stdout: expect.any(Function),
expect(execSpy).toHaveBeenCalledWith(['logout', registry], { stderr: expect.any(Function)
ignoreReturnCode: true })
}); });
}); });

68
__tests__/main.test.ts Normal file
View file

@ -0,0 +1,68 @@
import osm = require('os');
import {run} from '../src/main';
import * as docker from '../src/docker';
import * as stateHelper from '../src/state-helper';
import * as core from '@actions/core';
test('errors without username and password', async () => {
const platSpy = jest.spyOn(osm, 'platform');
platSpy.mockImplementation(() => 'linux');
const coreSpy: jest.SpyInstance = jest.spyOn(core, 'setFailed');
await run();
expect(coreSpy).toHaveBeenCalledWith('Username and password required');
});
test('successful with username and password', async () => {
const platSpy = jest.spyOn(osm, 'platform');
platSpy.mockImplementation(() => 'linux');
const setRegistrySpy: jest.SpyInstance = jest.spyOn(stateHelper, 'setRegistry');
const setLogoutSpy: jest.SpyInstance = jest.spyOn(stateHelper, 'setLogout');
const dockerSpy: jest.SpyInstance = jest.spyOn(docker, 'login');
dockerSpy.mockImplementation(() => {});
const username: string = 'dbowie';
process.env[`INPUT_USERNAME`] = username;
const password: string = 'groundcontrol';
process.env[`INPUT_PASSWORD`] = password;
await run();
expect(setRegistrySpy).toHaveBeenCalledWith('');
expect(setLogoutSpy).toHaveBeenCalledWith('');
expect(dockerSpy).toHaveBeenCalledWith('', username, password);
});
test('calls docker login', async () => {
const platSpy = jest.spyOn(osm, 'platform');
platSpy.mockImplementation(() => 'linux');
const setRegistrySpy: jest.SpyInstance = jest.spyOn(stateHelper, 'setRegistry');
const setLogoutSpy: jest.SpyInstance = jest.spyOn(stateHelper, 'setLogout');
const dockerSpy: jest.SpyInstance = jest.spyOn(docker, 'login');
dockerSpy.mockImplementation(() => {});
const username: string = 'dbowie';
process.env[`INPUT_USERNAME`] = username;
const password: string = 'groundcontrol';
process.env[`INPUT_PASSWORD`] = password;
const registry: string = 'ghcr.io';
process.env[`INPUT_REGISTRY`] = registry;
const logout: string = 'true';
process.env['INPUT_LOGOUT'] = logout;
await run();
expect(setRegistrySpy).toHaveBeenCalledWith(registry);
expect(setLogoutSpy).toHaveBeenCalledWith(logout);
expect(dockerSpy).toHaveBeenCalledWith(registry, username, password);
});

View file

@ -16,16 +16,12 @@ inputs:
password: password:
description: 'Password or personal access token used to log against the Docker registry' description: 'Password or personal access token used to log against the Docker registry'
required: false required: false
ecr:
description: 'Specifies whether the given registry is ECR (auto, true or false)'
default: 'auto'
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'
required: false required: false
runs: runs:
using: 'node20' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'
post: 'dist/index.js' post: 'dist/index.js'

View file

@ -1,3 +0,0 @@
comment: false
github_checks:
annotations: false

View file

@ -1,82 +0,0 @@
# syntax=docker/dockerfile:1
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git
WORKDIR /src
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache <<EOT
corepack enable
yarn --version
yarn config set --home enableTelemetry 0
EOT
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn install && mkdir /vendor && cp yarn.lock /vendor
FROM scratch AS vendor-update
COPY --from=deps /vendor /
FROM deps AS vendor-validate
RUN --mount=type=bind,target=.,rw <<EOT
set -e
git add -A
cp -rf /vendor/* .
if [ -n "$(git status --porcelain -- yarn.lock)" ]; then
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor"'
git status --porcelain -- yarn.lock
exit 1
fi
EOT
FROM deps AS build
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run build && mkdir /out && cp -Rf dist /out/
FROM scratch AS build-update
COPY --from=build /out /
FROM build AS build-validate
RUN --mount=type=bind,target=.,rw <<EOT
set -e
git add -A
cp -rf /out/* .
if [ -n "$(git status --porcelain -- dist)" ]; then
echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"'
git status --porcelain -- dist
exit 1
fi
EOT
FROM deps AS format
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--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
FROM scratch AS format-update
COPY --from=format /out /
FROM deps AS lint
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run lint
FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run test --coverage --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage /

4320
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

1
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

14887
dist/licenses.txt generated vendored

File diff suppressed because it is too large Load diff

1
dist/sourcemap-register.js generated vendored

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,10 @@
target "_common" { variable "NODE_VERSION" {
default = "12"
}
target "node-version" {
args = { args = {
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1 NODE_VERSION = NODE_VERSION
} }
} }
@ -9,58 +13,55 @@ group "default" {
} }
group "pre-checkin" { group "pre-checkin" {
targets = ["vendor", "format", "build"] targets = ["vendor-update", "format", "build"]
} }
group "validate" { group "validate" {
targets = ["lint", "build-validate", "vendor-validate"] targets = ["format-validate", "build-validate", "vendor-validate"]
} }
target "build" { target "build" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/build.Dockerfile"
target = "build-update" target = "build-update"
output = ["."] output = ["."]
} }
target "build-validate" { target "build-validate" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/build.Dockerfile"
target = "build-validate" target = "build-validate"
output = ["type=cacheonly"]
} }
target "format" { target "format" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/build.Dockerfile"
target = "format-update" target = "format-update"
output = ["."] output = ["."]
} }
target "lint" { target "format-validate" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/build.Dockerfile"
target = "lint" target = "format-validate"
output = ["type=cacheonly"]
} }
target "vendor" { target "vendor-update" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/vendor.Dockerfile"
target = "vendor-update" target = "update"
output = ["."] output = ["."]
} }
target "vendor-validate" { target "vendor-validate" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/vendor.Dockerfile"
target = "vendor-validate" target = "validate"
output = ["type=cacheonly"]
} }
target "test" { target "test" {
inherits = ["_common"] inherits = ["node-version"]
dockerfile = "dev.Dockerfile" dockerfile = "./hack/test.Dockerfile"
target = "test-coverage" target = "test-coverage"
output = ["./coverage"] output = ["./coverage"]
} }

42
hack/build.Dockerfile Normal file
View file

@ -0,0 +1,42 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install
FROM deps AS build
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run build && mkdir /out && cp -Rf dist /out/
FROM scratch AS build-update
COPY --from=build /out /
FROM build AS build-validate
RUN --mount=type=bind,target=.,rw \
git add -A && cp -rf /out/* .; \
if [ -n "$(git status --porcelain -- dist)" ]; then \
echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"'; \
git status --porcelain -- dist; \
exit 1; \
fi
FROM deps AS format
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run format \
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' | cpio -pdm /out
FROM scratch AS format-update
COPY --from=format /out /
FROM deps AS format-validate
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run format-check \

34
hack/test.Dockerfile Normal file
View file

@ -0,0 +1,34 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache binutils curl git unzip
ENV GLIBC_VER=2.31-r0
RUN curl -sL "https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub" -o "/etc/apk/keys/sgerrand.rsa.pub" \
&& curl -sLO "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-${GLIBC_VER}.apk" \
&& curl -sLO "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VER}/glibc-bin-${GLIBC_VER}.apk" \
&& apk add --no-cache \
glibc-${GLIBC_VER}.apk \
glibc-bin-${GLIBC_VER}.apk \
&& curl -sL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& unzip -qq "awscliv2.zip" \
&& ./aws/install \
&& aws --version
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install
FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
--mount=type=bind,from=crazymax/docker,source=/usr/libexec/docker/cli-plugins/docker-buildx,target=/usr/libexec/docker/cli-plugins/docker-buildx \
--mount=type=bind,from=crazymax/docker,source=/usr/local/bin/docker,target=/usr/bin/docker \
yarn run test --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage /

23
hack/vendor.Dockerfile Normal file
View file

@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache git
WORKDIR /src
FROM base AS vendored
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install && mkdir /out && cp yarn.lock /out
FROM scratch AS update
COPY --from=vendored /out /
FROM vendored AS validate
RUN --mount=type=bind,target=.,rw \
git add -A && cp -rf /out/* .; \
if [ -n "$(git status --porcelain -- yarn.lock)" ]; then \
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor-update"'; \
git status --porcelain -- yarn.lock; \
exit 1; \
fi

12
jest.config.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
setupFiles: ["dotenv/config"],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: false
}

View file

@ -1,30 +0,0 @@
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,16 +1,13 @@
{ {
"name": "docker-login", "name": "docker-login",
"description": "GitHub Action to login against a Docker registry", "description": "GitHub Action to login against a Docker registry",
"main": "src/main.ts", "main": "lib/main.js",
"scripts": { "scripts": {
"build": "ncc build --source-map --minify --license licenses.txt", "build": "tsc && ncc build",
"lint": "yarn run prettier && yarn run eslint", "format": "prettier --write **/*.ts",
"format": "yarn run prettier:fix && yarn run eslint:fix", "format-check": "prettier --check **/*.ts",
"eslint": "eslint --max-warnings=0 .", "test": "jest --coverage",
"eslint:fix": "eslint --fix .", "pre-checkin": "yarn run format && yarn run build"
"prettier": "prettier --check \"./**/*.ts\"",
"prettier:fix": "prettier --write \"./**/*.ts\"",
"test": "jest"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -21,30 +18,31 @@
"docker", "docker",
"login" "login"
], ],
"author": "Docker Inc.", "author": "Docker",
"license": "Apache-2.0", "contributors": [
"packageManager": "yarn@3.6.3", {
"name": "CrazyMax",
"url": "https://crazymax.dev"
}
],
"license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.11.1", "@actions/core": "^1.2.6",
"@aws-sdk/client-ecr": "^3.766.0", "@actions/exec": "^1.0.4",
"@aws-sdk/client-ecr-public": "^3.758.0", "@actions/io": "^1.0.2",
"@docker/actions-toolkit": "^0.57.0", "semver": "^7.3.2"
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.12.12", "@types/jest": "^26.0.3",
"@typescript-eslint/eslint-plugin": "^7.9.0", "@types/node": "^14.0.14",
"@typescript-eslint/parser": "^7.9.0", "@vercel/ncc": "^0.23.0",
"@vercel/ncc": "^0.38.1", "dotenv": "^8.2.0",
"eslint": "^8.57.0", "jest": "^26.1.0",
"eslint-config-prettier": "^9.1.0", "jest-circus": "^26.1.0",
"eslint-plugin-jest": "^28.5.0", "jest-runtime": "^26.1.0",
"eslint-plugin-prettier": "^5.1.3", "prettier": "^2.0.5",
"jest": "^29.7.0", "ts-jest": "^26.1.1",
"prettier": "^3.2.5", "typescript": "^3.9.5",
"ts-jest": "^29.1.2", "typescript-formatter": "^7.2.2"
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
} }
} }

View file

@ -1,9 +1,6 @@
import * as core from '@actions/core'; import * as semver from 'semver';
import {ECR} from '@aws-sdk/client-ecr'; import * as io from '@actions/io';
import {ECRPUBLIC} from '@aws-sdk/client-ecr-public'; import * as execm from './exec';
import {NodeHttpHandler} from '@smithy/node-http-handler';
import {HttpProxyAgent} from 'http-proxy-agent';
import {HttpsProxyAgent} from 'https-proxy-agent';
const ecrRegistryRegex = /^(([0-9]{12})\.dkr\.ecr\.(.+)\.amazonaws\.com(.cn)?)(\/([^:]+)(:.+)?)?$/; const ecrRegistryRegex = /^(([0-9]{12})\.dkr\.ecr\.(.+)\.amazonaws\.com(.cn)?)(\/([^:]+)(:.+)?)?$/;
@ -34,104 +31,63 @@ export const getAccountIDs = (registry: string): string[] => {
if (!matches) { if (!matches) {
return []; return [];
} }
const accountIDs: Array<string> = [matches[2]]; let accountIDs: Array<string> = [matches[2]];
if (process.env.AWS_ACCOUNT_IDS) { if (process.env.AWS_ACCOUNT_IDS) {
accountIDs.push(...process.env.AWS_ACCOUNT_IDS.split(',')); accountIDs.push(...process.env.AWS_ACCOUNT_IDS.split(','));
} }
return accountIDs.filter((item, index) => accountIDs.indexOf(item) === index); return accountIDs.filter((item, index) => accountIDs.indexOf(item) === index);
}; };
export interface RegistryData { export const getCLI = async (): Promise<string> => {
registry: string; return io.which('aws', true);
username: string; };
password: string;
}
export const getRegistriesData = async (registry: string, username?: string, password?: string): Promise<RegistryData[]> => { export const execCLI = async (args: string[]): Promise<string> => {
const region = getRegion(registry); return execm.exec(await getCLI(), args, true).then(res => {
const accountIDs = getAccountIDs(registry); if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
const authTokenRequest = {}; } else if (res.stderr != '') {
if (accountIDs.length > 0) { return res.stderr.trim();
core.debug(`Requesting AWS ECR auth token for ${accountIDs.join(', ')}`);
authTokenRequest['registryIds'] = accountIDs;
}
let httpProxyAgent;
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY || '';
if (httpProxy) {
core.debug(`Using http proxy ${httpProxy}`);
httpProxyAgent = new HttpProxyAgent(httpProxy);
}
let httpsProxyAgent;
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY || '';
if (httpsProxy) {
core.debug(`Using https proxy ${httpsProxy}`);
httpsProxyAgent = new HttpsProxyAgent(httpsProxy);
}
const credentials =
username && password
? {
accessKeyId: username,
secretAccessKey: password
}
: undefined;
if (isPubECR(registry)) {
core.info(`AWS Public ECR detected with ${region} region`);
const ecrPublic = new ECRPUBLIC({
customUserAgent: 'docker-login-action',
credentials,
region: region,
requestHandler: new NodeHttpHandler({
httpAgent: httpProxyAgent,
httpsAgent: httpsProxyAgent
})
});
const authTokenResponse = await ecrPublic.getAuthorizationToken(authTokenRequest);
if (!authTokenResponse.authorizationData || !authTokenResponse.authorizationData.authorizationToken) {
throw new Error('Could not retrieve an authorization token from AWS Public ECR');
}
const authToken = Buffer.from(authTokenResponse.authorizationData.authorizationToken, 'base64').toString('utf-8');
const creds = authToken.split(':', 2);
core.setSecret(creds[0]); // redacted in workflow logs
core.setSecret(creds[1]); // redacted in workflow logs
return [
{
registry: 'public.ecr.aws',
username: creds[0],
password: creds[1]
}
];
} else { } else {
core.info(`AWS ECR detected with ${region} region`); return res.stdout.trim();
const ecr = new ECR({
customUserAgent: 'docker-login-action',
credentials,
region: region,
requestHandler: new NodeHttpHandler({
httpAgent: httpProxyAgent,
httpsAgent: httpsProxyAgent
})
});
const authTokenResponse = await ecr.getAuthorizationToken(authTokenRequest);
if (!Array.isArray(authTokenResponse.authorizationData) || !authTokenResponse.authorizationData.length) {
throw new Error('Could not retrieve an authorization token from AWS ECR');
} }
const regDatas: RegistryData[] = [];
for (const authData of authTokenResponse.authorizationData) {
const authToken = Buffer.from(authData.authorizationToken || '', 'base64').toString('utf-8');
const creds = authToken.split(':', 2);
core.setSecret(creds[0]); // redacted in workflow logs
core.setSecret(creds[1]); // redacted in workflow logs
regDatas.push({
registry: authData.proxyEndpoint || '',
username: creds[0],
password: creds[1]
}); });
};
export const getCLIVersion = async (): Promise<string> => {
return parseCLIVersion(await execCLI(['--version']));
};
export const parseCLIVersion = async (stdout: string): Promise<string> => {
const matches = /aws-cli\/([0-9.]+)/.exec(stdout);
if (!matches) {
throw new Error(`Cannot parse AWS CLI version`);
} }
return regDatas; return semver.clean(matches[1]);
};
export const getDockerLoginCmds = async (
cliVersion: string,
registry: string,
region: string,
accountIDs: string[]
): Promise<string[]> => {
let ecrCmd = (await isPubECR(registry)) ? 'ecr-public' : 'ecr';
if (semver.satisfies(cliVersion, '>=2.0.0') || (await isPubECR(registry))) {
return execCLI([ecrCmd, 'get-login-password', '--region', region]).then(pwd => {
return [`docker login --username AWS --password ${pwd} ${registry}`];
});
} else {
return execCLI([
ecrCmd,
'get-login',
'--region',
region,
'--registry-ids',
accountIDs.join(' '),
'--no-include-email'
]).then(dockerLoginCmds => {
return dockerLoginCmds.trim().split(`\n`);
});
} }
}; };

View file

@ -4,8 +4,7 @@ export interface Inputs {
registry: string; registry: string;
username: string; username: string;
password: string; password: string;
ecr: string; logout: string;
logout: boolean;
} }
export function getInputs(): Inputs { export function getInputs(): Inputs {
@ -13,7 +12,6 @@ export function getInputs(): Inputs {
registry: core.getInput('registry'), registry: core.getInput('registry'),
username: core.getInput('username'), username: core.getInput('username'),
password: core.getInput('password'), password: core.getInput('password'),
ecr: core.getInput('ecr'), logout: core.getInput('logout')
logout: core.getBooleanInput('logout')
}; };
} }

View file

@ -1,10 +1,9 @@
import * as aws from './aws';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as aws from './aws';
import * as execm from './exec';
import {Docker} from '@docker/actions-toolkit/lib/docker/docker'; export async function login(registry: string, username: string, password: string): Promise<void> {
if (await aws.isECR(registry)) {
export async function login(registry: string, username: string, password: string, ecr: string): Promise<void> {
if (/true/i.test(ecr) || (ecr == 'auto' && aws.isECR(registry))) {
await loginECR(registry, username, password); await loginECR(registry, username, password);
} else { } else {
await loginStandard(registry, username, password); await loginStandard(registry, username, password);
@ -12,27 +11,19 @@ export async function login(registry: string, username: string, password: string
} }
export async function logout(registry: string): Promise<void> { export async function logout(registry: string): Promise<void> {
await Docker.getExecOutput(['logout', registry], { await execm.exec('docker', ['logout', registry], false).then(res => {
ignoreReturnCode: true if (res.stderr != '' && !res.success) {
}).then(res => { core.warning(res.stderr);
if (res.stderr.length > 0 && res.exitCode != 0) {
core.warning(res.stderr.trim());
} }
}); });
} }
export async function loginStandard(registry: string, username: string, password: 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');
} }
if (!username) {
throw new Error('Username required');
}
if (!password) {
throw new Error('Password required');
}
const loginArgs: Array<string> = ['login', '--password-stdin']; let loginArgs: Array<string> = ['login', '--password-stdin'];
loginArgs.push('--username', username); loginArgs.push('--username', username);
loginArgs.push(registry); loginArgs.push(registry);
@ -41,32 +32,43 @@ export async function loginStandard(registry: string, username: string, password
} else { } else {
core.info(`Logging into Docker Hub...`); core.info(`Logging into Docker Hub...`);
} }
await Docker.getExecOutput(loginArgs, { await execm.exec('docker', loginArgs, true, password).then(res => {
ignoreReturnCode: true, if (res.stderr != '' && !res.success) {
silent: true, throw new Error(res.stderr);
input: Buffer.from(password)
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
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> { export async function loginECR(registry: string, username: string, password: string): Promise<void> {
core.info(`Retrieving registries data through AWS SDK...`); const cliPath = await aws.getCLI();
const regDatas = await aws.getRegistriesData(registry, username, password); const cliVersion = await aws.getCLIVersion();
for (const regData of regDatas) { const region = await aws.getRegion(registry);
core.info(`Logging into ${regData.registry}...`); const accountIDs = await aws.getAccountIDs(registry);
await Docker.getExecOutput(['login', '--password-stdin', '--username', regData.username, regData.registry], {
ignoreReturnCode: true, if (await aws.isPubECR(registry)) {
silent: true, core.info(`AWS Public ECR detected with ${region} region`);
input: Buffer.from(regData.password) } else {
}).then(res => { core.info(`AWS ECR detected with ${region} region`);
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
} }
process.env.AWS_ACCESS_KEY_ID = username || process.env.AWS_ACCESS_KEY_ID;
process.env.AWS_SECRET_ACCESS_KEY = password || process.env.AWS_SECRET_ACCESS_KEY;
core.info(`Retrieving docker login command through AWS CLI ${cliVersion} (${cliPath})...`);
const loginCmds = await aws.getDockerLoginCmds(cliVersion, registry, region, accountIDs);
core.info(`Logging into ${registry}...`);
loginCmds.forEach((loginCmd, index) => {
execm.exec(loginCmd, [], true).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
}
if (loginCmds.length > 1) {
core.info(`Login Succeeded! (${index}/${loginCmds.length})`);
} else {
core.info('Login Succeeded!'); core.info('Login Succeeded!');
}
});
}); });
} }
}

40
src/exec.ts Normal file
View file

@ -0,0 +1,40 @@
import * as actionsExec from '@actions/exec';
import {ExecOptions} from '@actions/exec';
export interface ExecResult {
success: boolean;
stdout: string;
stderr: string;
}
export const exec = async (
command: string,
args: string[] = [],
silent: boolean,
stdin?: string
): Promise<ExecResult> => {
let stdout: string = '';
let stderr: string = '';
const options: ExecOptions = {
silent: silent,
ignoreReturnCode: true,
input: Buffer.from(stdin || '')
};
options.listeners = {
stdout: (data: Buffer) => {
stdout += data.toString();
},
stderr: (data: Buffer) => {
stderr += data.toString();
}
};
const returnCode: number = await actionsExec.exec(command, args, options);
return {
success: returnCode === 0,
stdout: stdout.trim(),
stderr: stderr.trim()
};
};

View file

@ -1,21 +1,28 @@
import * as actionsToolkit from '@docker/actions-toolkit'; import * as core from '@actions/core';
import * as context from './context'; import * as context from './context';
import * as docker from './docker'; import * as docker from './docker';
import * as stateHelper from './state-helper'; import * as stateHelper from './state-helper';
export async function main(): Promise<void> { export async function run(): Promise<void> {
const input: context.Inputs = context.getInputs(); try {
stateHelper.setRegistry(input.registry); const {registry, username, password, logout} = context.getInputs();
stateHelper.setLogout(input.logout); stateHelper.setRegistry(registry);
await docker.login(input.registry, input.username, input.password, input.ecr); stateHelper.setLogout(logout);
await docker.login(registry, username, password);
} catch (error) {
core.setFailed(error.message);
}
} }
async function post(): Promise<void> { async function logout(): Promise<void> {
if (!stateHelper.logout) { if (!stateHelper.logout) {
return; return;
} }
await docker.logout(stateHelper.registry); await docker.logout(stateHelper.registry);
} }
actionsToolkit.run(main, post); if (!stateHelper.IsPost) {
run();
} else {
logout();
}

View file

@ -1,5 +1,6 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
export const IsPost = !!process.env['STATE_isPost'];
export const registry = process.env['STATE_registry'] || ''; export const registry = process.env['STATE_registry'] || '';
export const logout = /true/i.test(process.env['STATE_logout'] || ''); export const logout = /true/i.test(process.env['STATE_logout'] || '');
@ -7,6 +8,10 @@ export function setRegistry(registry: string) {
core.saveState('registry', registry); core.saveState('registry', registry);
} }
export function setLogout(logout: boolean) { export function setLogout(logout: string) {
core.saveState('logout', logout); core.saveState('logout', logout);
} }
if (!IsPost) {
core.saveState('isPost', 'true');
}

View file

@ -1,21 +1,18 @@
{ {
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true,
"target": "es6", "target": "es6",
"module": "commonjs", "module": "commonjs",
"strict": true, "lib": [
"es6",
"dom"
],
"newLine": "lf", "newLine": "lf",
"outDir": "./lib", "outDir": "./lib",
"rootDir": "./src", "rootDir": "./src",
"forceConsistentCasingInFileNames": true, "strict": true,
"noImplicitAny": false, "noImplicitAny": false,
"resolveJsonModule": true, "esModuleInterop": true,
"useUnknownInCatchVariables": false, "sourceMap": true
}, },
"exclude": [ "exclude": ["node_modules", "**/*.test.ts"]
"./__tests__/**/*",
"./lib/**/*",
"node_modules",
"jest.config.ts"
]
} }

11609
yarn.lock

File diff suppressed because it is too large Load diff