Compare commits

..

18 Commits

Author SHA1 Message Date
9379083e42 Merge pull request #299 from crazy-max/split-docs
Enhance documentation
2021-02-17 19:08:07 +01:00
a63b18dea2 Enhance documentation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-17 18:53:20 +01:00
af867d4937 Merge pull request #296 from crazy-max/secret-file
Allow to use secret file mount
2021-02-16 13:15:29 +01:00
33eec1587d Update action.yml
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-16 11:56:02 +01:00
3db4797dd2 Merge pull request #298 from crazy-max/virtual-env
Enhance virtual-env workflow
2021-02-15 20:48:57 +01:00
659fcba376 Enhance virtual-env workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-15 20:39:21 +01:00
080cadd33e Allow to use secret file mount
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-15 10:08:25 +01:00
dc4c1fca8b Merge pull request #297 from crazy-max/labels
Remove label workflow
2021-02-14 23:17:33 +01:00
b280b0485b Merge pull request #287 from docker/dependabot/npm_and_yarn/csv-parse-4.15.1
Bump csv-parse from 4.14.2 to 4.15.1
2021-02-14 23:13:24 +01:00
b87564a5cc Remove label workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-14 23:12:36 +01:00
d2bc6a5d16 Update generated content
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-14 22:41:16 +01:00
e5f26cdae4 Merge pull request #295 from crazy-max/update-buildx
Update buildx
2021-02-14 22:30:37 +01:00
616efcd405 Update buildx
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-14 22:28:22 +01:00
0539e1a717 Bump csv-parse from 4.14.2 to 4.15.1
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.14.2 to 4.15.1.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.14.2...v4.15.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-02 05:59:48 +00:00
636b4540ec Merge pull request #273 from crazy-max/fix-workflow
Fix workflow for auto-push impl
2021-01-15 19:36:02 -08:00
af932bfb2e Fix workflow for auto-push impl
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-01-15 19:21:19 +01:00
2db03de115 Merge pull request #272 from crazy-max/virtual-env
Add virtual-env workflow
2021-01-15 19:17:18 +01:00
4643aec7c4 Add virtual-env workflow
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-01-15 19:13:45 +01:00
26 changed files with 825 additions and 829 deletions

111
.github/labels.yml vendored
View File

@ -1,111 +0,0 @@
## more info https://github.com/crazy-max/ghaction-github-labeler
- # automerge
name: ":bell: automerge"
color: "8f4fbc"
description: ""
- # bot
name: ":robot: bot"
color: "69cde9"
description: ""
- # bug
name: ":bug: bug"
color: "b60205"
description: ""
- # dependencies
name: ":game_die: dependencies"
color: "0366d6"
description: ""
from_name: "dependencies"
- # documentation
name: ":memo: documentation"
color: "c5def5"
description: ""
- # duplicate
name: ":busts_in_silhouette: duplicate"
color: "cccccc"
description: ""
- # enhancement
name: ":sparkles: enhancement"
color: "0054ca"
description: ""
- # feature request
name: ":bulb: feature request"
color: "0e8a16"
description: ""
- # feedback
name: ":mega: feedback"
color: "03a9f4"
description: ""
- # future maybe
name: ":rocket: future maybe"
color: "fef2c0"
description: ""
- # good first issue
name: ":hatching_chick: good first issue"
color: "7057ff"
description: ""
- # help wanted
name: ":pray: help wanted"
color: "4caf50"
description: ""
- # hold
name: ":hand: hold"
color: "24292f"
description: ""
- # invalid
name: ":no_entry_sign: invalid"
color: "e6e6e6"
description: ""
- # maybe bug
name: ":interrobang: maybe bug"
color: "ff5722"
description: ""
- # needs more info
name: ":thinking: needs more info"
color: "795548"
description: ""
- # question
name: ":question: question"
color: "3f51b5"
description: ""
- # upstream
name: ":eyes: upstream"
color: "fbca04"
description: ""
- # wontfix
name: ":coffin: wontfix"
color: "ffffff"
description: ""
- # registry-aws-ecr
name: "registry-aws-ecr"
color: "84858A"
description: ""
- # registry-azure-acr
name: "registry-azure-acr"
color: "84858A"
description: ""
- # registry-dockerhub
name: "registry-dockerhub"
color: "84858A"
description: ""
- # registry-github
name: "registry-github"
color: "84858A"
description: ""
- # registry-google-gar
name: "registry-google-gar"
color: "84858A"
description: ""
- # registry-google-gcr
name: "registry-google-gcr"
color: "84858A"
description: ""
- # registry-nexus
name: "registry-nexus"
color: "84858A"
description: ""
- # registry-quay
name: "registry-quay"
color: "84858A"
description: ""

View File

@ -244,12 +244,6 @@ jobs:
docker-driver: docker-driver:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
push:
- true
- false
services: services:
registry: registry:
image: registry:2 image: registry:2
@ -262,24 +256,12 @@ jobs:
- -
name: Build name: Build
id: docker_build id: docker_build
continue-on-error: ${{ matrix.push }}
uses: ./ uses: ./
with: with:
context: ./test context: ./test
file: ./test/Dockerfile file: ./test/Dockerfile
push: ${{ matrix.push }} push: true
tags: localhost:5000/name/app:latest tags: localhost:5000/name/app:latest
-
name: Check
run: |
echo "${{ toJson(steps.docker_build) }}"
if [ "${{ matrix.push }}" = "false" ]; then
exit 0
fi
if [ "${{ steps.docker_build.outcome }}" != "failure" ] || [ "${{ steps.docker_build.conclusion }}" != "success" ]; then
echo "::error::Should have failed"
exit 1
fi
- -
name: Dump context name: Dump context
if: always() if: always()

View File

@ -1,20 +0,0 @@
name: labels
on:
push:
branches:
- 'master'
paths:
- '.github/labels.yml'
- '.github/workflows/labels.yml'
jobs:
labeler:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Run Labeler
uses: crazy-max/ghaction-github-labeler@v3

38
.github/workflows/virtual-env.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: virtual-env
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *' # everyday at 10am
jobs:
os:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- ubuntu-20.04
- ubuntu-18.04
- ubuntu-16.04
steps:
-
name: List install packages
run: apt list --installed
-
name: Docker info
run: docker info
-
name: Docker version
run: docker version
-
name: buildx version
run: docker buildx version
-
name: containerd version
run: containerd --version
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1

View File

@ -1,4 +1,4 @@
#syntax=docker/dockerfile:1.1-experimental #syntax=docker/dockerfile:1.2
FROM node:12 AS deps FROM node:12 AS deps
WORKDIR /src WORKDIR /src
@ -23,7 +23,7 @@ FROM deps AS test
COPY --from=docker /usr/local/bin/docker /usr/bin/ COPY --from=docker /usr/local/bin/docker /usr/bin/
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
ARG BUILDX_VERSION=v0.4.2 ARG BUILDX_VERSION=v0.5.1
ENV RUNNER_TEMP=/tmp/github_runner ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN mkdir -p /usr/local/lib/docker/cli-plugins && \ RUN mkdir -p /usr/local/lib/docker/cli-plugins && \

