Skip to content

Commit a6df1cf

Browse files
Adds support for multi-platform builds (#119) (#107)
1 parent 0e68818 commit a6df1cf

File tree

10 files changed

+249
-41
lines changed

10 files changed

+249
-41
lines changed

.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"node": true,
99
"jest": true
1010
},
11+
"parserOptions": {
12+
"ecmaVersion": 2021
13+
},
1114
"rules": {
1215
"prettier/prettier": [
1316
"error",

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: npm
4+
directory: "/"
5+
schedule:
6+
interval: daily
7+
open-pull-requests-limit: 10
8+
target-branch: 'master'

.github/workflows/e2e.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ jobs:
2626
addLatest: true
2727
labels: org.opencontainers.image.description="A Hello World image used for e2e tests"
2828

29+
- name: Multi-platform e2e
30+
image: mrsmithers/hello-world
31+
dockerfile: ./e2e/Dockerfile
32+
registry: docker.io
33+
username: DOCKERHUB_USERNAME
34+
password: DOCKERHUB_PASSWORD
35+
multiPlatform: true
36+
platform: linux/amd64,linux/arm64,linux/arm/v7
37+
2938
- name: GCR e2e
3039
image: orbital-bank-301021/hello-world
3140
dockerfile: ./e2e/Dockerfile
@@ -70,7 +79,7 @@ jobs:
7079

7180
- name: Create check run
7281
if: ${{ inputs.pr-trigger }}
73-
uses: actions/github-script@v5
82+
uses: actions/github-script@v6
7483
env:
7584
name: ${{ matrix.name }}
7685
number: ${{ github.event.client_payload.pull_request.number }}
@@ -117,7 +126,7 @@ jobs:
117126
118127
- name: Update check run
119128
if: ${{ inputs.pr-trigger }}
120-
uses: actions/github-script@v5
129+
uses: actions/github-script@v6
121130
env:
122131
number: ${{ github.event.client_payload.pull_request.number }}
123132
job: ${{ github.job }}

README.md

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,23 @@ Builds a Docker image and pushes it to the private registry of your choosing.
1414
- AWS Elastic Container Registry (ECR)
1515
- GitHub Docker Registry
1616

17+
## Features
18+
19+
- [Auto-tagging with GitOps](#auto-tagging-with-gitops)
20+
- [BuildKit support](#buildkit-support)
21+
- [Multi-platform builds](#multi-platform-builds)
22+
1723
## Breaking changes
1824

19-
If you're experiencing issues, be sure you are using the [latest stable release](https://github.com/mr-smithers-excellent/docker-build-push/releases/latest) (currently v5). The AWS ECR login command became deprecated between v4 and v5. Additionally, support for multiple tags was added between v4 and v5.
25+
If you're experiencing issues, be sure you are using the [latest stable release](https://github.com/mr-smithers-excellent/docker-build-push/releases/latest) (currently v6).
26+
27+
### v6
28+
- Multi-platform builds now supported
29+
30+
### v5
31+
- AWS ECR [get-login command](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html) became deprecated, migrated to [get-login-password command](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ecr/get-login-password.html)
32+
- Support for multiple tags added
33+
- BuildKit support added
2034

2135
## Basic usage
2236

@@ -25,10 +39,10 @@ If you're experiencing issues, be sure you are using the [latest stable release]
2539

2640
```yaml
2741
steps:
28-
- uses: actions/checkout@v2
42+
- uses: actions/checkout@v3
2943
name: Check out code
3044

31-
- uses: mr-smithers-excellent/docker-build-push@v5
45+
- uses: mr-smithers-excellent/docker-build-push@v6
3246
name: Build & push Docker image
3347
with:
3448
image: repo/image
@@ -42,7 +56,7 @@ steps:
4256
## Inputs
4357
4458
| Name | Description | Required | Type |
45-
| -------------- | -------------------------------------------------------------------------------------------------------- | -------- | ------- |
59+
|----------------|----------------------------------------------------------------------------------------------------------|----------|---------|
4660
| image | Docker image name | Yes | String |
4761
| tags | Comma separated docker image tags (see [Tagging the image with GitOps](#tagging-the-image-using-gitops)) | No | List |
4862
| addLatest | Adds the `latest` tag to the GitOps-generated tags | No | Boolean |
@@ -58,12 +72,14 @@ steps:
5872
| password | Docker registry password or token | No | String |
5973
| githubOrg | GitHub organization to push image to (if not current) | No | String |
6074
| enableBuildKit | Enables Docker BuildKit support | No | Boolean |
75+
| multiPlatform | Enables Docker buildx support | No | Boolean |
76+
| overrideDriver | Disables setting up docker-container driver (if `true`, alternative docker driver must be set up) | No | Boolean |
6177
| pushImage | Flag for disabling the login & push steps, set to `true` by default | No | Boolean |
6278

6379
## Outputs
6480

6581
| Name | Description | Format |
66-
| ------------- | -------------------------------------------------- | ---------------------- |
82+
|---------------|----------------------------------------------------|------------------------|
6783
| imageFullName | Full name of the Docker image with registry prefix | `registry/owner/image` |
6884
| imageName | Name of the Docker image with owner prefix | `owner/image` |
6985
| tags | Tags for the Docker image | `v1,latest` |
@@ -82,7 +98,7 @@ There is a distinction between secrets at the [repository](https://docs.github.c
8298
- Modify sample below and include in your workflow `.github/workflows/*.yml` file
8399

84100
```yaml
85-
uses: mr-smithers-excellent/docker-build-push@v5
101+
uses: mr-smithers-excellent/docker-build-push@v6
86102
with:
87103
image: docker-hub-repo/image-name
88104
registry: docker.io
@@ -99,7 +115,7 @@ with:
99115
- Ensure you set the username to `_json_key`
100116

101117
```yaml
102-
uses: mr-smithers-excellent/docker-build-push@v5
118+
uses: mr-smithers-excellent/docker-build-push@v6
103119
with:
104120
image: gcp-project/image-name
105121
registry: gcr.io
@@ -116,7 +132,7 @@ with:
116132
- Modify sample below and include in your workflow `.github/workflows/*.yml` file
117133

118134
```yaml
119-
uses: mr-smithers-excellent/docker-build-push@v5
135+
uses: mr-smithers-excellent/docker-build-push@v6
120136
with:
121137
image: image-name
122138
registry: [aws-account-number].dkr.ecr.[region].amazonaws.com
@@ -137,7 +153,7 @@ env:
137153
#### New ghcr.io
138154

139155
```yaml
140-
uses: mr-smithers-excellent/docker-build-push@v5
156+
uses: mr-smithers-excellent/docker-build-push@v6
141157
with:
142158
image: image-name
143159
registry: ghcr.io
@@ -149,20 +165,20 @@ with:
149165
#### Legacy docker.pkg.github.com
150166

151167
```yaml
152-
uses: mr-smithers-excellent/docker-build-push@v5
168+
uses: mr-smithers-excellent/docker-build-push@v6
153169
with:
154170
image: github-repo/image-name
155171
registry: docker.pkg.github.com
156172
username: ${{ github.actor }}
157173
password: ${{ secrets.GITHUB_TOKEN }}
158174
```
159175

160-
## Tagging the image using GitOps
176+
## Auto-tagging with GitOps
161177

162178
By default, if you do not pass a `tags` input this action will use an algorithm based on the state of your git repo to determine the Docker image tag(s). This is designed to enable developers to more easily use [GitOps](https://www.weave.works/technologies/gitops/) in their CI/CD pipelines. Below is a table detailing how the GitHub trigger (branch or tag) determines the Docker tag(s).
163179

164180
| Trigger | Commit SHA | addLatest | addTimestamp | Docker Tag(s) |
165-
| ------------------------ | ---------- | --------- | ------------ | -------------------------------------- |
181+
|--------------------------|------------|-----------|--------------|----------------------------------------|
166182
| /refs/tags/v1.0 | N/A | false | N/A | v1.0 |
167183
| /refs/tags/v1.0 | N/A | true | N/A | v1.0,latest |
168184
| /refs/heads/dev | 1234567 | false | true | dev-1234567-2021-09-01.195027 |
@@ -171,3 +187,67 @@ By default, if you do not pass a `tags` input this action will use an algorithm
171187
| /refs/heads/main | 1234567 | true | false | main-1234567,latest |
172188
| /refs/heads/SOME-feature | 1234567 | false | true | some-feature-1234567-2021-09-01.195027 |
173189
| /refs/heads/SOME-feature | 1234567 | true | false | some-feature-1234567,latest |
190+
191+
## BuildKit support
192+
193+
Enables [Docker BuildKit](https://docs.docker.com/build/buildkit/)
194+
195+
```yaml
196+
steps:
197+
- uses: actions/checkout@v3
198+
name: Check out code
199+
200+
- uses: mr-smithers-excellent/docker-build-push@v6
201+
name: Build & push Docker image
202+
with:
203+
image: repo/image
204+
registry: docker.io
205+
enableBuildKit: true
206+
username: ${{ secrets.DOCKER_USERNAME }}
207+
password: ${{ secrets.DOCKER_PASSWORD }}
208+
```
209+
210+
## Multi-platform builds
211+
212+
Enables [multi-platform builds](https://docs.docker.com/build/building/multi-platform/) with the default [docker-container driver](https://docs.docker.com/build/drivers/docker-container/)
213+
214+
```yaml
215+
steps:
216+
- uses: actions/checkout@v3
217+
name: Check out code
218+
219+
- uses: mr-smithers-excellent/docker-build-push@v6
220+
name: Build & push Docker image
221+
with:
222+
image: repo/image
223+
registry: docker.io
224+
multiPlatform: true
225+
platform: linux/amd64,linux/arm64,linux/arm/v7
226+
username: ${{ secrets.DOCKER_USERNAME }}
227+
password: ${{ secrets.DOCKER_PASSWORD }}
228+
```
229+
230+
Enables [multi-platform builds](https://docs.docker.com/build/building/multi-platform/) with custom driver
231+
232+
```yaml
233+
steps:
234+
- uses: actions/checkout@v3
235+
name: Check out code
236+
237+
# Required when overrideDriver is set to true
238+
- uses: docker/setup-buildx-action@v2
239+
name: Customize Docker driver
240+
with:
241+
driver-opts: image=moby/buildkit:v0.11.0
242+
243+
- uses: mr-smithers-excellent/docker-build-push@v6
244+
name: Build & push Docker image
245+
with:
246+
image: repo/image
247+
registry: docker.io
248+
multiPlatform: true
249+
platform: linux/amd64,linux/arm64,linux/arm/v7
250+
overrideDriver: true
251+
username: ${{ secrets.DOCKER_USERNAME }}
252+
password: ${{ secrets.DOCKER_PASSWORD }}
253+
```

action.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ inputs:
5151
description: "Enables Docker BuildKit support"
5252
required: false
5353
default: "false"
54+
multiPlatform:
55+
description: "Builds image with buildx to support multiple platforms"
56+
required: false
57+
default: "false"
58+
overrideDriver:
59+
description: "Disables setting up docker-container driver"
60+
required: false
61+
default: "false"
5462
pushImage:
5563
description: "Flag for disabling the login & push steps, set to true by default"
5664
required: false

dist/index.js

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9455,11 +9455,26 @@ const buildOpts = {
94559455
labels: undefined,
94569456
target: undefined,
94579457
buildDir: undefined,
9458+
multiPlatform: false,
9459+
overrideDriver: false,
94589460
enableBuildKit: false,
94599461
platform: undefined,
94609462
skipPush: false
94619463
};
94629464

9465+
const setBuildOpts = (addLatest, addTimestamp) => {
9466+
buildOpts.tags = parseArray(core.getInput('tags')) || docker.createTags(addLatest, addTimestamp);
9467+
buildOpts.multiPlatform = core.getInput('multiPlatform') === 'true';
9468+
buildOpts.overrideDriver = core.getInput('overrideDriver') === 'true';
9469+
buildOpts.buildArgs = parseArray(core.getInput('buildArgs'));
9470+
buildOpts.labels = parseArray(core.getInput('labels'));
9471+
buildOpts.target = core.getInput('target');
9472+
buildOpts.buildDir = core.getInput('directory') || '.';
9473+
buildOpts.enableBuildKit = core.getInput('enableBuildKit') === 'true';
9474+
buildOpts.platform = core.getInput('platform');
9475+
buildOpts.skipPush = core.getInput('pushImage') === 'false';
9476+
};
9477+
94639478
const run = () => {
94649479
try {
94659480
// Capture action inputs
@@ -9471,14 +9486,7 @@ const run = () => {
94719486
const githubOwner = core.getInput('githubOrg') || github.getDefaultOwner();
94729487
const addLatest = core.getInput('addLatest') === 'true';
94739488
const addTimestamp = core.getInput('addTimestamp') === 'true';
9474-
buildOpts.tags = parseArray(core.getInput('tags')) || docker.createTags(addLatest, addTimestamp);
9475-
buildOpts.buildArgs = parseArray(core.getInput('buildArgs'));
9476-
buildOpts.labels = parseArray(core.getInput('labels'));
9477-
buildOpts.target = core.getInput('target');
9478-
buildOpts.buildDir = core.getInput('directory') || '.';
9479-
buildOpts.enableBuildKit = core.getInput('enableBuildKit') === 'true';
9480-
buildOpts.platform = core.getInput('platform');
9481-
buildOpts.skipPush = core.getInput('pushImage') === 'false';
9489+
setBuildOpts(addLatest, addTimestamp);
94829490

94839491
// Create the Docker image name
94849492
const imageFullName = docker.createFullImageName(registry, image, githubOwner);
@@ -9487,7 +9495,7 @@ const run = () => {
94879495
// Log in, build & push the Docker image
94889496
docker.login(username, password, registry, buildOpts.skipPush);
94899497
docker.build(imageFullName, dockerfile, buildOpts);
9490-
docker.push(imageFullName, buildOpts.tags, buildOpts.skipPush);
9498+
docker.push(imageFullName, buildOpts.tags, buildOpts);
94919499

94929500
// Capture outputs
94939501
core.setOutput('imageFullName', imageFullName);
@@ -9563,7 +9571,9 @@ const createTags = (addLatest, addTimestamp) => {
95639571
// Dynamically create 'docker build' command based on inputs provided
95649572
const createBuildCommand = (imageName, dockerfile, buildOpts) => {
95659573
const tagsSuffix = buildOpts.tags.map(tag => `-t ${imageName}:${tag}`).join(' ');
9566-
let buildCommandPrefix = `docker build -f ${dockerfile} ${tagsSuffix}`;
9574+
const builder = buildOpts.multiPlatform ? 'buildx build' : 'build';
9575+
9576+
let buildCommandPrefix = `docker ${builder} -f ${dockerfile} ${tagsSuffix}`;
95679577

95689578
if (buildOpts.buildArgs) {
95699579
const argsSuffix = buildOpts.buildArgs.map(arg => `--build-arg ${arg}`).join(' ');
@@ -9583,9 +9593,14 @@ const createBuildCommand = (imageName, dockerfile, buildOpts) => {
95839593
buildCommandPrefix = `${buildCommandPrefix} --platform ${buildOpts.platform}`;
95849594
}
95859595

9596+
if (buildOpts.multiPlatform && !buildOpts.skipPush) {
9597+
buildCommandPrefix = `${buildCommandPrefix} --push`;
9598+
}
9599+
95869600
if (buildOpts.enableBuildKit) {
95879601
buildCommandPrefix = `DOCKER_BUILDKIT=1 ${buildCommandPrefix}`;
95889602
}
9603+
core.info(`BuildCommand ${buildCommandPrefix} ${buildOpts.buildDir}`);
95899604

95909605
return `${buildCommandPrefix} ${buildOpts.buildDir}`;
95919606
};
@@ -9596,6 +9611,11 @@ const build = (imageName, dockerfile, buildOpts) => {
95969611
core.setFailed(`Dockerfile does not exist in location ${dockerfile}`);
95979612
}
95989613

9614+
// Setup buildx driver
9615+
if (buildOpts.multiPlatform && !buildOpts.overrideDriver) {
9616+
cp.execSync('docker buildx create --use');
9617+
}
9618+
95999619
core.info(`Building Docker image ${imageName} with tags ${buildOpts.tags}...`);
96009620
cp.execSync(createBuildCommand(imageName, dockerfile, buildOpts), cpOptions);
96019621
};
@@ -9629,8 +9649,12 @@ const login = (username, password, registry, skipPush) => {
96299649
};
96309650

96319651
// Push Docker image & all tags
9632-
const push = (imageName, tags, skipPush) => {
9633-
if (skipPush) {
9652+
const push = (imageName, tags, buildOpts) => {
9653+
if (buildOpts?.multiPlatform) {
9654+
core.info('Input multiPlatform is set to true, skipping Docker push step...');
9655+
return;
9656+
}
9657+
if (buildOpts?.skipPush) {
96349658
core.info('Input skipPush is set to true, skipping Docker push step...');
96359659
return;
96369660
}

0 commit comments

Comments
 (0)