Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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](#3496) for more
info.
This PR fixes #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>
  • Loading branch information
gal-legit and caarlos0 committed Nov 12, 2022
1 parent 778f099 commit 5eb1e4a
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 9 deletions.
4 changes: 3 additions & 1 deletion internal/artifact/artifact.go
Expand Up @@ -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:
Expand Down
30 changes: 29 additions & 1 deletion internal/pipe/docker/api.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
19 changes: 15 additions & 4 deletions internal/pipe/docker/api_docker.go
Expand Up @@ -2,6 +2,7 @@ package docker

import (
"fmt"
"regexp"

"github.com/goreleaser/goreleaser/pkg/context"
)
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion internal/pipe/docker/docker.go
Expand Up @@ -20,6 +20,7 @@ import (

const (
dockerConfigExtra = "DockerConfig"
dockerDigestExtra = "digest"

useBuildx = "buildx"
useDocker = "docker"
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
7 changes: 7 additions & 0 deletions internal/pipe/docker/docker_test.go
Expand Up @@ -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
})
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions internal/pipeline/pipeline.go
Expand Up @@ -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{},
)

0 comments on commit 5eb1e4a

Please sign in to comment.