mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-26 04:22:05 +02:00
feat: multi-arch docker images (#1923)
* feat: multi-arch docker images Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * feat: split files Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * docs: manifest Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * refactor: split files Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * test: added some Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * docs: flags Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: fmt Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * fix: diff Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * ci: enable experimental Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com> * ci: multi-arch goreleaser images Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
This commit is contained in:
parent
d2f4cb7039
commit
e337fc9ca0
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -11,6 +11,8 @@ on:
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_CLI_EXPERIMENTAL: "enabled"
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
|
106
.goreleaser.yml
106
.goreleaser.yml
@ -35,12 +35,12 @@ changelog:
|
||||
- go mod tidy
|
||||
dockers:
|
||||
- image_templates:
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-cgo'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-cgo'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-cgo'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-cgo'
|
||||
- 'goreleaser/goreleaser:latest-cgo'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-cgo'
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-cgo-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-cgo-amd64'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-cgo-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-cgo-amd64'
|
||||
- 'goreleaser/goreleaser:latest-cgo-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-cgo-amd64'
|
||||
dockerfile: Dockerfile.cgo
|
||||
binaries:
|
||||
- goreleaser
|
||||
@ -50,15 +50,40 @@ dockers:
|
||||
- "--label=org.opencontainers.image.name={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.source={{.GitURL}}"
|
||||
- "--build-arg"
|
||||
- "ARCH=amd64"
|
||||
extra_files:
|
||||
- scripts/entrypoint.sh
|
||||
- image_templates:
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}'
|
||||
- 'goreleaser/goreleaser:latest'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest'
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-cgo-arm64v8'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-cgo-arm64v8'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-cgo-arm64v8'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-cgo-arm64v8'
|
||||
- 'goreleaser/goreleaser:latest-cgo-arm64v8'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-cgo-arm64v8'
|
||||
dockerfile: Dockerfile.cgo
|
||||
binaries:
|
||||
- goreleaser
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.name={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.source={{.GitURL}}"
|
||||
- "--build-arg"
|
||||
- "ARCH=arm64v8"
|
||||
goarch: arm64
|
||||
extra_files:
|
||||
- scripts/entrypoint.sh
|
||||
- image_templates:
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-amd64'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-amd64'
|
||||
- 'goreleaser/goreleaser:latest-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-amd64'
|
||||
dockerfile: Dockerfile
|
||||
binaries:
|
||||
- goreleaser
|
||||
@ -69,16 +94,57 @@ dockers:
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.source={{.GitURL}}"
|
||||
- "--label=com.github.actions.name={{.ProjectName}}"
|
||||
- "--label=com.github.actions.description=Deliver Go binaries as fast and easily as possible"
|
||||
- "--label=com.github.actions.icon=terminal"
|
||||
- "--label=com.github.actions.color=blue"
|
||||
- "--label=repository=http://github.com/goreleaser/goreleaser"
|
||||
- "--label=homepage=http://goreleaser.com"
|
||||
- "--label=maintainer=Carlos Becker <goreleaser@carlosbecker.com>"
|
||||
|
||||
- "--build-arg"
|
||||
- "ARCH=amd64"
|
||||
extra_files:
|
||||
- scripts/entrypoint.sh
|
||||
- image_templates:
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-arm64v8'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-arm64v8'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-arm64v8'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-arm64v8'
|
||||
- 'goreleaser/goreleaser:latest-arm64v8'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-arm64v8'
|
||||
dockerfile: Dockerfile
|
||||
binaries:
|
||||
- goreleaser
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
||||
- "--label=org.opencontainers.image.name={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
||||
- "--label=org.opencontainers.image.source={{.GitURL}}"
|
||||
- "--build-arg"
|
||||
- "ARCH=arm64v8"
|
||||
goarch: arm64
|
||||
extra_files:
|
||||
- scripts/entrypoint.sh
|
||||
docker_manifests:
|
||||
- name_template: 'goreleaser/goreleaser:{{ .Tag }}'
|
||||
image_templates:
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-amd64'
|
||||
- 'goreleaser/goreleaser:{{ .Tag }}-arm64v8'
|
||||
- name_template: 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}'
|
||||
image_templates:
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:{{ .Tag }}-arm64v8'
|
||||
- name_template: 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}'
|
||||
image_templates:
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-amd64'
|
||||
- 'goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-arm64v8'
|
||||
- name_template: 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}'
|
||||
image_templates:
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:v{{ .Major }}.{{ .Minor }}-arm64v8'
|
||||
- name_template: 'goreleaser/goreleaser:latest'
|
||||
image_templates:
|
||||
- 'goreleaser/goreleaser:latest-amd64'
|
||||
- 'goreleaser/goreleaser:latest-arm64v8'
|
||||
- name_template: 'ghcr.io/goreleaser/goreleaser:latest'
|
||||
image_templates:
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-amd64'
|
||||
- 'ghcr.io/goreleaser/goreleaser:latest-arm64v8'
|
||||
archives:
|
||||
- name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
replacements:
|
||||
|
@ -1,4 +1,5 @@
|
||||
FROM golang:1.15-alpine
|
||||
ARG ARCH
|
||||
FROM ${ARCH}/golang:1.15-alpine
|
||||
|
||||
RUN apk add --no-cache bash \
|
||||
curl \
|
||||
|
@ -1,4 +1,5 @@
|
||||
FROM golang:1.15-alpine
|
||||
ARG ARCH
|
||||
FROM ${ARCH}/golang:1.15-alpine
|
||||
|
||||
RUN apk add --no-cache bash \
|
||||
build-base \
|
||||
|
3
internal/pipe/docker/doc.go
Normal file
3
internal/pipe/docker/doc.go
Normal file
@ -0,0 +1,3 @@
|
||||
// Package docker provides a Pipe that creates and pushes Docker images and
|
||||
// manifests.
|
||||
package docker
|
@ -1,4 +1,3 @@
|
||||
// Package docker provides a Pipe that creates and pushes a Docker image
|
||||
package docker
|
||||
|
||||
import (
|
||||
|
@ -60,6 +60,7 @@ func killAndRm(t *testing.T) {
|
||||
_ = exec.Command("docker", "rm", "alt_registry").Run()
|
||||
}
|
||||
|
||||
// TODO: this test is too big... split in smaller tests? Mainly the manifest ones...
|
||||
func TestRunPipe(t *testing.T) {
|
||||
type errChecker func(*testing.T, error)
|
||||
var shouldErr = func(msg string) errChecker {
|
||||
@ -89,13 +90,168 @@ func TestRunPipe(t *testing.T) {
|
||||
var noLabels = func(t *testing.T, count int) {}
|
||||
|
||||
var table = map[string]struct {
|
||||
dockers []config.Docker
|
||||
env map[string]string
|
||||
expect []string
|
||||
assertImageLabels imageLabelFinder
|
||||
assertError errChecker
|
||||
pubAssertError errChecker
|
||||
dockers []config.Docker
|
||||
manifests []config.DockerManifest
|
||||
env map[string]string
|
||||
expect []string
|
||||
assertImageLabels imageLabelFinder
|
||||
assertError errChecker
|
||||
pubAssertError errChecker
|
||||
manifestAssertError errChecker
|
||||
}{
|
||||
"multiarch": {
|
||||
dockers: []config.Docker{
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch:test-amd64"},
|
||||
Goos: "linux",
|
||||
Goarch: "amd64",
|
||||
Dockerfile: "testdata/Dockerfile.arch",
|
||||
Binaries: []string{"mybin"},
|
||||
BuildFlagTemplates: []string{"--build-arg", "ARCH=amd64"},
|
||||
},
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch:test-arm64v8"},
|
||||
Goos: "linux",
|
||||
Goarch: "arm64",
|
||||
Dockerfile: "testdata/Dockerfile.arch",
|
||||
Binaries: []string{"mybin"},
|
||||
BuildFlagTemplates: []string{"--build-arg", "ARCH=arm64v8"},
|
||||
},
|
||||
},
|
||||
manifests: []config.DockerManifest{
|
||||
{
|
||||
// XXX: fails if :latest https://github.com/docker/distribution/issues/3100
|
||||
NameTemplate: registry + "goreleaser/test_multiarch:test",
|
||||
ImageTemplates: []string{
|
||||
registry + "goreleaser/test_multiarch:test-amd64",
|
||||
registry + "goreleaser/test_multiarch:test-arm64v8",
|
||||
},
|
||||
CreateFlags: []string{"--insecure"},
|
||||
PushFlags: []string{"--insecure"},
|
||||
},
|
||||
},
|
||||
expect: []string{
|
||||
registry + "goreleaser/test_multiarch:test-amd64",
|
||||
registry + "goreleaser/test_multiarch:test-arm64v8",
|
||||
},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
},
|
||||
"multiarch image not found": {
|
||||
dockers: []config.Docker{
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_fail:latest-arm64v8"},
|
||||
Goos: "linux",
|
||||
Goarch: "arm64",
|
||||
Dockerfile: "testdata/Dockerfile.arch",
|
||||
Binaries: []string{"mybin"},
|
||||
BuildFlagTemplates: []string{"--build-arg", "ARCH=arm64v8"},
|
||||
},
|
||||
},
|
||||
manifests: []config.DockerManifest{
|
||||
{
|
||||
NameTemplate: registry + "goreleaser/test_multiarch_fail:test",
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_fail:latest-amd64"},
|
||||
CreateFlags: []string{"--insecure"},
|
||||
PushFlags: []string{"--insecure"},
|
||||
},
|
||||
},
|
||||
expect: []string{registry + "goreleaser/test_multiarch_fail:latest-arm64v8"},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldErr("failed to create docker manifest: localhost:5000/goreleaser/test_multiarch_fail:test"),
|
||||
assertImageLabels: noLabels,
|
||||
},
|
||||
"multiarch manifest template error": {
|
||||
dockers: []config.Docker{
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_manifest_tmpl_error"},
|
||||
Goos: "linux",
|
||||
Goarch: "arm64",
|
||||
Dockerfile: "testdata/Dockerfile",
|
||||
Binaries: []string{"mybin"},
|
||||
},
|
||||
},
|
||||
manifests: []config.DockerManifest{
|
||||
{
|
||||
NameTemplate: registry + "goreleaser/test_multiarch_manifest_tmpl_error:{{ .Goos }",
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_manifest_tmpl_error"},
|
||||
},
|
||||
},
|
||||
expect: []string{registry + "goreleaser/test_multiarch_manifest_tmpl_error"},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldErr(`template: tmpl:1: unexpected "}" in operand`),
|
||||
assertImageLabels: noLabels,
|
||||
},
|
||||
"multiarch image template error": {
|
||||
dockers: []config.Docker{
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_img_tmpl_error"},
|
||||
Goos: "linux",
|
||||
Goarch: "arm64",
|
||||
Dockerfile: "testdata/Dockerfile",
|
||||
Binaries: []string{"mybin"},
|
||||
},
|
||||
},
|
||||
manifests: []config.DockerManifest{
|
||||
{
|
||||
NameTemplate: registry + "goreleaser/test_multiarch_img_tmpl_error",
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_img_tmpl_error:{{ .Goos }"},
|
||||
},
|
||||
},
|
||||
expect: []string{registry + "goreleaser/test_multiarch_img_tmpl_error"},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldErr(`template: tmpl:1: unexpected "}" in operand`),
|
||||
assertImageLabels: noLabels,
|
||||
},
|
||||
"multiarch missing manifest name": {
|
||||
dockers: []config.Docker{
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_no_mainifest_name"},
|
||||
Goos: "linux",
|
||||
Goarch: "arm64",
|
||||
Dockerfile: "testdata/Dockerfile",
|
||||
Binaries: []string{"mybin"},
|
||||
},
|
||||
},
|
||||
manifests: []config.DockerManifest{
|
||||
{
|
||||
NameTemplate: " ",
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_no_mainifest_name"},
|
||||
},
|
||||
},
|
||||
expect: []string{registry + "goreleaser/test_multiarch_no_mainifest_name"},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: testlib.AssertSkipped,
|
||||
assertImageLabels: noLabels,
|
||||
},
|
||||
"multiarch missing images": {
|
||||
dockers: []config.Docker{
|
||||
{
|
||||
ImageTemplates: []string{registry + "goreleaser/test_multiarch_no_mainifest_images"},
|
||||
Dockerfile: "testdata/Dockerfile",
|
||||
Goos: "linux",
|
||||
Goarch: "arm64",
|
||||
Binaries: []string{"mybin"},
|
||||
},
|
||||
},
|
||||
manifests: []config.DockerManifest{
|
||||
{
|
||||
NameTemplate: "ignored",
|
||||
ImageTemplates: []string{" ", " ", ""},
|
||||
},
|
||||
},
|
||||
expect: []string{registry + "goreleaser/test_multiarch_no_mainifest_images"},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: testlib.AssertSkipped,
|
||||
assertImageLabels: noLabels,
|
||||
},
|
||||
"valid": {
|
||||
env: map[string]string{
|
||||
"FOO": "123",
|
||||
@ -149,8 +305,9 @@ func TestRunPipe(t *testing.T) {
|
||||
"label=org.label-schema.vcs-ref=a1b2c3d4",
|
||||
"label=org.label-schema.name=mybin",
|
||||
),
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"valid-with-builds": {
|
||||
dockers: []config.Docker{
|
||||
@ -168,9 +325,10 @@ func TestRunPipe(t *testing.T) {
|
||||
expect: []string{
|
||||
registry + "goreleaser/test_run_pipe_build:latest",
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"multiple images with same extra file": {
|
||||
dockers: []config.Docker{
|
||||
@ -199,9 +357,10 @@ func TestRunPipe(t *testing.T) {
|
||||
registry + "goreleaser/multiplefiles1:latest",
|
||||
registry + "goreleaser/multiplefiles2:latest",
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"multiple images with same dockerfile": {
|
||||
dockers: []config.Docker{
|
||||
@ -229,8 +388,9 @@ func TestRunPipe(t *testing.T) {
|
||||
registry + "goreleaser/test_run_pipe:latest",
|
||||
registry + "goreleaser/test_run_pipe2:latest",
|
||||
},
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"valid_skip_push": {
|
||||
dockers: []config.Docker{
|
||||
@ -295,9 +455,10 @@ func TestRunPipe(t *testing.T) {
|
||||
expect: []string{
|
||||
registry + "goreleaser/test_run_pipe:1.0.0",
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"valid build args": {
|
||||
dockers: []config.Docker{
|
||||
@ -317,9 +478,10 @@ func TestRunPipe(t *testing.T) {
|
||||
expect: []string{
|
||||
registry + "goreleaser/test_build_args:latest",
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"bad build args": {
|
||||
dockers: []config.Docker{
|
||||
@ -457,9 +619,10 @@ func TestRunPipe(t *testing.T) {
|
||||
expect: []string{
|
||||
"docker.io/nope:latest",
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldErr(`requested access to the resource is denied`),
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldErr(`requested access to the resource is denied`),
|
||||
manifestAssertError: shouldNotErr,
|
||||
},
|
||||
"dockerfile_doesnt_exist": {
|
||||
dockers: []config.Docker{
|
||||
@ -513,9 +676,10 @@ func TestRunPipe(t *testing.T) {
|
||||
Dockerfile: "testdata/Dockerfile.multiple",
|
||||
},
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
expect: []string{
|
||||
registry + "goreleaser/multiple:latest",
|
||||
},
|
||||
@ -534,9 +698,10 @@ func TestRunPipe(t *testing.T) {
|
||||
Dockerfile: "testdata/Dockerfile",
|
||||
},
|
||||
},
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
assertImageLabels: noLabels,
|
||||
assertError: shouldNotErr,
|
||||
pubAssertError: shouldNotErr,
|
||||
manifestAssertError: shouldNotErr,
|
||||
expect: []string{
|
||||
registry + "goreleaser/templatedbins:latest",
|
||||
},
|
||||
@ -575,9 +740,10 @@ func TestRunPipe(t *testing.T) {
|
||||
require.NoError(tt, err)
|
||||
|
||||
var ctx = context.New(config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Dockers: docker.dockers,
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Dockers: docker.dockers,
|
||||
DockerManifests: docker.manifests,
|
||||
})
|
||||
ctx.Parallelism = 1
|
||||
ctx.Env = docker.env
|
||||
@ -592,7 +758,7 @@ func TestRunPipe(t *testing.T) {
|
||||
Patch: 0,
|
||||
}
|
||||
for _, os := range []string{"linux", "darwin"} {
|
||||
for _, arch := range []string{"amd64", "386"} {
|
||||
for _, arch := range []string{"amd64", "386", "arm64"} {
|
||||
for _, bin := range []string{"mybin", "anotherbin"} {
|
||||
ctx.Artifacts.Add(&artifact.Artifact{
|
||||
Name: bin,
|
||||
@ -618,6 +784,7 @@ func TestRunPipe(t *testing.T) {
|
||||
docker.assertError(tt, err)
|
||||
if err == nil {
|
||||
docker.pubAssertError(tt, Pipe{}.Publish(ctx))
|
||||
docker.manifestAssertError(tt, ManifestPipe{}.Publish(ctx))
|
||||
}
|
||||
|
||||
for _, d := range docker.dockers {
|
||||
|
107
internal/pipe/docker/manifest.go
Normal file
107
internal/pipe/docker/manifest.go
Normal file
@ -0,0 +1,107 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/goreleaser/goreleaser/internal/pipe"
|
||||
"github.com/goreleaser/goreleaser/internal/semerrgroup"
|
||||
"github.com/goreleaser/goreleaser/internal/tmpl"
|
||||
"github.com/goreleaser/goreleaser/pkg/config"
|
||||
"github.com/goreleaser/goreleaser/pkg/context"
|
||||
)
|
||||
|
||||
// ManifestPipe is beta implementation of for the docker manifest feature,
|
||||
// allowing to publish multi-arch docker images.
|
||||
type ManifestPipe struct{}
|
||||
|
||||
func (ManifestPipe) String() string {
|
||||
return "docker manifests"
|
||||
}
|
||||
|
||||
// Publish the docker manifests.
|
||||
func (ManifestPipe) Publish(ctx *context.Context) error {
|
||||
if ctx.SkipPublish {
|
||||
return pipe.ErrSkipPublishEnabled
|
||||
}
|
||||
var g = semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism))
|
||||
for _, manifest := range ctx.Config.DockerManifests {
|
||||
manifest := manifest
|
||||
g.Go(func() error {
|
||||
name, err := manifestName(ctx, manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
images, err := manifestImages(ctx, manifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dockerManifestCreate(ctx, name, images, manifest.CreateFlags); err != nil {
|
||||
return err
|
||||
}
|
||||
return dockerManifestPush(ctx, name, manifest.PushFlags)
|
||||
})
|
||||
}
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func manifestName(ctx *context.Context, manifest config.DockerManifest) (string, error) {
|
||||
name, err := tmpl.New(ctx).Apply(manifest.NameTemplate)
|
||||
if err != nil {
|
||||
return name, err
|
||||
}
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return name, pipe.Skip("manifest name is empty")
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func manifestImages(ctx *context.Context, manifest config.DockerManifest) ([]string, error) {
|
||||
var imgs []string
|
||||
for _, img := range manifest.ImageTemplates {
|
||||
str, err := tmpl.New(ctx).Apply(img)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
imgs = append(imgs, str)
|
||||
}
|
||||
if strings.TrimSpace(strings.Join(manifest.ImageTemplates, "")) == "" {
|
||||
return imgs, pipe.Skip("manifest has no images")
|
||||
}
|
||||
return imgs, nil
|
||||
}
|
||||
|
||||
func dockerManifestCreate(ctx *context.Context, manifest string, images, flags []string) error {
|
||||
log.WithField("manifest", manifest).Info("creating docker manifest")
|
||||
var args = []string{"manifest", "create", manifest}
|
||||
for _, img := range images {
|
||||
args = append(args, "--amend", img)
|
||||
}
|
||||
args = append(args, flags...)
|
||||
/* #nosec */
|
||||
var cmd = exec.CommandContext(ctx, "docker", args...)
|
||||
log.WithField("cmd", cmd.Args).WithField("cwd", cmd.Dir).Debug("running")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create docker manifest: %s: \n%s: %w", manifest, string(out), err)
|
||||
}
|
||||
log.Debugf("docker manifest output: \n%s", string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerManifestPush(ctx *context.Context, manifest string, flags []string) error {
|
||||
log.WithField("manifest", manifest).Info("pushing docker manifest")
|
||||
var args = []string{"manifest", "push", manifest}
|
||||
args = append(args, flags...)
|
||||
/* #nosec */
|
||||
var cmd = exec.CommandContext(ctx, "docker", args...)
|
||||
log.WithField("cmd", cmd.Args).WithField("cwd", cmd.Dir).Debug("running")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to push docker manifest: %s: \n%s: %w", manifest, string(out), err)
|
||||
}
|
||||
log.Debugf("docker manifest output: \n%s", string(out))
|
||||
return nil
|
||||
}
|
3
internal/pipe/docker/testdata/Dockerfile.arch
vendored
Normal file
3
internal/pipe/docker/testdata/Dockerfile.arch
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
ARG ARCH
|
||||
FROM ${ARCH}/alpine
|
||||
ADD mybin /
|
@ -40,6 +40,7 @@ var publishers = []Publisher{
|
||||
custompublishers.Pipe{},
|
||||
artifactory.Pipe{},
|
||||
docker.Pipe{},
|
||||
docker.ManifestPipe{},
|
||||
snapcraft.Pipe{},
|
||||
// This should be one of the last steps
|
||||
release.Pipe{},
|
||||
|
@ -490,6 +490,14 @@ type Docker struct {
|
||||
BuildFlagTemplates []string `yaml:"build_flag_templates,omitempty"`
|
||||
}
|
||||
|
||||
// DockerManifest config.
|
||||
type DockerManifest struct {
|
||||
NameTemplate string `yaml:"name_template,omitempty"`
|
||||
ImageTemplates []string `yaml:"image_templates,omitempty"`
|
||||
CreateFlags []string `yaml:"create_flags,omitempty"`
|
||||
PushFlags []string `yaml:"push_flags,omitempty"`
|
||||
}
|
||||
|
||||
// Filters config.
|
||||
type Filters struct {
|
||||
Exclude []string `yaml:",omitempty"`
|
||||
@ -563,29 +571,30 @@ type Source struct {
|
||||
|
||||
// Project includes all project configuration.
|
||||
type Project struct {
|
||||
ProjectName string `yaml:"project_name,omitempty"`
|
||||
Env []string `yaml:",omitempty"`
|
||||
Release Release `yaml:",omitempty"`
|
||||
Milestones []Milestone `yaml:",omitempty"`
|
||||
Brews []Homebrew `yaml:",omitempty"`
|
||||
Scoop Scoop `yaml:",omitempty"`
|
||||
Builds []Build `yaml:",omitempty"`
|
||||
Archives []Archive `yaml:",omitempty"`
|
||||
NFPMs []NFPM `yaml:"nfpms,omitempty"`
|
||||
Snapcrafts []Snapcraft `yaml:",omitempty"`
|
||||
Snapshot Snapshot `yaml:",omitempty"`
|
||||
Checksum Checksum `yaml:",omitempty"`
|
||||
Dockers []Docker `yaml:",omitempty"`
|
||||
Artifactories []Upload `yaml:",omitempty"`
|
||||
Uploads []Upload `yaml:",omitempty"`
|
||||
Blobs []Blob `yaml:"blobs,omitempty"`
|
||||
Publishers []Publisher `yaml:"publishers,omitempty"`
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
Signs []Sign `yaml:",omitempty"`
|
||||
EnvFiles EnvFiles `yaml:"env_files,omitempty"`
|
||||
Before Before `yaml:",omitempty"`
|
||||
Source Source `yaml:",omitempty"`
|
||||
ProjectName string `yaml:"project_name,omitempty"`
|
||||
Env []string `yaml:",omitempty"`
|
||||
Release Release `yaml:",omitempty"`
|
||||
Milestones []Milestone `yaml:",omitempty"`
|
||||
Brews []Homebrew `yaml:",omitempty"`
|
||||
Scoop Scoop `yaml:",omitempty"`
|
||||
Builds []Build `yaml:",omitempty"`
|
||||
Archives []Archive `yaml:",omitempty"`
|
||||
NFPMs []NFPM `yaml:"nfpms,omitempty"`
|
||||
Snapcrafts []Snapcraft `yaml:",omitempty"`
|
||||
Snapshot Snapshot `yaml:",omitempty"`
|
||||
Checksum Checksum `yaml:",omitempty"`
|
||||
Dockers []Docker `yaml:",omitempty"`
|
||||
DockerManifests []DockerManifest `yaml:"docker_manifests,omitempty"`
|
||||
Artifactories []Upload `yaml:",omitempty"`
|
||||
Uploads []Upload `yaml:",omitempty"`
|
||||
Blobs []Blob `yaml:"blobs,omitempty"`
|
||||
Publishers []Publisher `yaml:"publishers,omitempty"`
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
Signs []Sign `yaml:",omitempty"`
|
||||
EnvFiles EnvFiles `yaml:"env_files,omitempty"`
|
||||
Before Before `yaml:",omitempty"`
|
||||
Source Source `yaml:",omitempty"`
|
||||
|
||||
// this is a hack ¯\_(ツ)_/¯
|
||||
SingleBuild Build `yaml:"build,omitempty"`
|
||||
|
119
www/docs/customization/docker_manifest.md
Normal file
119
www/docs/customization/docker_manifest.md
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
title: Docker Manifest
|
||||
---
|
||||
|
||||
Since [v0.148.0](https://github.com/goreleaser/goreleaser/releases/tag/v0.148.0),
|
||||
GoReleaser supports building and pushing Docker multi-platform images through
|
||||
the `docker manifest` tool.
|
||||
|
||||
For it to work, it [has to be enabled in the client configurations](https://github.com/docker/cli/blob/master/experimental/README.md).
|
||||
|
||||
Please make sure `docker manifest` works before opening issues.
|
||||
|
||||
!!! warning
|
||||
Please note that this is a beta feature, and it may change or be removed
|
||||
at any time.
|
||||
|
||||
## Customization
|
||||
|
||||
You can create several manifests in a single GoReleaser run, here are all the
|
||||
options available:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
docker_manifests:
|
||||
# You can have multiple Docker manifests.
|
||||
-
|
||||
# Name template for the manifest.
|
||||
# Defaults to empty.
|
||||
name_template: foo/bar:{{ .Version }}
|
||||
|
||||
# Image name templates to be added to this manifest.
|
||||
# Defaults to empty.
|
||||
image_templates:
|
||||
- foo/bar:{{ .Version }}-amd64
|
||||
- foo/bar:{{ .Version }}-arm64v8
|
||||
|
||||
# Extra flags to be passed down to the manifest create command.
|
||||
# Defaults to empty.
|
||||
create_flags:
|
||||
- --insecure
|
||||
|
||||
# Extra flags to be passed down to the manifest push command.
|
||||
# Defaults to empty.
|
||||
push_flags:
|
||||
- --insecure
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Learn more about the [name template engine](/customization/templates/).
|
||||
|
||||
## How it works
|
||||
|
||||
We basically build and push our images as usual, but we also add a new
|
||||
section to our config defining which images are part of which manifests.
|
||||
|
||||
GoReleaser will create and publish the manifest in its publish phase.
|
||||
|
||||
!!! warning
|
||||
Unfortunately, the manifest tool needs the images to be pushed to create
|
||||
the manifest, that's why we both create and push it in the publish phase.
|
||||
|
||||
## Example config
|
||||
|
||||
In this example we will use Docker's `--build-arg` passing an `ARCH` argument.
|
||||
This way we can use the same `Dockerfile` for both the `amd64` and the `arm64`
|
||||
images:
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
ARG ARCH
|
||||
FROM ${ARCH}/alpine
|
||||
COPY mybin /usr/bin/mybin
|
||||
ENTRYPOINT ["/usr/bin/mybin"]
|
||||
```
|
||||
|
||||
Then, on our GoReleaser config file, we need to define both the `dockers` and
|
||||
the `docker_manifests` section:
|
||||
|
||||
```yaml
|
||||
# goreleaser.yml
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
binary: mybin
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
dockers:
|
||||
- image_templates:
|
||||
- "foo/bar:{{ .Version }}-amd64"
|
||||
binaries:
|
||||
- mybin
|
||||
dockerfile: Dockerfile
|
||||
build_flag_templates:
|
||||
- "--build-arg"
|
||||
- "ARCH=amd64"
|
||||
- image_templates:
|
||||
- "foo/bar:{{ .Version }}-arm64v8"
|
||||
binaries:
|
||||
- mybin
|
||||
goarch: arm64
|
||||
dockerfile: Dockerfile
|
||||
build_flag_templates:
|
||||
- "--build-arg"
|
||||
- "ARCH=arm64v8"
|
||||
docker_manifests:
|
||||
- name_template: foo/bar:{{ .Version }}
|
||||
image_templates:
|
||||
- foo/bar:{{ .Version }}-amd64
|
||||
- foo/bar:{{ .Version }}-arm64v8
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Notice that `ARCH` needs to be in the Docker arch format, not Go's.
|
||||
|
||||
That config will build the 2 Docker images defined, as well as the manifest,
|
||||
and push everything to Docker Hub.
|
@ -66,6 +66,7 @@ nav:
|
||||
- customization/checksum.md
|
||||
- customization/publishers.md
|
||||
- customization/docker.md
|
||||
- customization/docker_manifest.md
|
||||
- customization/env.md
|
||||
- customization/hooks.md
|
||||
- customization/homebrew.md
|
||||
|
Loading…
x
Reference in New Issue
Block a user