github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/docker/manifest.go (about) 1 package docker 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/caarlos0/log" 9 "github.com/goreleaser/goreleaser/internal/artifact" 10 "github.com/goreleaser/goreleaser/internal/ids" 11 "github.com/goreleaser/goreleaser/internal/pipe" 12 "github.com/goreleaser/goreleaser/internal/semerrgroup" 13 "github.com/goreleaser/goreleaser/internal/skips" 14 "github.com/goreleaser/goreleaser/internal/tmpl" 15 "github.com/goreleaser/goreleaser/pkg/config" 16 "github.com/goreleaser/goreleaser/pkg/context" 17 ) 18 19 // ManifestPipe is an implementation for the docker manifest feature, 20 // allowing to publish multi-arch docker images. 21 type ManifestPipe struct{} 22 23 func (ManifestPipe) String() string { return "docker manifests" } 24 25 func (ManifestPipe) Skip(ctx *context.Context) bool { 26 return len(ctx.Config.DockerManifests) == 0 || skips.Any(ctx, skips.Docker) 27 } 28 29 func (ManifestPipe) Dependencies(ctx *context.Context) []string { 30 var cmds []string 31 for _, s := range ctx.Config.DockerManifests { 32 switch s.Use { 33 case useDocker, useBuildx: 34 cmds = append(cmds, "docker") 35 // TODO: check buildx 36 } 37 } 38 return cmds 39 } 40 41 // Default sets the pipe defaults. 42 func (ManifestPipe) Default(ctx *context.Context) error { 43 ids := ids.New("docker_manifests") 44 for i := range ctx.Config.DockerManifests { 45 manifest := &ctx.Config.DockerManifests[i] 46 if manifest.ID != "" { 47 ids.Inc(manifest.ID) 48 } 49 if manifest.Use == "" { 50 manifest.Use = useDocker 51 } 52 if err := validateManifester(manifest.Use); err != nil { 53 return err 54 } 55 } 56 return ids.Validate() 57 } 58 59 // Publish the docker manifests. 60 func (ManifestPipe) Publish(ctx *context.Context) error { 61 g := semerrgroup.NewSkipAware(semerrgroup.New(1)) 62 for _, manifest := range ctx.Config.DockerManifests { 63 manifest := manifest 64 g.Go(func() error { 65 skip, err := tmpl.New(ctx).Apply(manifest.SkipPush) 66 if err != nil { 67 return err 68 } 69 if strings.TrimSpace(skip) == "true" { 70 return pipe.Skip("docker_manifest.skip_push is set") 71 } 72 73 if strings.TrimSpace(skip) == "auto" && ctx.Semver.Prerelease != "" { 74 return pipe.Skip("prerelease detected with 'auto' push, skipping docker manifest") 75 } 76 77 name, err := manifestName(ctx, manifest) 78 if err != nil { 79 return err 80 } 81 82 images, err := manifestImages(ctx, manifest) 83 if err != nil { 84 return err 85 } 86 87 manifester := manifesters[manifest.Use] 88 89 log.WithField("manifest", name).WithField("images", images).Info("creating") 90 if err := manifester.Create(ctx, name, images, manifest.CreateFlags); err != nil { 91 return err 92 } 93 art := &artifact.Artifact{ 94 Type: artifact.DockerManifest, 95 Name: name, 96 Path: name, 97 Extra: map[string]interface{}{}, 98 } 99 if manifest.ID != "" { 100 art.Extra[artifact.ExtraID] = manifest.ID 101 } 102 103 log.WithField("manifest", name).Info("pushing") 104 digest, err := manifester.Push(ctx, name, manifest.PushFlags) 105 if err != nil { 106 return err 107 } 108 art.Extra[artifact.ExtraDigest] = digest 109 ctx.Artifacts.Add(art) 110 return nil 111 }) 112 } 113 return g.Wait() 114 } 115 116 func validateManifester(use string) error { 117 valid := make([]string, 0, len(manifesters)) 118 for k := range manifesters { 119 valid = append(valid, k) 120 } 121 for _, s := range valid { 122 if s == use { 123 return nil 124 } 125 } 126 sort.Strings(valid) 127 return fmt.Errorf("docker manifest: invalid use: %s, valid options are %v", use, valid) 128 } 129 130 func manifestName(ctx *context.Context, manifest config.DockerManifest) (string, error) { 131 name, err := tmpl.New(ctx).Apply(manifest.NameTemplate) 132 if err != nil { 133 return name, err 134 } 135 if strings.TrimSpace(name) == "" { 136 return name, pipe.Skip("manifest name is empty") 137 } 138 return name, nil 139 } 140 141 func manifestImages(ctx *context.Context, manifest config.DockerManifest) ([]string, error) { 142 artifacts := ctx.Artifacts.Filter(artifact.ByType(artifact.DockerImage)).List() 143 imgs := make([]string, 0, len(manifest.ImageTemplates)) 144 for _, img := range manifest.ImageTemplates { 145 str, err := tmpl.New(ctx).Apply(img) 146 if err != nil { 147 return []string{}, err 148 } 149 imgs = append(imgs, withDigest(manifest.Use, str, artifacts)) 150 } 151 if strings.TrimSpace(strings.Join(manifest.ImageTemplates, "")) == "" { 152 return imgs, pipe.Skip("manifest has no images") 153 } 154 return imgs, nil 155 } 156 157 func withDigest(use, name string, images []*artifact.Artifact) string { 158 for _, art := range images { 159 if art.Name == name { 160 if digest := artifact.ExtraOr(*art, artifact.ExtraDigest, ""); digest != "" { 161 return name + "@" + digest 162 } 163 break 164 } 165 } 166 log.Warnf("did not find the digest for %s using %s, defaulting to insecure mode", name, use) 167 return name 168 }