1
0
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:
Carlos Alexandro Becker 2020-11-28 16:26:37 -03:00 committed by GitHub
parent d2f4cb7039
commit e337fc9ca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 560 additions and 81 deletions

View File

@ -11,6 +11,8 @@ on:
jobs:
goreleaser:
runs-on: ubuntu-latest
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
steps:
-
name: Checkout

View File

@ -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:

View File

@ -1,4 +1,5 @@
FROM golang:1.15-alpine
ARG ARCH
FROM ${ARCH}/golang:1.15-alpine
RUN apk add --no-cache bash \
curl \

View File

@ -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 \

View File

@ -0,0 +1,3 @@
// Package docker provides a Pipe that creates and pushes Docker images and
// manifests.
package docker

View File

@ -1,4 +1,3 @@
// Package docker provides a Pipe that creates and pushes a Docker image
package docker
import (

View File

@ -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 {

View 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
}

View File

@ -0,0 +1,3 @@
ARG ARCH
FROM ${ARCH}/alpine
ADD mybin /

View File

@ -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{},

View File

@ -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"`

View 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.

View File

@ -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