You've already forked goreleaser
							
							
				mirror of
				https://github.com/goreleaser/goreleaser.git
				synced 2025-10-30 23:58:09 +02:00 
			
		
		
		
	feat: add ko support (#3653)
continuing the PR by @developer-guy - [x] should be a publisher, as it does publish the images it builds every time - [x] `Default` method does not work - [x] the `fromConfig` thing should probably be on the defaults, too - [x] wire `--skip-ko` - [x] documentation - [x] more tests - [x] use same registry as docker tests does - [ ] see if we can make the log output match goreleaser's - [ ] ?? closes #2556 closes #3490 Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com> Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com> Co-authored-by: actions-user <actions@github.com> Co-authored-by: Jason Hall <jason@chainguard.dev> Co-authored-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							48f77f9ea4
						
					
				
				
					commit
					2450746e5c
				
			| @@ -36,6 +36,7 @@ type releaseOpts struct { | ||||
| 	skipAnnounce       bool | ||||
| 	skipSBOMCataloging bool | ||||
| 	skipDocker         bool | ||||
| 	skipKo             bool | ||||
| 	skipBefore         bool | ||||
| 	rmDist             bool | ||||
| 	deprecated         bool | ||||
| @@ -77,6 +78,7 @@ func newReleaseCmd() *releaseCmd { | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipSign, "skip-sign", false, "Skips signing artifacts") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipSBOMCataloging, "skip-sbom", false, "Skips cataloging artifacts") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipDocker, "skip-docker", false, "Skips Docker Images/Manifests builds") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipKo, "skip-ko", false, "Skips Ko builds") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipBefore, "skip-before", false, "Skips global before hooks") | ||||
| 	cmd.Flags().BoolVar(&root.opts.skipValidate, "skip-validate", false, "Skips git checks") | ||||
| 	cmd.Flags().BoolVar(&root.opts.rmDist, "rm-dist", false, "Removes the dist folder") | ||||
| @@ -137,6 +139,7 @@ func setupReleaseContext(ctx *context.Context, options releaseOpts) { | ||||
| 	ctx.SkipSign = options.skipSign | ||||
| 	ctx.SkipSBOMCataloging = options.skipSBOMCataloging | ||||
| 	ctx.SkipDocker = options.skipDocker | ||||
| 	ctx.SkipKo = options.skipKo | ||||
| 	ctx.SkipBefore = options.skipBefore | ||||
| 	ctx.RmDist = options.rmDist | ||||
|  | ||||
|   | ||||
							
								
								
									
										80
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								go.mod
									
									
									
									
									
								
							| @@ -6,6 +6,7 @@ require ( | ||||
| 	code.gitea.io/sdk/gitea v0.15.1 | ||||
| 	github.com/Masterminds/semver/v3 v3.2.0 | ||||
| 	github.com/atc0005/go-teams-notify/v2 v2.7.0 | ||||
| 	github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 | ||||
| 	github.com/caarlos0/ctrlc v1.2.0 | ||||
| 	github.com/caarlos0/env/v6 v6.10.1 | ||||
| 	github.com/caarlos0/go-reddit/v3 v3.0.1 | ||||
| @@ -13,12 +14,16 @@ require ( | ||||
| 	github.com/caarlos0/log v0.2.1 | ||||
| 	github.com/charmbracelet/keygen v0.3.0 | ||||
| 	github.com/charmbracelet/lipgloss v0.6.0 | ||||
| 	github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 | ||||
| 	github.com/dghubble/go-twitter v0.0.0-20211115160449-93a8679adecb | ||||
| 	github.com/dghubble/oauth1 v0.7.2 | ||||
| 	github.com/disgoorg/disgo v0.14.1 | ||||
| 	github.com/disgoorg/snowflake/v2 v2.0.1 | ||||
| 	github.com/distribution/distribution/v3 v3.0.0-20221021092657-c47a966fded8 | ||||
| 	github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible | ||||
| 	github.com/google/go-containerregistry v0.11.0 | ||||
| 	github.com/google/go-github/v48 v48.2.0 | ||||
| 	github.com/google/ko v0.12.0 | ||||
| 	github.com/google/uuid v1.3.0 | ||||
| 	github.com/goreleaser/fileglob v1.3.0 | ||||
| 	github.com/goreleaser/nfpm/v2 v2.23.0 | ||||
| @@ -44,6 +49,7 @@ require ( | ||||
| 	golang.org/x/oauth2 v0.4.0 | ||||
| 	golang.org/x/sync v0.1.0 | ||||
| 	golang.org/x/text v0.6.0 | ||||
| 	golang.org/x/tools v0.2.0 | ||||
| 	gopkg.in/mail.v2 v2.3.1 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| ) | ||||
| @@ -56,6 +62,7 @@ require ( | ||||
| 	cloud.google.com/go/kms v1.7.0 // indirect | ||||
| 	cloud.google.com/go/storage v1.28.0 // indirect | ||||
| 	github.com/AlekSi/pointer v1.2.0 // indirect | ||||
| 	github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect | ||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0 // indirect | ||||
| 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 // indirect | ||||
| 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect | ||||
| @@ -64,8 +71,16 @@ require ( | ||||
| 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.6.1 // indirect | ||||
| 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect | ||||
| 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect | ||||
| 	github.com/Azure/go-autorest/autorest v0.11.28 // indirect | ||||
| 	github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect | ||||
| 	github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect | ||||
| 	github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect | ||||
| 	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect | ||||
| 	github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect | ||||
| 	github.com/Azure/go-autorest/logger v0.2.1 // indirect | ||||
| 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect | ||||
| 	github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect | ||||
| 	github.com/BurntSushi/toml v1.1.0 // indirect | ||||
| 	github.com/Masterminds/goutils v1.1.1 // indirect | ||||
| 	github.com/Masterminds/semver v1.5.0 // indirect | ||||
| 	github.com/Masterminds/sprig v2.22.0+incompatible // indirect | ||||
| @@ -73,6 +88,8 @@ require ( | ||||
| 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect | ||||
| 	github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c // indirect | ||||
| 	github.com/acomagu/bufpipe v1.0.3 // indirect | ||||
| 	github.com/alessio/shellescape v1.4.1 // indirect | ||||
| 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect | ||||
| 	github.com/aws/aws-sdk-go v1.44.151 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2 v1.17.1 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.9 // indirect | ||||
| @@ -84,6 +101,8 @@ require ( | ||||
| 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.19 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/internal/ini v1.3.26 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.16 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/ecr v1.17.5 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.5 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.10 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.20 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.19 // indirect | ||||
| @@ -95,24 +114,43 @@ require ( | ||||
| 	github.com/aws/aws-sdk-go-v2/service/sts v1.17.5 // indirect | ||||
| 	github.com/aws/smithy-go v1.13.4 // indirect | ||||
| 	github.com/aymanbagabas/go-osc52 v1.2.1 // indirect | ||||
| 	github.com/beorn7/perks v1.0.1 // indirect | ||||
| 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect | ||||
| 	github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3 // indirect | ||||
| 	github.com/cavaliergopher/cpio v1.0.1 // indirect | ||||
| 	github.com/cenkalti/backoff/v4 v4.1.3 // indirect | ||||
| 	github.com/cespare/xxhash/v2 v2.1.2 // indirect | ||||
| 	github.com/containerd/continuity v0.3.0 // indirect | ||||
| 	github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/dghubble/sling v1.4.0 // indirect | ||||
| 	github.com/dimchansky/utfbom v1.1.1 // indirect | ||||
| 	github.com/disgoorg/json v1.0.0 // indirect | ||||
| 	github.com/disgoorg/log v1.2.0 // indirect | ||||
| 	github.com/docker/cli v20.10.14+incompatible // indirect | ||||
| 	github.com/docker/cli v20.10.17+incompatible // indirect | ||||
| 	github.com/docker/distribution v2.8.1+incompatible // indirect | ||||
| 	github.com/docker/docker v20.10.21+incompatible // indirect | ||||
| 	github.com/docker/docker-credential-helpers v0.6.4 // indirect | ||||
| 	github.com/docker/go-connections v0.4.0 // indirect | ||||
| 	github.com/docker/go-metrics v0.0.1 // indirect | ||||
| 	github.com/docker/go-units v0.5.0 // indirect | ||||
| 	github.com/emirpasic/gods v1.12.0 // indirect | ||||
| 	github.com/evanphx/json-patch/v5 v5.6.0 // indirect | ||||
| 	github.com/fsnotify/fsnotify v1.6.0 // indirect | ||||
| 	github.com/go-git/gcfg v1.5.0 // indirect | ||||
| 	github.com/go-git/go-billy/v5 v5.3.1 // indirect | ||||
| 	github.com/go-git/go-git/v5 v5.4.2 // indirect | ||||
| 	github.com/go-openapi/analysis v0.21.4 // indirect | ||||
| 	github.com/go-openapi/errors v0.20.3 // indirect | ||||
| 	github.com/go-openapi/jsonpointer v0.19.5 // indirect | ||||
| 	github.com/go-openapi/jsonreference v0.20.0 // indirect | ||||
| 	github.com/go-openapi/loads v0.21.2 // indirect | ||||
| 	github.com/go-openapi/runtime v0.24.2 // indirect | ||||
| 	github.com/go-openapi/spec v0.20.7 // indirect | ||||
| 	github.com/go-openapi/strfmt v0.21.3 // indirect | ||||
| 	github.com/go-openapi/swag v0.22.3 // indirect | ||||
| 	github.com/go-openapi/validate v0.22.0 // indirect | ||||
| 	github.com/gobwas/glob v0.2.3 // indirect | ||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | ||||
| 	github.com/golang-jwt/jwt/v4 v4.4.3 // indirect | ||||
| @@ -125,22 +163,29 @@ require ( | ||||
| 	github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect | ||||
| 	github.com/googleapis/gax-go/v2 v2.7.0 // indirect | ||||
| 	github.com/goreleaser/chglog v0.2.2 // indirect | ||||
| 	github.com/gorilla/mux v1.8.0 // indirect | ||||
| 	github.com/gorilla/websocket v1.5.0 // indirect | ||||
| 	github.com/hashicorp/errwrap v1.1.0 // indirect | ||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||
| 	github.com/hashicorp/go-retryablehttp v0.7.1 // indirect | ||||
| 	github.com/hashicorp/go-version v1.2.1 // indirect | ||||
| 	github.com/hashicorp/go-version v1.6.0 // indirect | ||||
| 	github.com/hashicorp/hcl v1.0.0 // indirect | ||||
| 	github.com/huandu/xstrings v1.3.2 // indirect | ||||
| 	github.com/iancoleman/orderedmap v0.2.0 // indirect | ||||
| 	github.com/inconshreveable/mousetrap v1.0.1 // indirect | ||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||
| 	github.com/jmespath/go-jmespath v0.4.0 // indirect | ||||
| 	github.com/josharian/intern v1.0.0 // indirect | ||||
| 	github.com/kevinburke/ssh_config v1.1.0 // indirect | ||||
| 	github.com/klauspost/compress v1.15.13 // indirect | ||||
| 	github.com/kylelemons/godebug v1.1.0 // indirect | ||||
| 	github.com/letsencrypt/boulder v0.0.0-20220929215747-76583552c2be // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||
| 	github.com/magiconair/properties v1.8.6 // indirect | ||||
| 	github.com/mailru/easyjson v0.7.7 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.16 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.14 // indirect | ||||
| 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect | ||||
| 	github.com/mitchellh/copystructure v1.2.0 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||
| 	github.com/mitchellh/reflectwalk v1.0.2 // indirect | ||||
| @@ -148,28 +193,49 @@ require ( | ||||
| 	github.com/muesli/mango v0.1.0 // indirect | ||||
| 	github.com/muesli/mango-pflag v0.1.0 // indirect | ||||
| 	github.com/muesli/reflow v0.3.0 // indirect | ||||
| 	github.com/oklog/ulid v1.3.1 // indirect | ||||
| 	github.com/opencontainers/go-digest v1.0.0 // indirect | ||||
| 	github.com/opencontainers/image-spec v1.0.2 // indirect | ||||
| 	github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect | ||||
| 	github.com/opencontainers/runc v1.1.2 // indirect | ||||
| 	github.com/pelletier/go-toml v1.9.5 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.0.5 // indirect | ||||
| 	github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect | ||||
| 	github.com/pkg/errors v0.9.1 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| 	github.com/prometheus/client_golang v1.13.1 // indirect | ||||
| 	github.com/prometheus/client_model v0.3.0 // indirect | ||||
| 	github.com/prometheus/common v0.37.0 // indirect | ||||
| 	github.com/prometheus/procfs v0.8.0 // indirect | ||||
| 	github.com/rivo/uniseg v0.4.2 // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b // indirect | ||||
| 	github.com/sergi/go-diff v1.2.0 // indirect | ||||
| 	github.com/sirupsen/logrus v1.8.1 // indirect | ||||
| 	github.com/sigstore/cosign v1.13.1 // indirect | ||||
| 	github.com/sigstore/rekor v0.12.1-0.20220915152154-4bb6f441c1b2 // indirect | ||||
| 	github.com/sigstore/sigstore v1.4.4 // indirect | ||||
| 	github.com/sirupsen/logrus v1.9.0 // indirect | ||||
| 	github.com/spf13/afero v1.9.2 // indirect | ||||
| 	github.com/spf13/cast v1.5.0 // indirect | ||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| 	github.com/spf13/viper v1.13.0 // indirect | ||||
| 	github.com/subosito/gotenv v1.4.1 // indirect | ||||
| 	github.com/technoweenie/multipartstreamer v1.0.1 // indirect | ||||
| 	github.com/theupdateframework/go-tuf v0.5.2-0.20220930112810-3890c1e7ace4 // indirect | ||||
| 	github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect | ||||
| 	github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect | ||||
| 	github.com/vbatts/tar-split v0.11.2 // indirect | ||||
| 	github.com/xanzy/ssh-agent v0.3.1 // indirect | ||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect | ||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | ||||
| 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | ||||
| 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | ||||
| 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect | ||||
| 	go.mongodb.org/mongo-driver v1.10.2 // indirect | ||||
| 	go.opencensus.io v0.24.0 // indirect | ||||
| 	golang.org/x/mod v0.6.0 // indirect | ||||
| 	golang.org/x/net v0.5.0 // indirect | ||||
| 	golang.org/x/sys v0.4.0 // indirect | ||||
| 	golang.org/x/term v0.4.0 // indirect | ||||
| 	golang.org/x/time v0.3.0 // indirect | ||||
| 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect | ||||
| 	google.golang.org/api v0.103.0 // indirect | ||||
| @@ -178,6 +244,10 @@ require ( | ||||
| 	google.golang.org/grpc v1.51.0 // indirect | ||||
| 	google.golang.org/protobuf v1.28.1 // indirect | ||||
| 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect | ||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||
| 	gopkg.in/square/go-jose.v2 v2.6.0 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||
| 	sigs.k8s.io/kind v0.14.0 // indirect | ||||
| 	sigs.k8s.io/yaml v1.3.0 // indirect | ||||
| ) | ||||
|   | ||||
| @@ -13,8 +13,6 @@ import ( | ||||
| 	"github.com/goreleaser/goreleaser/internal/testlib" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/config" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/context" | ||||
| 	"github.com/ory/dockertest/v3" | ||||
| 	"github.com/ory/dockertest/v3/docker" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| @@ -28,30 +26,8 @@ const ( | ||||
| func start(tb testing.TB) { | ||||
| 	tb.Helper() | ||||
| 	tb.Log("starting registries") | ||||
| 	startRegistry(tb, "registry", registryPort) | ||||
| 	startRegistry(tb, "alt_registry", altRegistryPort) | ||||
| } | ||||
|  | ||||
| func startRegistry(tb testing.TB, name, port string) { | ||||
| 	tb.Helper() | ||||
|  | ||||
| 	pool := testlib.MustDockerPool(tb) | ||||
| 	testlib.MustKillContainer(tb, name) | ||||
| 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{ | ||||
| 		Name:       name, | ||||
| 		Repository: "registry", | ||||
| 		Tag:        "2", | ||||
| 		PortBindings: map[docker.Port][]docker.PortBinding{ | ||||
| 			docker.Port("5000/tcp"): {{HostPort: port}}, | ||||
| 		}, | ||||
| 	}, func(hc *docker.HostConfig) { | ||||
| 		hc.AutoRemove = true | ||||
| 	}) | ||||
| 	require.NoError(tb, err) | ||||
|  | ||||
| 	tb.Cleanup(func() { | ||||
| 		require.NoError(tb, pool.Purge(resource)) | ||||
| 	}) | ||||
| 	testlib.StartRegistry(tb, "registry", registryPort) | ||||
| 	testlib.StartRegistry(tb, "alt_registry", altRegistryPort) | ||||
| } | ||||
|  | ||||
| // TODO: this test is too big... split in smaller tests? Mainly the manifest ones... | ||||
|   | ||||
							
								
								
									
										350
									
								
								internal/pipe/ko/ko.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										350
									
								
								internal/pipe/ko/ko.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,350 @@ | ||||
| // Package ko implements the pipe interface with the intent of | ||||
| // building OCI compliant images with ko. | ||||
| package ko | ||||
|  | ||||
| import ( | ||||
| 	stdctx "context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"path/filepath" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/awslabs/amazon-ecr-credential-helper/ecr-login" | ||||
| 	"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper" | ||||
| 	"github.com/google/go-containerregistry/pkg/authn" | ||||
| 	"github.com/google/go-containerregistry/pkg/authn/github" | ||||
| 	"github.com/google/go-containerregistry/pkg/name" | ||||
| 	"github.com/google/go-containerregistry/pkg/v1/google" | ||||
| 	"github.com/google/go-containerregistry/pkg/v1/remote" | ||||
| 	"github.com/google/ko/pkg/build" | ||||
| 	"github.com/google/ko/pkg/commands/options" | ||||
| 	"github.com/google/ko/pkg/publish" | ||||
| 	"github.com/goreleaser/goreleaser/internal/ids" | ||||
| 	"github.com/goreleaser/goreleaser/internal/semerrgroup" | ||||
| 	"github.com/goreleaser/goreleaser/internal/tmpl" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/config" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/context" | ||||
| 	"golang.org/x/tools/go/packages" | ||||
| ) | ||||
|  | ||||
| const chainguardStatic = "cgr.dev/chainguard/static" | ||||
|  | ||||
| var ( | ||||
| 	baseImages     sync.Map | ||||
| 	amazonKeychain authn.Keychain = authn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(io.Discard))) | ||||
| 	azureKeychain  authn.Keychain = authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper()) | ||||
| 	keychain                      = authn.NewMultiKeychain( | ||||
| 		amazonKeychain, | ||||
| 		authn.DefaultKeychain, | ||||
| 		google.Keychain, | ||||
| 		github.Keychain, | ||||
| 		azureKeychain, | ||||
| 	) | ||||
|  | ||||
| 	errNoRepository = errors.New("ko: missing repository: please set either the repository field or a $KO_DOCKER_REPO environment variable") | ||||
| ) | ||||
|  | ||||
| // Pipe that build OCI compliant images with ko. | ||||
| type Pipe struct{} | ||||
|  | ||||
| func (Pipe) String() string { return "ko" } | ||||
| func (Pipe) Skip(ctx *context.Context) bool { | ||||
| 	return ctx.SkipKo || len(ctx.Config.Kos) == 0 | ||||
| } | ||||
|  | ||||
| // Default sets the Pipes defaults. | ||||
| func (Pipe) Default(ctx *context.Context) error { | ||||
| 	ids := ids.New("kos") | ||||
| 	for i := range ctx.Config.Kos { | ||||
| 		ko := &ctx.Config.Kos[i] | ||||
| 		if ko.ID == "" { | ||||
| 			ko.ID = ctx.Config.ProjectName | ||||
| 		} | ||||
|  | ||||
| 		if ko.Build == "" { | ||||
| 			ko.Build = ko.ID | ||||
| 		} | ||||
|  | ||||
| 		build, err := findBuild(ctx, *ko) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		if len(ko.Ldflags) == 0 { | ||||
| 			ko.Ldflags = build.Ldflags | ||||
| 		} | ||||
|  | ||||
| 		if len(ko.Flags) == 0 { | ||||
| 			ko.Flags = build.Flags | ||||
| 		} | ||||
|  | ||||
| 		if len(ko.Env) == 0 { | ||||
| 			ko.Env = build.Env | ||||
| 		} | ||||
|  | ||||
| 		if ko.Main == "" { | ||||
| 			ko.Main = build.Main | ||||
| 		} | ||||
|  | ||||
| 		if ko.WorkingDir == "" { | ||||
| 			ko.WorkingDir = build.Dir | ||||
| 		} | ||||
|  | ||||
| 		if ko.BaseImage == "" { | ||||
| 			ko.BaseImage = chainguardStatic | ||||
| 		} | ||||
|  | ||||
| 		if len(ko.Platforms) == 0 { | ||||
| 			ko.Platforms = []string{"linux/amd64"} | ||||
| 		} | ||||
|  | ||||
| 		if len(ko.Tags) == 0 { | ||||
| 			ko.Tags = []string{"latest"} | ||||
| 		} | ||||
|  | ||||
| 		if ko.SBOM == "" { | ||||
| 			ko.SBOM = "spdx" | ||||
| 		} | ||||
|  | ||||
| 		if repo := ctx.Env["KO_DOCKER_REPO"]; repo != "" { | ||||
| 			ko.Repository = repo | ||||
| 			ko.RepositoryFromEnv = true | ||||
| 		} | ||||
|  | ||||
| 		if ko.Repository == "" { | ||||
| 			return errNoRepository | ||||
| 		} | ||||
|  | ||||
| 		ids.Inc(ko.ID) | ||||
| 	} | ||||
| 	return ids.Validate() | ||||
| } | ||||
|  | ||||
| // Publish executes the Pipe. | ||||
| func (Pipe) Publish(ctx *context.Context) error { | ||||
| 	g := semerrgroup.New(ctx.Parallelism) | ||||
| 	for _, ko := range ctx.Config.Kos { | ||||
| 		g.Go(doBuild(ctx, ko)) | ||||
| 	} | ||||
| 	return g.Wait() | ||||
| } | ||||
|  | ||||
| type buildOptions struct { | ||||
| 	importPath          string | ||||
| 	main                string | ||||
| 	flags               []string | ||||
| 	env                 []string | ||||
| 	imageRepo           string | ||||
| 	fromEnv             bool | ||||
| 	workingDir          string | ||||
| 	platforms           []string | ||||
| 	baseImage           string | ||||
| 	tags                []string | ||||
| 	sbom                string | ||||
| 	ldflags             []string | ||||
| 	bare                bool | ||||
| 	preserveImportPaths bool | ||||
| 	baseImportPaths     bool | ||||
| } | ||||
|  | ||||
| func (o *buildOptions) makeBuilder(ctx *context.Context) (*build.Caching, error) { | ||||
| 	buildOptions := []build.Option{ | ||||
| 		build.WithConfig(map[string]build.Config{ | ||||
| 			o.importPath: { | ||||
| 				Ldflags: o.ldflags, | ||||
| 				Flags:   o.flags, | ||||
| 				Main:    o.main, | ||||
| 				Env:     o.env, | ||||
| 			}, | ||||
| 		}), | ||||
| 		build.WithPlatforms(o.platforms...), | ||||
| 		build.WithBaseImages(func(ctx stdctx.Context, s string) (name.Reference, build.Result, error) { | ||||
| 			ref, err := name.ParseReference(o.baseImage) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
|  | ||||
| 			if cached, found := baseImages.Load(o.baseImage); found { | ||||
| 				return ref, cached.(build.Result), nil | ||||
| 			} | ||||
|  | ||||
| 			desc, err := remote.Get( | ||||
| 				ref, | ||||
| 				remote.WithAuthFromKeychain(keychain), | ||||
| 			) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 			if desc.MediaType.IsImage() { | ||||
| 				img, err := desc.Image() | ||||
| 				baseImages.Store(o.baseImage, img) | ||||
| 				return ref, img, err | ||||
| 			} | ||||
| 			if desc.MediaType.IsIndex() { | ||||
| 				idx, err := desc.ImageIndex() | ||||
| 				baseImages.Store(o.baseImage, idx) | ||||
| 				return ref, idx, err | ||||
| 			} | ||||
| 			return nil, nil, fmt.Errorf("unexpected base image media type: %s", desc.MediaType) | ||||
| 		}), | ||||
| 	} | ||||
| 	switch o.sbom { | ||||
| 	case "spdx": | ||||
| 		buildOptions = append(buildOptions, build.WithSPDX("devel")) | ||||
| 	case "cyclonedx": | ||||
| 		buildOptions = append(buildOptions, build.WithCycloneDX()) | ||||
| 	case "go.version-m": | ||||
| 		buildOptions = append(buildOptions, build.WithGoVersionSBOM()) | ||||
| 	case "none": | ||||
| 		// don't do anything. | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unknown sbom type: %q", o.sbom) | ||||
| 	} | ||||
|  | ||||
| 	b, err := build.NewGo(ctx, o.workingDir, buildOptions...) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("newGo: %w", err) | ||||
| 	} | ||||
| 	return build.NewCaching(b) | ||||
| } | ||||
|  | ||||
| func doBuild(ctx *context.Context, ko config.Ko) func() error { | ||||
| 	return func() error { | ||||
| 		opts, err := buildBuildOptions(ctx, ko) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		b, err := opts.makeBuilder(ctx) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("makeBuilder: %w", err) | ||||
| 		} | ||||
| 		r, err := b.Build(ctx, opts.importPath) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("build: %w", err) | ||||
| 		} | ||||
|  | ||||
| 		po := []publish.Option{publish.WithTags(opts.tags), publish.WithAuthFromKeychain(keychain)} | ||||
|  | ||||
| 		var repo string | ||||
| 		if opts.fromEnv { | ||||
| 			repo = opts.imageRepo | ||||
| 		} else { | ||||
| 			// image resource's `repo` takes precedence if set, and selects the | ||||
| 			// `--bare` namer so the image is named exactly `repo`. | ||||
| 			repo = opts.imageRepo | ||||
| 			po = append(po, publish.WithNamer(options.MakeNamer(&options.PublishOptions{ | ||||
| 				DockerRepo:          opts.imageRepo, | ||||
| 				Bare:                opts.bare, | ||||
| 				PreserveImportPaths: opts.preserveImportPaths, | ||||
| 				BaseImportPaths:     opts.baseImportPaths, | ||||
| 			}))) | ||||
| 		} | ||||
|  | ||||
| 		p, err := publish.NewDefault(repo, po...) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("newDefault: %w", err) | ||||
| 		} | ||||
| 		defer func() { _ = p.Close() }() | ||||
| 		if _, err = p.Publish(ctx, r, opts.importPath); err != nil { | ||||
| 			return fmt.Errorf("publish: %w", err) | ||||
| 		} | ||||
| 		if err := p.Close(); err != nil { | ||||
| 			return fmt.Errorf("close: %w", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func findBuild(ctx *context.Context, ko config.Ko) (config.Build, error) { | ||||
| 	for _, build := range ctx.Config.Builds { | ||||
| 		if build.ID == ko.Build { | ||||
| 			return build, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return config.Build{}, fmt.Errorf("no builds with id %q", ko.Build) | ||||
| } | ||||
|  | ||||
| func buildBuildOptions(ctx *context.Context, cfg config.Ko) (*buildOptions, error) { | ||||
| 	localImportPath := cfg.Main | ||||
|  | ||||
| 	dir := filepath.Clean(cfg.WorkingDir) | ||||
| 	if dir == "." { | ||||
| 		dir = "" | ||||
| 	} | ||||
|  | ||||
| 	pkgs, err := packages.Load(&packages.Config{ | ||||
| 		Mode: packages.NeedName, | ||||
| 		Dir:  dir, | ||||
| 	}, localImportPath) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf( | ||||
| 			"ko: %s does not contain a valid local import path (%s) for directory (%s): %w", | ||||
| 			cfg.ID, localImportPath, cfg.WorkingDir, err, | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	if len(pkgs) != 1 { | ||||
| 		return nil, fmt.Errorf( | ||||
| 			"ko: %s results in %d local packages, only 1 is expected", | ||||
| 			cfg.ID, len(pkgs), | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	opts := &buildOptions{ | ||||
| 		importPath:          pkgs[0].PkgPath, | ||||
| 		workingDir:          cfg.WorkingDir, | ||||
| 		bare:                cfg.Bare, | ||||
| 		preserveImportPaths: cfg.PreserveImportPaths, | ||||
| 		baseImportPaths:     cfg.BaseImportPaths, | ||||
| 		baseImage:           cfg.BaseImage, | ||||
| 		platforms:           cfg.Platforms, | ||||
| 		sbom:                cfg.SBOM, | ||||
| 		imageRepo:           cfg.Repository, | ||||
| 		fromEnv:             cfg.RepositoryFromEnv, | ||||
| 	} | ||||
|  | ||||
| 	tags, err := applyTemplate(ctx, cfg.Tags) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	opts.tags = tags | ||||
|  | ||||
| 	if len(cfg.Env) > 0 { | ||||
| 		env, err := applyTemplate(ctx, cfg.Env) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		opts.env = env | ||||
| 	} | ||||
|  | ||||
| 	if len(cfg.Flags) > 0 { | ||||
| 		flags, err := applyTemplate(ctx, cfg.Flags) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		opts.flags = flags | ||||
| 	} | ||||
|  | ||||
| 	if len(cfg.Ldflags) > 0 { | ||||
| 		ldflags, err := applyTemplate(ctx, cfg.Ldflags) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		opts.ldflags = ldflags | ||||
| 	} | ||||
| 	return opts, nil | ||||
| } | ||||
|  | ||||
| func applyTemplate(ctx *context.Context, templateable []string) ([]string, error) { | ||||
| 	var templated []string | ||||
| 	for _, t := range templateable { | ||||
| 		tlf, err := tmpl.New(ctx).Apply(t) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		templated = append(templated, tlf) | ||||
| 	} | ||||
| 	return templated, nil | ||||
| } | ||||
							
								
								
									
										280
									
								
								internal/pipe/ko/ko_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										280
									
								
								internal/pipe/ko/ko_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,280 @@ | ||||
| package ko | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
|  | ||||
| 	_ "github.com/distribution/distribution/v3/registry/auth/htpasswd" | ||||
| 	_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" | ||||
| 	"github.com/goreleaser/goreleaser/internal/testlib" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/config" | ||||
| 	"github.com/goreleaser/goreleaser/pkg/context" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	registryPort = "5052" | ||||
| 	registry     = "localhost:5052/" | ||||
| ) | ||||
|  | ||||
| func TestDefault(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{ | ||||
| 		Env: []string{ | ||||
| 			"KO_DOCKER_REPO=" + registry, | ||||
| 			"COSIGN_REPOSITORY=" + registry, | ||||
| 			"LDFLAGS=foobar", | ||||
| 			"FLAGS=barfoo", | ||||
| 			"LE_ENV=test", | ||||
| 		}, | ||||
| 		ProjectName: "test", | ||||
| 		Builds: []config.Build{ | ||||
| 			{ | ||||
| 				ID:  "test", | ||||
| 				Dir: ".", | ||||
| 				BuildDetails: config.BuildDetails{ | ||||
| 					Ldflags: []string{"{{.Env.LDFLAGS}}"}, | ||||
| 					Flags:   []string{"{{.Env.FLAGS}}"}, | ||||
| 					Env:     []string{"SOME_ENV={{.Env.LE_ENV}}"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Kos: []config.Ko{ | ||||
| 			{}, | ||||
| 		}, | ||||
| 	}) | ||||
| 	require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 	require.Equal(t, config.Ko{ | ||||
| 		ID:                "test", | ||||
| 		Build:             "test", | ||||
| 		BaseImage:         chainguardStatic, | ||||
| 		Repository:        registry, | ||||
| 		RepositoryFromEnv: true, | ||||
| 		Platforms:         []string{"linux/amd64"}, | ||||
| 		SBOM:              "spdx", | ||||
| 		Tags:              []string{"latest"}, | ||||
| 		WorkingDir:        ".", | ||||
| 		Ldflags:           []string{"{{.Env.LDFLAGS}}"}, | ||||
| 		Flags:             []string{"{{.Env.FLAGS}}"}, | ||||
| 		Env:               []string{"SOME_ENV={{.Env.LE_ENV}}"}, | ||||
| 	}, ctx.Config.Kos[0]) | ||||
| } | ||||
|  | ||||
| func TestDefaultNoImage(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{ | ||||
| 		ProjectName: "test", | ||||
| 		Builds: []config.Build{ | ||||
| 			{ | ||||
| 				ID: "test", | ||||
| 			}, | ||||
| 		}, | ||||
| 		Kos: []config.Ko{ | ||||
| 			{}, | ||||
| 		}, | ||||
| 	}) | ||||
| 	require.ErrorIs(t, Pipe{}.Default(ctx), errNoRepository) | ||||
| } | ||||
|  | ||||
| func TestDescription(t *testing.T) { | ||||
| 	require.NotEmpty(t, Pipe{}.String()) | ||||
| } | ||||
|  | ||||
| func TestSkip(t *testing.T) { | ||||
| 	t.Run("skip ko set", func(t *testing.T) { | ||||
| 		ctx := context.New(config.Project{ | ||||
| 			Kos: []config.Ko{{}}, | ||||
| 		}) | ||||
| 		ctx.SkipKo = true | ||||
| 		require.True(t, Pipe{}.Skip(ctx)) | ||||
| 	}) | ||||
| 	t.Run("skip no kos", func(t *testing.T) { | ||||
| 		ctx := context.New(config.Project{}) | ||||
| 		require.True(t, Pipe{}.Skip(ctx)) | ||||
| 	}) | ||||
| 	t.Run("dont skip", func(t *testing.T) { | ||||
| 		ctx := context.New(config.Project{ | ||||
| 			Kos: []config.Ko{{}}, | ||||
| 		}) | ||||
| 		require.False(t, Pipe{}.Skip(ctx)) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestPublishPipeNoMatchingBuild(t *testing.T) { | ||||
| 	ctx := context.New(config.Project{ | ||||
| 		Builds: []config.Build{ | ||||
| 			{ | ||||
| 				ID: "doesnt matter", | ||||
| 			}, | ||||
| 		}, | ||||
| 		Kos: []config.Ko{ | ||||
| 			{ | ||||
| 				ID:    "default", | ||||
| 				Build: "wont match nothing", | ||||
| 			}, | ||||
| 		}, | ||||
| 	}) | ||||
|  | ||||
| 	require.EqualError(t, Pipe{}.Default(ctx), `no builds with id "wont match nothing"`) | ||||
| } | ||||
|  | ||||
| func TestPublishPipeSuccess(t *testing.T) { | ||||
| 	testlib.StartRegistry(t, "ko_registry", registryPort) | ||||
|  | ||||
| 	table := []struct { | ||||
| 		Name      string | ||||
| 		SBOM      string | ||||
| 		BaseImage string | ||||
| 		Platforms []string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			Name: "sbom-spdx", | ||||
| 			SBOM: "spdx", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "sbom-none", | ||||
| 			SBOM: "none", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "sbom-cyclonedx", | ||||
| 			SBOM: "cyclonedx", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name: "sbom-go.version-m", | ||||
| 			SBOM: "go.version-m", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:      "base-image-is-not-index", | ||||
| 			BaseImage: "alpine:latest@sha256:c0d488a800e4127c334ad20d61d7bc21b4097540327217dfab52262adc02380c", | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:      "multiple-platforms", | ||||
| 			Platforms: []string{"linux/amd64", "linux/arm64"}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range table { | ||||
| 		t.Run(table.Name, func(t *testing.T) { | ||||
| 			ctx := context.New(config.Project{ | ||||
| 				Builds: []config.Build{ | ||||
| 					{ | ||||
| 						ID: "foo", | ||||
| 						BuildDetails: config.BuildDetails{ | ||||
| 							Ldflags: []string{"-s", "-w"}, | ||||
| 							Flags:   []string{"-tags", "netgo"}, | ||||
| 							Env:     []string{"GOCACHE=" + t.TempDir()}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				Kos: []config.Ko{ | ||||
| 					{ | ||||
| 						ID:         "default", | ||||
| 						Build:      "foo", | ||||
| 						WorkingDir: "./testdata/app/", | ||||
| 						BaseImage:  table.BaseImage, | ||||
| 						Repository: fmt.Sprintf("%s/goreleasertest", registry), | ||||
| 						Platforms:  table.Platforms, | ||||
| 						Tags:       []string{table.Name}, | ||||
| 						SBOM:       table.SBOM, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}) | ||||
|  | ||||
| 			require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 			require.NoError(t, Pipe{}.Publish(ctx)) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPublishPipeError(t *testing.T) { | ||||
| 	makeCtx := func() *context.Context { | ||||
| 		ctx := context.New(config.Project{ | ||||
| 			Builds: []config.Build{ | ||||
| 				{ | ||||
| 					ID:   "foo", | ||||
| 					Main: "./...", | ||||
| 				}, | ||||
| 			}, | ||||
| 			Kos: []config.Ko{ | ||||
| 				{ | ||||
| 					ID:         "default", | ||||
| 					Build:      "foo", | ||||
| 					WorkingDir: "./testdata/app/", | ||||
| 					Repository: "fakerepo:8080/", | ||||
| 					Tags:       []string{"latest", "{{.Tag}}"}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}) | ||||
| 		ctx.Git.CurrentTag = "v1.0.0" | ||||
| 		return ctx | ||||
| 	} | ||||
|  | ||||
| 	t.Run("invalid base image", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Kos[0].BaseImage = "not a valid image hopefully" | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		require.EqualError(t, Pipe{}.Publish(ctx), `build: could not parse reference: not a valid image hopefully`) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invalid sbom", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Kos[0].SBOM = "nope" | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		require.EqualError(t, Pipe{}.Publish(ctx), `makeBuilder: unknown sbom type: "nope"`) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invalid build", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Kos[0].WorkingDir = t.TempDir() | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		require.EqualError(t, Pipe{}.Publish(ctx), `build: exit status 1`) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invalid tags tmpl", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Kos[0].Tags = []string{"{{.Nope}}"} | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		testlib.RequireTemplateError(t, Pipe{}.Publish(ctx)) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invalid env tmpl", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Builds[0].Env = []string{"{{.Nope}}"} | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		testlib.RequireTemplateError(t, Pipe{}.Publish(ctx)) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invalid ldflags tmpl", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Builds[0].Ldflags = []string{"{{.Nope}}"} | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		testlib.RequireTemplateError(t, Pipe{}.Publish(ctx)) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("invalid flags tmpl", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		ctx.Config.Builds[0].Flags = []string{"{{.Nope}}"} | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		testlib.RequireTemplateError(t, Pipe{}.Publish(ctx)) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("publish fail", func(t *testing.T) { | ||||
| 		ctx := makeCtx() | ||||
| 		require.NoError(t, Pipe{}.Default(ctx)) | ||||
| 		err := Pipe{}.Publish(ctx) | ||||
| 		require.Error(t, err) | ||||
| 		require.Contains(t, err.Error(), `publish: writing sbom: Get "https://fakerepo:8080/v2/": dial tcp:`) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func TestApplyTemplate(t *testing.T) { | ||||
| 	t.Run("success", func(t *testing.T) { | ||||
| 		foo, err := applyTemplate(context.New(config.Project{ | ||||
| 			Env: []string{"FOO=bar"}, | ||||
| 		}), []string{"{{ .Env.FOO }}"}) | ||||
| 		require.NoError(t, err) | ||||
| 		require.Equal(t, []string{"bar"}, foo) | ||||
| 	}) | ||||
| 	t.Run("error", func(t *testing.T) { | ||||
| 		_, err := applyTemplate(context.New(config.Project{}), []string{"{{ .Nope}}"}) | ||||
| 		require.Error(t, err) | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										3
									
								
								internal/pipe/ko/testdata/app/go.mod
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								internal/pipe/ko/testdata/app/go.mod
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| module testapp | ||||
|  | ||||
| go 1.19 | ||||
							
								
								
									
										5
									
								
								internal/pipe/ko/testdata/app/main.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								internal/pipe/ko/testdata/app/main.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| package main | ||||
|  | ||||
| func main() { | ||||
| 	println("hello") | ||||
| } | ||||
| @@ -14,6 +14,7 @@ import ( | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/chocolatey" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/custompublishers" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/docker" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/ko" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/krew" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/milestone" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/release" | ||||
| @@ -40,6 +41,7 @@ var publishers = []Publisher{ | ||||
| 	custompublishers.Pipe{}, | ||||
| 	docker.Pipe{}, | ||||
| 	docker.ManifestPipe{}, | ||||
| 	ko.Pipe{}, | ||||
| 	sign.DockerPipe{}, | ||||
| 	snapcraft.Pipe{}, | ||||
| 	// This should be one of the last steps | ||||
|   | ||||
| @@ -2,8 +2,11 @@ package testlib | ||||
|  | ||||
| import ( | ||||
| 	"sync" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/ory/dockertest/v3" | ||||
| 	"github.com/ory/dockertest/v3/docker" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| @@ -41,3 +44,27 @@ func MustKillContainer(f Fataler, name string) { | ||||
| type Fataler interface { | ||||
| 	Fatal(args ...any) | ||||
| } | ||||
|  | ||||
| // StartRegistry starts a registry with the given name in the given port, and | ||||
| // sets up its deletion on test.Cleanup. | ||||
| func StartRegistry(tb testing.TB, name, port string) { | ||||
| 	tb.Helper() | ||||
|  | ||||
| 	pool := MustDockerPool(tb) | ||||
| 	MustKillContainer(tb, name) | ||||
| 	resource, err := pool.RunWithOptions(&dockertest.RunOptions{ | ||||
| 		Name:       name, | ||||
| 		Repository: "registry", | ||||
| 		Tag:        "2", | ||||
| 		PortBindings: map[docker.Port][]docker.PortBinding{ | ||||
| 			docker.Port("5000/tcp"): {{HostPort: port}}, | ||||
| 		}, | ||||
| 	}, func(hc *docker.HostConfig) { | ||||
| 		hc.AutoRemove = true | ||||
| 	}) | ||||
| 	require.NoError(tb, err) | ||||
|  | ||||
| 	tb.Cleanup(func() { | ||||
| 		require.NoError(tb, pool.Purge(resource)) | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -199,6 +199,26 @@ type Krew struct { | ||||
| 	SkipUpload            string       `yaml:"skip_upload,omitempty" json:"skip_upload,omitempty" jsonschema:"oneof_type=string;boolean"` | ||||
| } | ||||
|  | ||||
| // Ko contains the ko section | ||||
| type Ko struct { | ||||
| 	ID                  string   `yaml:"id,omitempty" json:"id,omitempty"` | ||||
| 	Build               string   `yaml:"build,omitempty" json:"build,omitempty"` | ||||
| 	Main                string   `yaml:"main,omitempty" json:"main,omitempty"` | ||||
| 	WorkingDir          string   `yaml:"working_dir,omitempty" json:"working_dir,omitempty"` | ||||
| 	BaseImage           string   `yaml:"base_image,omitempty" json:"base_image,omitempty"` | ||||
| 	Repository          string   `yaml:"repository,omitempty" json:"repository,omitempty"` | ||||
| 	RepositoryFromEnv   bool     `yaml:"-" json:"-"` | ||||
| 	Platforms           []string `yaml:"platforms,omitempty" json:"platforms,omitempty"` | ||||
| 	Tags                []string `yaml:"tags,omitempty" json:"tags,omitempty"` | ||||
| 	SBOM                string   `yaml:"sbom,omitempty" json:"sbom,omitempty"` | ||||
| 	Ldflags             []string `yaml:"ldflags,omitempty" json:"ldflags,omitempty"` | ||||
| 	Flags               []string `yaml:"flags,omitempty" json:"flags,omitempty"` | ||||
| 	Env                 []string `yaml:"env,omitempty" json:"env,omitempty"` | ||||
| 	Bare                bool     `yaml:"bare,omitempty" json:"bare,omitempty"` | ||||
| 	PreserveImportPaths bool     `yaml:"preserve_import_paths,omitempty" json:"preserve_import_paths,omitempty"` | ||||
| 	BaseImportPaths     bool     `yaml:"base_import_paths,omitempty" json:"base_import_paths,omitempty"` | ||||
| } | ||||
|  | ||||
| // Scoop contains the scoop.sh section. | ||||
| type Scoop struct { | ||||
| 	Name                  string       `yaml:"name,omitempty" json:"name,omitempty"` | ||||
| @@ -916,6 +936,7 @@ type Project struct { | ||||
| 	Brews           []Homebrew       `yaml:"brews,omitempty" json:"brews,omitempty"` | ||||
| 	AURs            []AUR            `yaml:"aurs,omitempty" json:"aurs,omitempty"` | ||||
| 	Krews           []Krew           `yaml:"krews,omitempty" json:"krews,omitempty"` | ||||
| 	Kos             []Ko             `yaml:"kos,omitempty" json:"kos,omitempty"` | ||||
| 	Scoop           Scoop            `yaml:"scoop,omitempty" json:"scoop,omitempty"` | ||||
| 	Builds          []Build          `yaml:"builds,omitempty" json:"builds,omitempty"` | ||||
| 	Archives        []Archive        `yaml:"archives,omitempty" json:"archives,omitempty"` | ||||
|   | ||||
| @@ -96,6 +96,7 @@ type Context struct { | ||||
| 	SkipSign           bool | ||||
| 	SkipValidate       bool | ||||
| 	SkipSBOMCataloging bool | ||||
| 	SkipKo             bool | ||||
| 	SkipDocker         bool | ||||
| 	SkipBefore         bool | ||||
| 	RmDist             bool | ||||
|   | ||||
| @@ -16,6 +16,7 @@ import ( | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/discord" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/docker" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/gomod" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/ko" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/krew" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/linkedin" | ||||
| 	"github.com/goreleaser/goreleaser/internal/pipe/mastodon" | ||||
| @@ -74,6 +75,7 @@ var Defaulters = []Defaulter{ | ||||
| 	aur.Pipe{}, | ||||
| 	brew.Pipe{}, | ||||
| 	krew.Pipe{}, | ||||
| 	ko.Pipe{}, | ||||
| 	scoop.Pipe{}, | ||||
| 	discord.Pipe{}, | ||||
| 	reddit.Pipe{}, | ||||
|   | ||||
							
								
								
									
										143
									
								
								www/docs/customization/ko.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								www/docs/customization/ko.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| # Docker Images with Ko | ||||
|  | ||||
| > Since v1.15. | ||||
|  | ||||
| You can also use [ko][] to build and publish Docker container images. | ||||
|  | ||||
| Please notice that ko will build your binary again. | ||||
| That shouldn't increase the release times too much, as it'll use the same build | ||||
| options as the [build][] pipe when possible, so the results will probably be cached. | ||||
|  | ||||
| !!! warning | ||||
|     Ko only runs on the publish phase, so it might be a bit hard to test — you | ||||
|     might need to push to a fake repository (or a fake tag) when playing around | ||||
|     with its configuration. | ||||
|  | ||||
| ```yaml | ||||
| # .goreleaser.yaml | ||||
| kos: | ||||
| - | ||||
|   # ID of this image. | ||||
|   id: foo | ||||
|  | ||||
|   # Build ID that should be used to import the build settings. | ||||
|   build: build-id | ||||
|  | ||||
|   # Main path to build. | ||||
|   # | ||||
|   # Defaults to the build's main. | ||||
|   main: ./cmd/... | ||||
|  | ||||
|   # Working directory used to build. | ||||
|   # | ||||
|   # Defaults to the build's dir. | ||||
|   working_dir: . | ||||
|  | ||||
|   # Base image to publish to use. | ||||
|   # | ||||
|   # Defaults to cgr.dev/chainguard/static. | ||||
|   base_image: alpine | ||||
|  | ||||
|   # Repository to push to. | ||||
|   # | ||||
|   # Defaults to the value of $KO_DOCKER_REPO. | ||||
|   repository: ghcr.io/foo/bar | ||||
|  | ||||
|   # Platforms to build and publish. | ||||
|   # | ||||
|   # Defaults to linux/amd64. | ||||
|   platforms: | ||||
|   - linux/amd64 | ||||
|   - linux/arm64 | ||||
|  | ||||
|   # Tag templates to build and push. | ||||
|   # | ||||
|   # Defaults to `latest`. | ||||
|   tags: | ||||
|   - latest | ||||
|   - '{{.Tag}}' | ||||
|  | ||||
|   # SBOM format to use. | ||||
|   # | ||||
|   # Defaults to spdx. | ||||
|   # Valid options are: spdx, cyclonedx, go.version-m and none. | ||||
|   sbom: none | ||||
|  | ||||
|   # Ldflags to use on build. | ||||
|   # | ||||
|   # Defaults to the build's ldflags. | ||||
|   ldflags: | ||||
|   - foo | ||||
|   - bar | ||||
|  | ||||
|   # Flags to use on build. | ||||
|   # | ||||
|   # Defaults to the build's flags. | ||||
|   flags: | ||||
|   - foo | ||||
|   - bar | ||||
|  | ||||
|   # Env to use on build. | ||||
|   # | ||||
|   # Defaults to the build's env. | ||||
|   env: | ||||
|   - FOO=bar | ||||
|   - SOMETHING=value | ||||
|  | ||||
|  | ||||
|   # Bare uses a tag on the KO_DOCKER_REPO without anything additional. | ||||
|   # | ||||
|   # Defaults to false. | ||||
|   bare: true | ||||
|  | ||||
|   # Whether to preserve the full import path after the repository name. | ||||
|   # | ||||
|   # Defaults to false. | ||||
|   preserve_import_paths: true | ||||
|  | ||||
|   # Whether to use the base path without the MD5 hash after the repository name. | ||||
|   # | ||||
|   # Defaults to false. | ||||
|   base_import_paths: true | ||||
| ``` | ||||
|  | ||||
| Refer to [ko's project page][ko] for more information. | ||||
|  | ||||
|  | ||||
| ## Example | ||||
|  | ||||
| Here's a minimal example: | ||||
|  | ||||
| ```yaml | ||||
| # .goreleaser.yml | ||||
| before: | ||||
|   hooks: | ||||
|     - go mod tidy | ||||
|  | ||||
| builds: | ||||
|   - env: [ "CGO_ENABLED=1" ] | ||||
|     binary: test | ||||
|     goos: | ||||
|     - darwin | ||||
|     - linux | ||||
|     goarch: | ||||
|     - amd64 | ||||
|     - arch64 | ||||
|  | ||||
| kos: | ||||
|   - repository: ghcr.io/caarlos0/test-ko | ||||
|     tags: | ||||
|       - '{{.Version}}' | ||||
|       - latest | ||||
|     bare: true | ||||
|     preserve_import_paths: false | ||||
|     platforms: | ||||
|       - linux/amd64 | ||||
|       - linux/arm64 | ||||
| ``` | ||||
|  | ||||
| This will build the binaries for `linux/arm64`, `linux/amd64`, `darwin/amd64` | ||||
| and `darwin/arm64`, as well as the Docker images and manifest for Linux. | ||||
|  | ||||
| [ko]: https://ko.build | ||||
| [build]: /customization/build/ | ||||
| @@ -106,6 +106,7 @@ nav: | ||||
|     - customization/chocolatey.md | ||||
|     - customization/docker.md | ||||
|     - customization/docker_manifest.md | ||||
|     - customization/ko.md | ||||
|   - customization/sbom.md | ||||
|   - Signing: | ||||
|     - Checksums and artifacts: customization/sign.md | ||||
|   | ||||
		Reference in New Issue
	
	Block a user