mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-02-01 13:07:49 +02:00
feat: add digest to artifacts info of published docker images (#3540)
Extract the digest (sha256) of docker images from the `docker push` command for dockers published to the docker registry. Outputting the digest is required to avoid a race condition when referencing the image, where the image tag is being modified before the reference is done. See this [blog post](https://github.com/goreleaser/goreleaser/issues/3496) for more info. This PR fixes https://github.com/goreleaser/goreleaser/issues/3496. Note that the 'publish' pipe now must run before the 'metadata' pipe, so that the information extracted during the 'publish' pipe would appear in the metadata. Previously, the published docker images metadata wasn't printed (because of the order). It made sense because the content of the published image was just a subset of the local one. Now that it is printed to the metadata, it should have a different name to avoid confusion. As I mentioned, it wasn't printed before - so there shouldn't be any backward-compatibility issues. --- Local tests: ``` go test -v . === RUN TestVersion === RUN TestVersion/only_version === RUN TestVersion/version_and_date === RUN TestVersion/version,_date,_built_by === RUN TestVersion/all_empty === RUN TestVersion/complete --- PASS: TestVersion (0.00s) --- PASS: TestVersion/only_version (0.00s) --- PASS: TestVersion/version_and_date (0.00s) --- PASS: TestVersion/version,_date,_built_by (0.00s) --- PASS: TestVersion/all_empty (0.00s) --- PASS: TestVersion/complete (0.00s) PASS ok github.com/goreleaser/goreleaser 0.764s ``` Output example: ``` { "name": "gallegit/hello-world:latest", "path": "gallegit/hello-world:latest", "goos": "linux", "goarch": "amd64", "internal_type": 10, "type": "Published Docker Image", "extra": { "digest": "sha256:c3f7dd196a046dc061236d3c6ae1e2946269e90da30b0a959240ca799750e632" } } ``` Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com> Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
parent
778f099a9a
commit
5eb1e4ad0d
@ -86,8 +86,10 @@ func (t Type) String() string {
|
||||
return "Binary"
|
||||
case LinuxPackage:
|
||||
return "Linux Package"
|
||||
case PublishableDockerImage, DockerImage:
|
||||
case PublishableDockerImage:
|
||||
return "Docker Image"
|
||||
case DockerImage:
|
||||
return "Published Docker Image"
|
||||
case DockerManifest:
|
||||
return "Docker Manifest"
|
||||
case PublishableSnapcraft, Snapcraft:
|
||||
|
@ -34,7 +34,7 @@ func registerImager(use string, impl imager) {
|
||||
// imager is something that can build and push docker images.
|
||||
type imager interface {
|
||||
Build(ctx *context.Context, root string, images, flags []string) error
|
||||
Push(ctx *context.Context, image string, flags []string) error
|
||||
Push(ctx *context.Context, image string, flags []string) (digest string, err error)
|
||||
}
|
||||
|
||||
// manifester is something that can create and push docker manifests.
|
||||
@ -66,3 +66,31 @@ func runCommand(ctx *context.Context, dir, binary string, args ...string) error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCommandWithOutput(ctx *context.Context, dir, binary string, args ...string) ([]byte, error) {
|
||||
fields := log.Fields{
|
||||
"cmd": append([]string{binary}, args[0]),
|
||||
"cwd": dir,
|
||||
}
|
||||
|
||||
/* #nosec */
|
||||
cmd := exec.CommandContext(ctx, binary, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Env = ctx.Env.Strings()
|
||||
|
||||
var b bytes.Buffer
|
||||
w := gio.Safe(&b)
|
||||
cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
|
||||
|
||||
log.WithFields(fields).WithField("args", args[1:]).Debug("running")
|
||||
out, err := cmd.Output()
|
||||
if out != nil {
|
||||
// regardless of command success, always print stdout for backward-compatibility with runCommand()
|
||||
_, _ = io.MultiWriter(logext.NewWriter(fields, logext.Error), w).Write(out)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s", err, b.String())
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/goreleaser/goreleaser/pkg/context"
|
||||
)
|
||||
@ -43,11 +44,21 @@ type dockerImager struct {
|
||||
buildx bool
|
||||
}
|
||||
|
||||
func (i dockerImager) Push(ctx *context.Context, image string, flags []string) error {
|
||||
if err := runCommand(ctx, ".", "docker", "push", image); err != nil {
|
||||
return fmt.Errorf("failed to push %s: %w", image, err)
|
||||
var dockerDigestPattern = regexp.MustCompile("sha256:[a-z0-9]{64}")
|
||||
|
||||
func (i dockerImager) Push(ctx *context.Context, image string, flags []string) (digest string, err error) {
|
||||
outBytes, err := runCommandWithOutput(ctx, ".", "docker", "push", image)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to push %s: %w", image, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
out := string(outBytes)
|
||||
digest = dockerDigestPattern.FindString(out)
|
||||
if digest == "" {
|
||||
return "", fmt.Errorf("failed to find docker digest in docker push output: %v", out)
|
||||
}
|
||||
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
func (i dockerImager) Build(ctx *context.Context, root string, images, flags []string) error {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
|
||||
const (
|
||||
dockerConfigExtra = "DockerConfig"
|
||||
dockerDigestExtra = "digest"
|
||||
|
||||
useBuildx = "buildx"
|
||||
useDocker = "docker"
|
||||
@ -248,7 +249,8 @@ func dockerPush(ctx *context.Context, image *artifact.Artifact) error {
|
||||
return pipe.Skip("prerelease detected with 'auto' push, skipping docker publish: " + image.Name)
|
||||
}
|
||||
|
||||
if err := imagers[docker.Use].Push(ctx, image.Name, docker.PushFlags); err != nil {
|
||||
digest, err := imagers[docker.Use].Push(ctx, image.Name, docker.PushFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -264,6 +266,8 @@ func dockerPush(ctx *context.Context, image *artifact.Artifact) error {
|
||||
if docker.ID != "" {
|
||||
art.Extra[artifact.ExtraID] = docker.ID
|
||||
}
|
||||
art.Extra[dockerDigestExtra] = digest
|
||||
|
||||
ctx.Artifacts.Add(art)
|
||||
return nil
|
||||
}
|
||||
|
@ -1091,6 +1091,13 @@ func TestRunPipe(t *testing.T) {
|
||||
// t.Log("removing docker image", img)
|
||||
require.NoError(t, rmi(img), "could not delete image %s", img)
|
||||
}
|
||||
|
||||
_ = ctx.Artifacts.Filter(artifact.ByType(artifact.DockerImage)).Visit(func(a *artifact.Artifact) error {
|
||||
digest, err := artifact.Extra[string](*a, dockerDigestExtra)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, digest)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -111,10 +111,10 @@ var Pipeline = append(
|
||||
chocolatey.Pipe{},
|
||||
// create and push docker images
|
||||
docker.Pipe{},
|
||||
// creates a metadata.json and an artifacts.json files in the dist folder
|
||||
metadata.Pipe{},
|
||||
// publishes artifacts
|
||||
publish.Pipe{},
|
||||
// creates a metadata.json and an artifacts.json files in the dist folder
|
||||
metadata.Pipe{},
|
||||
// announce releases
|
||||
announce.Pipe{},
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user