557
README.md
View File

@ -6,18 +6,18 @@
## Upgrade from v1 ## Upgrade from v1
`v2` of this action includes significant updates and now uses Docker [Buildx](https://github.com/docker/buildx). It `v2` of this action includes significant updates and now uses Docker [Buildx](https://github.com/docker/buildx). It's
works with 3 new actions ([login](https://github.com/docker/login-action), [setup-buildx](https://github.com/docker/setup-buildx-action) also rewritten as a [typescript-action](https://github.com/actions/typescript-action/) to be as close as possible
and [setup-qemu](https://github.com/docker/setup-qemu-action)) that we have created. It's also rewritten as a of the [GitHub Runner](https://github.com/actions/virtual-environments) during its execution.
[typescript-action](https://github.com/actions/typescript-action/) to be as close as possible of the
[GitHub Runner](https://github.com/actions/virtual-environments) during its execution.
[Upgrade notes](UPGRADE.md) and many [usage examples](#usage) have been added to handle most use cases but `v1` is [Upgrade notes](UPGRADE.md) with many [usage examples](#advanced-usage) have been added to handle most use cases but
still available through [`releases/v1` branch](https://github.com/docker/build-push-action/tree/releases/v1). `v1` is still available through [`releases/v1` branch](https://github.com/docker/build-push-action/tree/releases/v1).
## About ## About
GitHub Action to build and push Docker images with [Buildx](https://github.com/docker/buildx). GitHub Action to build and push Docker images with [Buildx](https://github.com/docker/buildx) with full support of the
features provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. This includes multi-platform
build, secrets, remote cache, etc. and different builder deployment/namespacing options.
> :bulb: See also: > :bulb: See also:
> * [login](https://github.com/docker/login-action) action > * [login](https://github.com/docker/login-action) action
@ -31,46 +31,58 @@ ___
* [Usage](#usage) * [Usage](#usage)
* [Git context](#git-context) * [Git context](#git-context)
* [Path context](#path-context) * [Path context](#path-context)
* [Isolated builders](#isolated-builders)
* [Multi-platform image](#multi-platform-image)
* [Advanced usage](#advanced-usage) * [Advanced usage](#advanced-usage)
* [Push to multi-registries](#push-to-multi-registries) * [Multi-platform image](docs/advanced/multi-platform.md)
* [Cache to registry](#cache-to-registry) * [Secrets](docs/advanced/secrets.md)
* [Local registry](#local-registry) * [Isolated builders](docs/advanced/isolated-builders.md)
* [Export image to Docker](#export-image-to-docker) * [Push to multi-registries](docs/advanced/push-multi-registries.md)
* [Leverage GitHub cache](#leverage-github-cache) * [Cache](docs/advanced/cache.md)
* [Handle tags and labels](#handle-tags-and-labels) * [Registry cache](docs/advanced/cache.md#registry-cache)
* [Update DockerHub repo description](#update-dockerhub-repo-description) * [GitHub cache](docs/advanced/cache.md#github-cache)
* [Local registry](docs/advanced/local-registry.md)
* [Export image to Docker](docs/advanced/export-docker.md)
* [Handle tags and labels](docs/advanced/tags-labels.md)
* [Update DockerHub repo description](docs/advanced/dockerhub-desc.md)
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [outputs](#outputs) * [outputs](#outputs)
* [Notes](#notes)
* [Multi-line secret value](#multi-line-secret-value)
* [Troubleshooting](#troubleshooting) * [Troubleshooting](#troubleshooting)
* [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot)
* [Limitation](#limitation) * [Limitation](#limitation)
## Usage ## Usage
This action uses our [setup-buildx](https://github.com/docker/setup-buildx-action) action that extends the By default, this action uses the [Git context](#git-context) so you don't need to use the
`docker build` command named [buildx](https://github.com/docker/buildx) with the full support of the features [`actions/checkout`](https://github.com/actions/checkout/) action to checkout the repository because this will be
provided by [Moby BuildKit](https://github.com/moby/buildkit) builder toolkit. This includes multi-arch build, done directly by buildkit. The git reference will be based on the [event that triggered your workflow](https://docs.github.com/en/actions/reference/events-that-trigger-workflows)
build-secrets, remote cache, etc. and different builder deployment/namespacing options. and will result in the following context: `https://github.com/<owner>/<repo>.git#<ref>`.
Be careful because **any file mutation in the steps that precede the build step will be ignored** since
the context is based on the git reference. However, you can use the [Path context](#path-context) using the
[`context` input](#inputs) alongside the [`actions/checkout`](https://github.com/actions/checkout/) action to remove
this restriction.
In the examples below we are using 3 other actions:
* [`setup-buildx`](https://github.com/docker/setup-buildx-action) action will create and boot a builder using by
default the `docker-container` [builder driver](https://github.com/docker/buildx#--driver-driver). This is
**not required but recommended** using it to be able to build multi-platform images, export cache, etc.
* [`setup-qemu`](https://github.com/docker/setup-qemu-action) action can be useful if you want
to add emulation support with QEMU to be able to build against more platforms.
* [`login`](https://github.com/docker/setup-qemu-action) action will take care to log in against a Docker registry.
### Git context ### Git context
The default behavior of this action is to use the Git context invoked by your workflow.
(eg. `https://github.com/<owner>/<repo>.git#<ref>`)
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: master branches:
- 'master'
jobs: jobs:
main: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
@ -92,17 +104,14 @@ jobs:
with: with:
push: true push: true
tags: user/app:latest tags: user/app:latest
build-args: |
arg1=value1
arg2=value2
- -
name: Image digest name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}
``` ```
Building from current repository automatically uses the [GitHub Token](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token) Building from the current repository automatically uses the [GitHub Token](https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token)
as provided by `secrets` so it does not need to be passed. But if you want to authenticate against another private so it does not need to be passed. If you want to authenticate against another private repository, you have to use
repository, you have to use a secret named `GIT_AUTH_TOKEN` to be able to authenticate against it with buildx: a [secret](docs/advanced/secrets.md) named `GIT_AUTH_TOKEN` to be able to authenticate against it with buildx:
```yaml ```yaml
- -
@ -117,23 +126,20 @@ repository, you have to use a secret named `GIT_AUTH_TOKEN` to be able to authen
``` ```
> :warning: Subdir for Git context is not yet supported ([moby/buildkit#1684](https://github.com/moby/buildkit/issues/1684)) > :warning: Subdir for Git context is not yet supported ([moby/buildkit#1684](https://github.com/moby/buildkit/issues/1684))
> but you can use the [path context](#path-context) in the meantime. > but you can use the [path context](#path-context) in the meantime. More info on [Docker docs website](https://docs.docker.com/engine/reference/commandline/build/#git-repositories).
> More info: https://docs.docker.com/engine/reference/commandline/build/#git-repositories
### Path context ### Path context
You can also use the `PATH` context alongside the [`actions/checkout`](https://github.com/actions/checkout/) action.
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: master branches:
- 'master'
jobs: jobs:
path-context: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
@ -156,435 +162,23 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: true push: true
tags: user/app:latest tags: user/app:latest
``` ```
### Isolated builders
```yaml
name: ci
on:
push:
branches: master
jobs:
multi-builders:
runs-on: ubuntu-latest
steps:
-
uses: docker/setup-buildx-action@v1
id: builder1
-
uses: docker/setup-buildx-action@v1
id: builder2
-
name: Builder 1 name
run: echo ${{ steps.builder1.outputs.name }}
-
name: Builder 2 name
run: echo ${{ steps.builder2.outputs.name }}
-
name: Build against builder1
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder1.outputs.name }}
target: mytarget1
-
name: Build against builder2
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder2.outputs.name }}
target: mytarget2
```
### Multi-platform image
```yaml
name: ci
on:
push:
branches: master
jobs:
multi:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
push: true
tags: |
user/app:latest
user/app:1.0.0
```
## Advanced usage ## Advanced usage
### Push to multi-registries * [Multi-platform image](docs/advanced/multi-platform.md)
* [Secrets](docs/advanced/secrets.md)
The following workflow will connect you to [DockerHub](https://github.com/docker/login-action#dockerhub) * [Isolated builders](docs/advanced/isolated-builders.md)
and [GitHub Container Registry](https://github.com/docker/login-action#github-container-registry) and push the * [Push to multi-registries](docs/advanced/push-multi-registries.md)
image to these registries. * [Cache](docs/advanced/cache.md)
* [Registry cache](docs/advanced/cache.md#registry-cache)
<details> * [GitHub cache](docs/advanced/cache.md#github-cache)
<summary><b>Show workflow</b></summary> * [Local registry](docs/advanced/local-registry.md)
* [Export image to Docker](docs/advanced/export-docker.md)
```yaml * [Handle tags and labels](docs/advanced/tags-labels.md)
name: ci * [Update DockerHub repo description](docs/advanced/dockerhub-desc.md)
on:
push:
branches: master
jobs:
multi-registries:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
push: true
tags: |
user/app:latest
user/app:1.0.0
ghcr.io/user/app:latest
ghcr.io/user/app:1.0.0
```
</details>
### Cache to registry
You can import/export cache from a cache manifest or (special) image configuration on the registry.
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
registry-cache:
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
cache-from: type=registry,ref=user/app:latest
cache-to: type=inline
```
</details>
### Local registry
For testing purposes you may need to create a [local registry](https://hub.docker.com/_/registry) to push images into:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
local-registry:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: network=host
-
name: Build and push to local registry
uses: docker/build-push-action@v2
with:
push: true
tags: localhost:5000/name/app:latest
-
name: Inspect
run: |
docker buildx imagetools inspect localhost:5000/name/app:latest
```
</details>
### Export image to Docker
You may want your build result to be available in the Docker client through `docker images` to be able to use it
in another step of your workflow:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
export-docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
load: true
tags: myimage:latest
-
name: Inspect
run: |
docker image inspect myimage:latest
```
</details>
### Leverage GitHub cache
You can leverage [GitHub cache](https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows)
using [actions/cache](https://github.com/actions/cache) with this action:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
github-cache:
runs-on: ubuntu-latest
steps:
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
```
</details>
> If you want to [export layers for all stages](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue),
> you have to specify `mode=max` attribute in `cache-to`.
### Handle tags and labels
If you come from [`v1`](https://github.com/docker/build-push-action/tree/releases/v1#readme) and want an
"automatic" tag management and [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md)
for labels, you can do it in a dedicated step. The following workflow will use the [Docker meta action](https://github.com/crazy-max/ghaction-docker-meta)
to handle tags and labels based on GitHub actions events and Git metadata.
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push:
branches:
- '**'
tags:
- 'v*.*.*'
pull_request:
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: name/app # list of Docker images to use as base name for tags
tag-sha: true # add git short SHA as Docker tag
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
```
</details>
### Update DockerHub repo description
You can update the [DockerHub repository description](https://docs.docker.com/docker-hub/repos/) using
a third-party action called [DockerHub Description](https://github.com/peter-evans/dockerhub-description)
with this action:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
main:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
-
name: Update repo description
uses: peter-evans/dockerhub-description@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: user/app
```
</details>
## Customizing ## Customizing
@ -622,7 +216,8 @@ Following inputs can be used as `step.with` keys
| `outputs` | List | List of [output destinations](https://github.com/docker/buildx#-o---outputpath-typetypekeyvalue) (format: `type=local,dest=path`) | | `outputs` | List | List of [output destinations](https://github.com/docker/buildx#-o---outputpath-typetypekeyvalue) (format: `type=local,dest=path`) |
| `cache-from` | List | List of [external cache sources](https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue) (eg. `type=local,src=path/to/dir`) | | `cache-from` | List | List of [external cache sources](https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue) (eg. `type=local,src=path/to/dir`) |
| `cache-to` | List | List of [cache export destinations](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue) (eg. `type=local,dest=path/to/dir`) | | `cache-to` | List | List of [cache export destinations](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue) (eg. `type=local,dest=path/to/dir`) |
| `secrets` | List | List of secrets to expose to the build (eg. `key=value`, `GIT_AUTH_TOKEN=mytoken`) | | `secrets` | List | List of secrets to expose to the build (eg. `key=string`, `GIT_AUTH_TOKEN=mytoken`) |
| `secret-files` | List | List of secret files to expose to the build (eg. `key=filename`, `MY_SECRET=./secret.txt`) |
| `ssh` | List | List of SSH agent socket or keys to expose to the build | | `ssh` | List | List of SSH agent socket or keys to expose to the build |
### outputs ### outputs
@ -633,38 +228,6 @@ Following outputs are available
|---------------|---------|---------------------------------------| |---------------|---------|---------------------------------------|
| `digest` | String | Image content-addressable identifier also called a digest | | `digest` | String | Image content-addressable identifier also called a digest |
## Notes
### Multi-line secret value
To handle multi-line value for a secret, you will need to place the key-value pair between quotes:
```yaml
secrets: |
"MYSECRET=${{ secrets.GPG_KEY }}"
GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"
"JSON_SECRET={""key1"":""value1"",""key2"":""value2""}"
```
| Key | Value |
|--------------------|--------------------------------------------------|
| `MYSECRET` | `***********************` |
| `GIT_AUTH_TOKEN` | `abcdefghi,jklmno=0123456789` |
| `MYSECRET` | `aaaaaaaa\nbbbbbbb\nccccccccc` |
| `FOO` | `bar` |
| `EMPTYLINE` | `aaaa\n\nbbbb\nccc` |
| `JSON_SECRET` | `{"key1":"value1","key2":"value2"}` |
> Note: all quote signs need to be doubled for escaping.
## Troubleshooting ## Troubleshooting
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) See [TROUBLESHOOTING.md](TROUBLESHOOTING.md)

View File

@ -1,113 +1,7 @@
# Troubleshooting # Troubleshooting
* [`auto-push is currently not implemented for docker driver`](#auto-push-is-currently-not-implemented-for-docker-driver)
* [Cannot push to a registry](#cannot-push-to-a-registry) * [Cannot push to a registry](#cannot-push-to-a-registry)
## `auto-push is currently not implemented for docker driver`
If you're using the default builder (which uses the docker driver) without using our `setup-buildx-action`, you may
encounter this error message if you try to push your image:
```
Run docker/build-push-action@v2
📣 Buildx version: 0.4.2
🏃 Starting build...
/usr/bin/docker buildx build --tag localhost:5000/name/app:latest --iidfile /tmp/docker-build-push-eYl8PB/iidfile --file ./test/Dockerfile --push ./test
auto-push is currently not implemented for docker driver
Error: buildx call failed with: auto-push is currently not implemented for docker driver
```
While waiting for an implementation to be done on buildx/buildkit, you have the following possibilities
to solve this atm:
### With `docker-container` driver and `setup-buildx`
> Recommended solution
```yaml
jobs:
build:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USER }}
password: ${{ secrets.PASSWORD }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ env.REGISTRY }}/myapp:latest
push: true
```
### With `docker` driver
```yaml
jobs:
build:
-
name: Checkout
uses: actions/checkout@v2
-
name: Login
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USER }}
password: ${{ secrets.PASSWORD }}
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ env.REGISTRY }}/myapp:latest
load: true
-
name: Push
run: docker push ${{ env.REGISTRY }}/myapp:latest
```
### With `docker` driver and `setup-buildx`
```yaml
jobs:
build:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver: docker
-
name: Login
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ env.USER }}
password: ${{ secrets.PASSWORD }}
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
tags: ${{ env.REGISTRY }}/myapp:latest
load: true
-
name: Push
run: docker push ${{ env.REGISTRY }}/myapp:latest
```
## Cannot push to a registry ## Cannot push to a registry
While pushing to a registry, you may encounter these kinds of issues: While pushing to a registry, you may encounter these kinds of issues:
@ -165,8 +59,7 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile platforms: linux/amd64,linux/arm64
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
tags: docker.io/user/app:latest tags: docker.io/user/app:latest
outputs: type=oci,dest=/tmp/image.tar outputs: type=oci,dest=/tmp/image.tar
- -

View File

@ -63,7 +63,6 @@ steps:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
pull: true pull: true
push: true push: true
build-args: | build-args: |
@ -136,7 +135,6 @@ steps:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }} tags: ${{ steps.prep.outputs.tags }}
labels: | labels: |
@ -145,5 +143,5 @@ steps:
org.opencontainers.image.revision=${{ github.sha }} org.opencontainers.image.revision=${{ github.sha }}
``` ```
> You can also use the [Docker meta action](https://github.com/crazy-max/ghaction-docker-meta) to handle tags and > You can also use the [Docker meta action to handle tags and labels](docs/advanced/tags-labels.md) based on GitHub
> labels based on GitHub actions events and Git metadata. A workflow example is available in the [README](README.md#handle-tags-and-labels). > actions events and Git metadata.

View File

@ -119,21 +119,34 @@ describe('parseVersion', () => {
describe('getSecret', () => { describe('getSecret', () => {
test.each([ test.each([
['A_SECRET=abcdef0123456789', 'A_SECRET', 'abcdef0123456789', false], ['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', false],
['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', false], ['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', false],
['MY_KEY=c3RyaW5nLXdpdGgtZXF1YWxzCg==', 'MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg==', false], ['MY_KEY=c3RyaW5nLXdpdGgtZXF1YWxzCg==', false, 'MY_KEY', 'c3RyaW5nLXdpdGgtZXF1YWxzCg==', false],
['aaaaaaaa', '', '', true], ['aaaaaaaa', false, '', '', true],
['aaaaaaaa=', '', '', true], ['aaaaaaaa=', false, '', '', true],
['=bbbbbbb', '', '', true] ['=bbbbbbb', false, '', '', true],
])('given %p key and %p secret', async (kvp, key, secret, invalid) => { [
`foo=${path.join(__dirname, 'fixtures', 'secret.txt').split(path.sep).join(path.posix.sep)}`,
true,
'foo',
'bar',
false
],
[`notfound=secret`, true, '', '', true]
])('given %p key and %p secret', async (kvp, file, exKey, exValue, invalid) => {
try { try {
const secretArgs = await buildx.getSecret(kvp); let secret: string;
if (file) {
secret = await buildx.getSecretFile(kvp);
} else {
secret = await buildx.getSecretString(kvp);
}
expect(true).toBe(!invalid); expect(true).toBe(!invalid);
console.log(`secretArgs: ${secretArgs}`); console.log(`secret: ${secret}`);
expect(secretArgs).toEqual(`id=${key},src=${tmpNameSync}`); expect(secret).toEqual(`id=${exKey},src=${tmpNameSync}`);
const secretContent = await fs.readFileSync(tmpNameSync, 'utf-8'); const secretValue = await fs.readFileSync(tmpNameSync, 'utf-8');
console.log(`secretValue: ${secretContent}`); console.log(`secretValue: ${secretValue}`);
expect(secretContent).toEqual(secret); expect(secretValue).toEqual(exValue);
} catch (err) { } catch (err) {
expect(true).toBe(invalid); expect(true).toBe(invalid);
} }

View File

@ -337,6 +337,27 @@ ccc`],
'--push', '--push',
'https://github.com/docker/build-push-action.git#heads/master' 'https://github.com/docker/build-push-action.git#heads/master'
] ]
],
[
'0.5.1',
new Map<string, string>([
['context', 'https://github.com/docker/build-push-action.git#heads/master'],
['tag', 'localhost:5000/name/app:latest'],
['secret-files', `MY_SECRET=${path.join(__dirname, 'fixtures', 'secret.txt').split(path.sep).join(path.posix.sep)}`],
['file', './test/Dockerfile'],
['builder', 'builder-git-context-2'],
['push', 'true']
]),
[
'buildx',
'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=MY_SECRET,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', './test/Dockerfile',
'--builder', 'builder-git-context-2',
'--push',
'https://github.com/docker/build-push-action.git#heads/master'
]
] ]
])( ])(
'given %p with %p as inputs, returns %p', 'given %p with %p as inputs, returns %p',

View File

@ -0,0 +1 @@
bar

View File

@ -60,15 +60,18 @@ inputs:
description: "List of cache export destinations for buildx (eg. user/app:cache, type=local,dest=path/to/dir)" description: "List of cache export destinations for buildx (eg. user/app:cache, type=local,dest=path/to/dir)"
required: false required: false
secrets: secrets:
description: "List of secrets to expose to the build (eg. key=value, GIT_AUTH_TOKEN=mytoken)" description: "List of secrets to expose to the build (eg. key=string, GIT_AUTH_TOKEN=mytoken)"
required: false
secret-files:
description: "List of secret files to expose to the build (eg. key=filename, MY_SECRET=./secret.txt)"
required: false
ssh:
description: "List of SSH agent socket or keys to expose to the build"
required: false required: false
github-token: github-token:
description: "GitHub Token used to authenticate against a repository for Git context" description: "GitHub Token used to authenticate against a repository for Git context"
default: ${{ github.token }} default: ${{ github.token }}
required: false required: false
ssh:
description: "List of SSH agent socket or keys to expose to the build"
required: false
outputs: outputs:
digest: digest:

138
dist/index.js generated vendored
View File

@ -4581,7 +4581,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getImageID = exports.getImageIDFile = void 0; exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getSecretFile = exports.getSecretString = exports.getImageID = exports.getImageIDFile = void 0;
const sync_1 = __importDefault(__webpack_require__(750)); const sync_1 = __importDefault(__webpack_require__(750));
const fs_1 = __importDefault(__webpack_require__(747)); const fs_1 = __importDefault(__webpack_require__(747));
const path_1 = __importDefault(__webpack_require__(622)); const path_1 = __importDefault(__webpack_require__(622));
@ -4604,18 +4604,36 @@ function getImageID() {
}); });
} }
exports.getImageID = getImageID; exports.getImageID = getImageID;
function getSecret(kvp) { function getSecretString(kvp) {
return __awaiter(this, void 0, void 0, function* () {
return getSecret(kvp, false);
});
}
exports.getSecretString = getSecretString;
function getSecretFile(kvp) {
return __awaiter(this, void 0, void 0, function* () {
return getSecret(kvp, true);
});
}
exports.getSecretFile = getSecretFile;
function getSecret(kvp, file) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const delimiterIndex = kvp.indexOf('='); const delimiterIndex = kvp.indexOf('=');
const key = kvp.substring(0, delimiterIndex); const key = kvp.substring(0, delimiterIndex);
const value = kvp.substring(delimiterIndex + 1); let value = kvp.substring(delimiterIndex + 1);
if (key.length == 0 || value.length == 0) { if (key.length == 0 || value.length == 0) {
throw new Error(`${kvp} is not a valid secret`); throw new Error(`${kvp} is not a valid secret`);
} }
if (file) {
if (!fs_1.default.existsSync(value)) {
throw new Error(`secret file ${value} not found`);
}
value = fs_1.default.readFileSync(value, { encoding: 'utf-8' });
}
const secretFile = context.tmpNameSync({ const secretFile = context.tmpNameSync({
tmpdir: context.tmpDir() tmpdir: context.tmpDir()
}); });
yield fs_1.default.writeFileSync(secretFile, value); fs_1.default.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`; return `id=${key},src=${secretFile}`;
}); });
} }
@ -11668,10 +11686,14 @@ additional information.
const { Transform } = __webpack_require__(413) const { Transform } = __webpack_require__(413)
const ResizeableBuffer = __webpack_require__(942) const ResizeableBuffer = __webpack_require__(942)
// white space characters
// https://en.wikipedia.org/wiki/Whitespace_character
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes#Types
// \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff
const tab = 9 const tab = 9
const nl = 10 const nl = 10 // \n, 0x0A in hexadecimal, 10 in decimal
const np = 12 const np = 12
const cr = 13 const cr = 13 // \r, 0x0D in hexadécimal, 13 in decimal
const space = 32 const space = 32
const boms = { const boms = {
// Note, the following are equals: // Note, the following are equals:
@ -11860,6 +11882,27 @@ class Parser extends Transform {
throw new Error(`Invalid Option: from_line must be an integer, got ${JSON.stringify(opts.from_line)}`) throw new Error(`Invalid Option: from_line must be an integer, got ${JSON.stringify(opts.from_line)}`)
} }
} }
// Normalize options `ignore_last_delimiters`
if(options.ignore_last_delimiters === undefined || options.ignore_last_delimiters === null){
options.ignore_last_delimiters = false
}else if(typeof options.ignore_last_delimiters === 'number'){
options.ignore_last_delimiters = Math.floor(options.ignore_last_delimiters)
if(options.ignore_last_delimiters === 0){
options.ignore_last_delimiters = false
}
}else if(typeof options.ignore_last_delimiters !== 'boolean'){
throw new CsvError('CSV_INVALID_OPTION_IGNORE_LAST_DELIMITERS', [
'Invalid option `ignore_last_delimiters`:',
'the value must be a boolean value or an integer,',
`got ${JSON.stringify(options.ignore_last_delimiters)}`
], options)
}
if(options.ignore_last_delimiters === true && options.columns === false){
throw new CsvError('CSV_IGNORE_LAST_DELIMITERS_REQUIRES_COLUMNS', [
'The option `ignore_last_delimiters`',
'requires the activation of the `columns` option'
], options)
}
// Normalize option `info` // Normalize option `info`
if(options.info === undefined || options.info === null || options.info === false){ if(options.info === undefined || options.info === null || options.info === false){
options.info = false options.info = false
@ -12177,7 +12220,7 @@ class Parser extends Transform {
} }
// Auto discovery of record_delimiter, unix, mac and windows supported // Auto discovery of record_delimiter, unix, mac and windows supported
if(this.state.quoting === false && record_delimiter.length === 0){ if(this.state.quoting === false && record_delimiter.length === 0){
const record_delimiterCount = this.__autoDiscoverRowDelimiter(buf, pos) const record_delimiterCount = this.__autoDiscoverRecordDelimiter(buf, pos)
if(record_delimiterCount){ if(record_delimiterCount){
record_delimiter = this.options.record_delimiter record_delimiter = this.options.record_delimiter
} }
@ -12218,12 +12261,12 @@ class Parser extends Transform {
const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr) const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr)
const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+quote.length, nextChr) const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+quote.length, nextChr)
const isNextChrDelimiter = this.__isDelimiter(buf, pos+quote.length, nextChr) const isNextChrDelimiter = this.__isDelimiter(buf, pos+quote.length, nextChr)
const isNextChrRowDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRowDelimiter(buf, pos+quote.length) : this.__isRecordDelimiter(nextChr, buf, pos+quote.length) const isNextChrRecordDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRecordDelimiter(buf, pos+quote.length) : this.__isRecordDelimiter(nextChr, buf, pos+quote.length)
// Escape a quote // Escape a quote
// Treat next char as a regular character // Treat next char as a regular character
if(escape !== null && this.__isEscape(buf, pos, chr) && this.__isQuote(buf, pos + escape.length)){ if(escape !== null && this.__isEscape(buf, pos, chr) && this.__isQuote(buf, pos + escape.length)){
pos += escape.length - 1 pos += escape.length - 1
}else if(!nextChr || isNextChrDelimiter || isNextChrRowDelimiter || isNextChrComment || isNextChrTrimable){ }else if(!nextChr || isNextChrDelimiter || isNextChrRecordDelimiter || isNextChrComment || isNextChrTrimable){
this.state.quoting = false this.state.quoting = false
this.state.wasQuoting = true this.state.wasQuoting = true
pos += quote.length - 1 pos += quote.length - 1
@ -12234,7 +12277,7 @@ class Parser extends Transform {
'Invalid Closing Quote:', 'Invalid Closing Quote:',
`got "${String.fromCharCode(nextChr)}"`, `got "${String.fromCharCode(nextChr)}"`,
`at line ${this.info.lines}`, `at line ${this.info.lines}`,
'instead of delimiter, row delimiter, trimable character', 'instead of delimiter, record delimiter, trimable character',
'(if activated) or comment', '(if activated) or comment',
], this.options, this.__context()) ], this.options, this.__context())
) )
@ -12275,25 +12318,24 @@ class Parser extends Transform {
this.info.comment_lines++ this.info.comment_lines++
// Skip full comment line // Skip full comment line
}else{ }else{
// Activate records emition if above from_line
if(this.state.enabled === false && this.info.lines + (this.state.wasRowDelimiter === true ? 1: 0) >= from_line){
this.state.enabled = true
this.__resetField()
this.__resetRecord()
pos += recordDelimiterLength - 1
continue
}
// Skip if line is empty and skip_empty_lines activated // Skip if line is empty and skip_empty_lines activated
if(skip_empty_lines === true && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0){ if(skip_empty_lines === true && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0){
this.info.empty_lines++ this.info.empty_lines++
pos += recordDelimiterLength - 1 pos += recordDelimiterLength - 1
continue continue
} }
// Activate records emition if above from_line const errField = this.__onField()
if(this.state.enabled === false && this.info.lines + (this.state.wasRowDelimiter === true ? 1: 0 ) >= from_line){ if(errField !== undefined) return errField
this.state.enabled = true const errRecord = this.__onRecord()
this.__resetField() if(errRecord !== undefined) return errRecord
this.__resetRow()
pos += recordDelimiterLength - 1
continue
}else{
const errField = this.__onField()
if(errField !== undefined) return errField
const errRecord = this.__onRow()
if(errRecord !== undefined) return errRecord
}
if(to !== -1 && this.info.records >= to){ if(to !== -1 && this.info.records >= to){
this.state.stop = true this.state.stop = true
this.push(null) this.push(null)
@ -12366,7 +12408,7 @@ class Parser extends Transform {
if(this.state.wasQuoting === true || this.state.record.length !== 0 || this.state.field.length !== 0){ if(this.state.wasQuoting === true || this.state.record.length !== 0 || this.state.field.length !== 0){
const errField = this.__onField() const errField = this.__onField()
if(errField !== undefined) return errField if(errField !== undefined) return errField
const errRecord = this.__onRow() const errRecord = this.__onRecord()
if(errRecord !== undefined) return errRecord if(errRecord !== undefined) return errRecord
}else if(this.state.wasRowDelimiter === true){ }else if(this.state.wasRowDelimiter === true){
this.info.empty_lines++ this.info.empty_lines++
@ -12382,21 +12424,17 @@ class Parser extends Transform {
this.state.wasRowDelimiter = false this.state.wasRowDelimiter = false
} }
} }
// Helper to test if a character is a space or a line delimiter __onRecord(){
__isCharTrimable(chr){
return chr === space || chr === tab || chr === cr || chr === nl || chr === np
}
__onRow(){
const {columns, columns_duplicates_to_array, encoding, info, from, relax_column_count, relax_column_count_less, relax_column_count_more, raw, skip_lines_with_empty_values} = this.options const {columns, columns_duplicates_to_array, encoding, info, from, relax_column_count, relax_column_count_less, relax_column_count_more, raw, skip_lines_with_empty_values} = this.options
const {enabled, record} = this.state const {enabled, record} = this.state
if(enabled === false){ if(enabled === false){
return this.__resetRow() return this.__resetRecord()
} }
// Convert the first line into column names // Convert the first line into column names
const recordLength = record.length const recordLength = record.length
if(columns === true){ if(columns === true){
if(isRecordEmpty(record)){ if(isRecordEmpty(record)){
this.__resetRow() this.__resetRecord()
return return
} }
return this.__firstLineToColumns(record) return this.__firstLineToColumns(record)
@ -12438,12 +12476,12 @@ class Parser extends Transform {
} }
if(skip_lines_with_empty_values === true){ if(skip_lines_with_empty_values === true){
if(isRecordEmpty(record)){ if(isRecordEmpty(record)){
this.__resetRow() this.__resetRecord()
return return
} }
} }
if(this.state.recordHasError === true){ if(this.state.recordHasError === true){
this.__resetRow() this.__resetRecord()
this.state.recordHasError = false this.state.recordHasError = false
return return
} }
@ -12517,7 +12555,7 @@ class Parser extends Transform {
} }
} }
} }
this.__resetRow() this.__resetRecord()
} }
__firstLineToColumns(record){ __firstLineToColumns(record){
const {firstLineToHeaders} = this.state const {firstLineToHeaders} = this.state
@ -12537,13 +12575,13 @@ class Parser extends Transform {
const normalizedHeaders = normalizeColumnsArray(headers) const normalizedHeaders = normalizeColumnsArray(headers)
this.state.expectedRecordLength = normalizedHeaders.length this.state.expectedRecordLength = normalizedHeaders.length
this.options.columns = normalizedHeaders this.options.columns = normalizedHeaders
this.__resetRow() this.__resetRecord()
return return
}catch(err){ }catch(err){
return err return err
} }
} }
__resetRow(){ __resetRecord(){
if(this.options.raw === true){ if(this.options.raw === true){
this.state.rawBuffer.reset() this.state.rawBuffer.reset()
} }
@ -12616,6 +12654,10 @@ class Parser extends Transform {
} }
return [undefined, field] return [undefined, field]
} }
// Helper to test if a character is a space or a line delimiter
__isCharTrimable(chr){
return chr === space || chr === tab || chr === cr || chr === nl || chr === np
}
// Keep it in case we implement the `cast_int` option // Keep it in case we implement the `cast_int` option
// __isInt(value){ // __isInt(value){
// // return Number.isInteger(parseInt(value)) // // return Number.isInteger(parseInt(value))
@ -12642,14 +12684,19 @@ class Parser extends Transform {
needMoreDataSize, needMoreDataSize,
// Skip if the remaining buffer smaller than record delimiter // Skip if the remaining buffer smaller than record delimiter
recordDelimiterMaxLength, recordDelimiterMaxLength,
// Skip if the remaining buffer can be row delimiter following the closing quote // Skip if the remaining buffer can be record delimiter following the closing quote
// 1 is for quote.length // 1 is for quote.length
quoting ? (quote.length + recordDelimiterMaxLength) : 0, quoting ? (quote.length + recordDelimiterMaxLength) : 0,
) )
return numOfCharLeft < requiredLength return numOfCharLeft < requiredLength
} }
__isDelimiter(buf, pos, chr){ __isDelimiter(buf, pos, chr){
const {delimiter} = this.options const {delimiter, ignore_last_delimiters} = this.options
if(ignore_last_delimiters === true && this.state.record.length === this.options.columns.length - 1){
return 0
}else if(ignore_last_delimiters !== false && typeof ignore_last_delimiters === 'number' && this.state.record.length === ignore_last_delimiters - 1){
return 0
}
loop1: for(let i = 0; i < delimiter.length; i++){ loop1: for(let i = 0; i < delimiter.length; i++){
const del = delimiter[i] const del = delimiter[i]
if(del[0] === chr){ if(del[0] === chr){
@ -12704,7 +12751,7 @@ class Parser extends Transform {
} }
return true return true
} }
__autoDiscoverRowDelimiter(buf, pos){ __autoDiscoverRecordDelimiter(buf, pos){
const {encoding} = this.options const {encoding} = this.options
const chr = buf[pos] const chr = buf[pos]
if(chr === cr){ if(chr === cr){
@ -13003,6 +13050,7 @@ function getInputs(defaultContext) {
cacheFrom: yield getInputList('cache-from', true), cacheFrom: yield getInputList('cache-from', true),
cacheTo: yield getInputList('cache-to', true), cacheTo: yield getInputList('cache-to', true),
secrets: yield getInputList('secrets', true), secrets: yield getInputList('secrets', true),
secretFiles: yield getInputList('secret-files', true),
githubToken: core.getInput('github-token'), githubToken: core.getInput('github-token'),
ssh: yield getInputList('ssh') ssh: yield getInputList('ssh')
}; };
@ -13055,14 +13103,22 @@ function getBuildArgs(inputs, defaultContext, buildxVersion) {
})); }));
yield exports.asyncForEach(inputs.secrets, (secret) => __awaiter(this, void 0, void 0, function* () { yield exports.asyncForEach(inputs.secrets, (secret) => __awaiter(this, void 0, void 0, function* () {
try { try {
args.push('--secret', yield buildx.getSecret(secret)); args.push('--secret', yield buildx.getSecretString(secret));
}
catch (err) {
core.warning(err.message);
}
}));
yield exports.asyncForEach(inputs.secretFiles, (secretFile) => __awaiter(this, void 0, void 0, function* () {
try {
args.push('--secret', yield buildx.getSecretFile(secretFile));
} }
catch (err) { catch (err) {
core.warning(err.message); core.warning(err.message);
} }
})); }));
if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) { if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) {
args.push('--secret', yield buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`)); args.push('--secret', yield buildx.getSecretString(`GIT_AUTH_TOKEN=${inputs.githubToken}`));
} }
yield exports.asyncForEach(inputs.ssh, (ssh) => __awaiter(this, void 0, void 0, function* () { yield exports.asyncForEach(inputs.ssh, (ssh) => __awaiter(this, void 0, void 0, function* () {
args.push('--ssh', ssh); args.push('--ssh', ssh);

107
docs/advanced/cache.md Normal file
View File

@ -0,0 +1,107 @@
# Cache
* [Registry cache](#registry-cache)
* [GitHub cache](#github-cache)
> More info about buildx cache: https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue
## Registry cache
You can import/export cache from a cache manifest or (special) image configuration on the registry.
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: user/app:latest
cache-from: type=registry,ref=user/app:latest
cache-to: type=inline
```
## GitHub cache
> :warning: At the moment caches are copied over the existing cache so it [keeps growing](https://github.com/docker/build-push-action/issues/252).
> The `Move cache` step is used as a temporary fix (see https://github.com/moby/buildkit/issues/1896).
> :rocket: There is a new cache backend using GitHub cache being developed that will lighten your workflow.
> More info: https://github.com/docker/buildx/pull/535
You can leverage [GitHub cache](https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows)
using [actions/cache](https://github.com/actions/cache) with this action:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: user/app:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
-
# Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
```

View File

@ -0,0 +1,48 @@
# Update DockerHub repo description
You can update the [DockerHub repository description](https://docs.docker.com/docker-hub/repos/) using
a third party action called [DockerHub Description](https://github.com/peter-evans/dockerhub-description)
with this action:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: user/app:latest
-
name: Update repo description
uses: peter-evans/dockerhub-description@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: user/app
```

View File

@ -0,0 +1,35 @@
# Export image to Docker
You may want your build result to be available in the Docker client through `docker images` to be able to use it
in another step of your workflow:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
load: true
tags: myimage:latest
-
name: Inspect
run: |
docker image inspect myimage:latest
```

View File

@ -0,0 +1,44 @@
# Isolated builders
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
uses: docker/setup-buildx-action@v1
id: builder1
-
uses: docker/setup-buildx-action@v1
id: builder2
-
name: Builder 1 name
run: echo ${{ steps.builder1.outputs.name }}
-
name: Builder 2 name
run: echo ${{ steps.builder2.outputs.name }}
-
name: Build against builder1
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder1.outputs.name }}
context: .
target: mytarget1
-
name: Build against builder2
uses: docker/build-push-action@v2
with:
builder: ${{ steps.builder2.outputs.name }}
context: .
target: mytarget2
```

View File

@ -0,0 +1,44 @@
# Local registry
For testing purposes you may need to create a [local registry](https://hub.docker.com/_/registry) to push images into:
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
driver-opts: network=host
-
name: Build and push to local registry
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: localhost:5000/name/app:latest
-
name: Inspect
run: |
docker buildx imagetools inspect localhost:5000/name/app:latest
```

View File

@ -0,0 +1,44 @@
# Multi-platform image
You can build multi-platform images using the [`platforms` input](../../README.md#inputs) as described below.
> :bulb: List of available platforms will be displayed and available through our [setup-buildx](https://github.com/docker/setup-buildx-action#about) action.
> :bulb: If you want support for more platforms, you can use QEMU with our [setup-qemu](https://github.com/docker/setup-qemu-action) action.
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: user/app:latest
```

View File

@ -0,0 +1,57 @@
# Push to multi-registries
* [Docker Hub and GHCR](#docker-hub-and-ghcr)
## Docker Hub and GHCR
The following workflow will connect you to [DockerHub](https://github.com/docker/login-action#dockerhub)
and [GitHub Container Registry](https://github.com/docker/login-action#github-container-registry) and push the
image to these registries.
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: |
user/app:latest
user/app:1.0.0
ghcr.io/user/app:latest
ghcr.io/user/app:1.0.0
```

84
docs/advanced/secrets.md Normal file
View File

@ -0,0 +1,84 @@
# Secrets
In the following example we will expose and use the [GITHUB_TOKEN secret](https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret)
as provided by GitHub in your workflow.
First let's create our `Dockerfile` to use our secret:
```Dockerfile
#syntax=docker/dockerfile:1.2
FROM alpine
RUN --mount=type=secret,id=github_token \
cat /run/secrets/github_token
```
As you can see we have named our secret `github_token`. Here is the workflow you can use to expose this secret using
the [`secrets` input](../../README.md#inputs):
```yaml
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
tags: user/app:latest
secrets: |
"github_token=${{ secrets.GITHUB_TOKEN }}"
```
> :bulb: You can also expose a secret file to the build with [`secret-files`](../../README.md#inputs) input:
> ```yaml
> secret-files: |
> "MY_SECRET=./secret.txt"
> ```
If you're using [GitHub secrets](https://docs.github.com/en/actions/reference/encrypted-secrets) and need to handle
multi-line value, you will need to place the key-value pair between quotes:
```yaml
secrets: |
"MYSECRET=${{ secrets.GPG_KEY }}"
GIT_AUTH_TOKEN=abcdefghi,jklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"
"JSON_SECRET={""key1"":""value1"",""key2"":""value2""}"
```
| Key | Value |
|--------------------|--------------------------------------------------|
| `MYSECRET` | `***********************` |
| `GIT_AUTH_TOKEN` | `abcdefghi,jklmno=0123456789` |
| `MYSECRET` | `aaaaaaaa\nbbbbbbb\nccccccccc` |
| `FOO` | `bar` |
| `EMPTYLINE` | `aaaa\n\nbbbb\nccc` |
| `JSON_SECRET` | `{"key1":"value1","key2":"value2"}` |
> :bulb: All quote signs need to be doubled for escaping.

View File

@ -0,0 +1,70 @@
# Handle tags and labels
If you come from [`v1`](https://github.com/docker/build-push-action/tree/releases/v1#readme) and want an
"automatic" tag management and [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md)
for labels, you can do it in a dedicated step. The following workflow will use the [Docker meta action](https://github.com/crazy-max/ghaction-docker-meta)
to handle tags and labels based on GitHub actions events and Git metadata.
```yaml
name: ci
on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push:
branches:
- '**'
tags:
- 'v*.*.*'
pull_request:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
# list of Docker images to use as base name for tags
images: |
name/app
ghcr.io/username/app
# add git short SHA as Docker tag
tag-sha: true
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ secrets.GHCR_USERNAME }}
password: ${{ secrets.GHCR_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}
```

View File

@ -31,7 +31,7 @@
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4", "@actions/exec": "^1.0.4",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"csv-parse": "^4.14.2", "csv-parse": "^4.15.1",
"semver": "^7.3.4", "semver": "^7.3.4",
"tmp": "^0.2.1" "tmp": "^0.2.1"
}, },

View File

@ -18,17 +18,34 @@ export async function getImageID(): Promise<string | undefined> {
return fs.readFileSync(iidFile, {encoding: 'utf-8'}); return fs.readFileSync(iidFile, {encoding: 'utf-8'});
} }
export async function getSecret(kvp: string): Promise<string> { export async function getSecretString(kvp: string): Promise<string> {
return getSecret(kvp, false);
}
export async function getSecretFile(kvp: string): Promise<string> {
return getSecret(kvp, true);
}
export async function getSecret(kvp: string, file: boolean): Promise<string> {
const delimiterIndex = kvp.indexOf('='); const delimiterIndex = kvp.indexOf('=');
const key = kvp.substring(0, delimiterIndex); const key = kvp.substring(0, delimiterIndex);
const value = kvp.substring(delimiterIndex + 1); let value = kvp.substring(delimiterIndex + 1);
if (key.length == 0 || value.length == 0) { if (key.length == 0 || value.length == 0) {
throw new Error(`${kvp} is not a valid secret`); throw new Error(`${kvp} is not a valid secret`);
} }
if (file) {
if (!fs.existsSync(value)) {
throw new Error(`secret file ${value} not found`);
}
value = fs.readFileSync(value, {encoding: 'utf-8'});
}
const secretFile = context.tmpNameSync({ const secretFile = context.tmpNameSync({
tmpdir: context.tmpDir() tmpdir: context.tmpDir()
}); });
await fs.writeFileSync(secretFile, value); fs.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`; return `id=${key},src=${secretFile}`;
} }

View File

@ -30,6 +30,7 @@ export interface Inputs {
cacheFrom: string[]; cacheFrom: string[];
cacheTo: string[]; cacheTo: string[];
secrets: string[]; secrets: string[];
secretFiles: string[];
githubToken: string; githubToken: string;
ssh: string[]; ssh: string[];
} }
@ -73,6 +74,7 @@ export async function getInputs(defaultContext: string): Promise<Inputs> {
cacheFrom: await getInputList('cache-from', true), cacheFrom: await getInputList('cache-from', true),
cacheTo: await getInputList('cache-to', true), cacheTo: await getInputList('cache-to', true),
secrets: await getInputList('secrets', true), secrets: await getInputList('secrets', true),
secretFiles: await getInputList('secret-files', true),
githubToken: core.getInput('github-token'), githubToken: core.getInput('github-token'),
ssh: await getInputList('ssh') ssh: await getInputList('ssh')
}; };
@ -123,13 +125,20 @@ async function getBuildArgs(inputs: Inputs, defaultContext: string, buildxVersio
}); });
await asyncForEach(inputs.secrets, async secret => { await asyncForEach(inputs.secrets, async secret => {
try { try {
args.push('--secret', await buildx.getSecret(secret)); args.push('--secret', await buildx.getSecretString(secret));
} catch (err) {
core.warning(err.message);
}
});
await asyncForEach(inputs.secretFiles, async secretFile => {
try {
args.push('--secret', await buildx.getSecretFile(secretFile));
} catch (err) { } catch (err) {
core.warning(err.message); core.warning(err.message);
} }
}); });
if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) { if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) {
args.push('--secret', await buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`)); args.push('--secret', await buildx.getSecretString(`GIT_AUTH_TOKEN=${inputs.githubToken}`));
} }
await asyncForEach(inputs.ssh, async ssh => { await asyncForEach(inputs.ssh, async ssh => {
args.push('--ssh', ssh); args.push('--ssh', ssh);

View File

@ -1236,10 +1236,10 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csv-parse@*, csv-parse@^4.14.2: csv-parse@*, csv-parse@^4.15.1:
version "4.14.2" version "4.15.1"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.14.2.tgz#c1329cff95a99b8773a92c4e62f8bff114b34726" resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.15.1.tgz#fc5a0a1b24eaa6d4c24892daa387c46f7f92f8d2"
integrity sha512-YE2xlTKtM035/94llhgsp9qFQxGi47EkQJ1pZ+mLT/98GpIsbjkMGAb7Rmu9hNxVfYFOLf10hP+rPVqnoccLgw== integrity sha512-TXIvRtNp0fqMJbk3yPR35bQIDzMH4khDwduElzE7Fl1wgnl25mnWYLSLqd/wS5GsDoX1rWtysivEYMNsz5jKwQ==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"