You've already forked goreleaser
							
							
				mirror of
				https://github.com/goreleaser/goreleaser.git
				synced 2025-10-30 23:58:09 +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:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user