github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/pipe/docker/docker.go (about) 1 package docker 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "sort" 9 "strings" 10 11 "github.com/apex/log" 12 "github.com/goreleaser/goreleaser/internal/artifact" 13 "github.com/goreleaser/goreleaser/internal/deprecate" 14 "github.com/goreleaser/goreleaser/internal/gio" 15 "github.com/goreleaser/goreleaser/internal/ids" 16 "github.com/goreleaser/goreleaser/internal/pipe" 17 "github.com/goreleaser/goreleaser/internal/semerrgroup" 18 "github.com/goreleaser/goreleaser/internal/tmpl" 19 "github.com/goreleaser/goreleaser/pkg/config" 20 "github.com/goreleaser/goreleaser/pkg/context" 21 ) 22 23 const ( 24 dockerConfigExtra = "DockerConfig" 25 26 useBuildx = "buildx" 27 useDocker = "docker" 28 useBuildPacks = "buildpacks" 29 ) 30 31 // Pipe for docker. 32 type Pipe struct{} 33 34 func (Pipe) String() string { return "docker images" } 35 func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.Dockers) == 0 } 36 37 // Default sets the pipe defaults. 38 func (Pipe) Default(ctx *context.Context) error { 39 ids := ids.New("dockers") 40 for i := range ctx.Config.Dockers { 41 docker := &ctx.Config.Dockers[i] 42 43 if docker.ID != "" { 44 ids.Inc(docker.ID) 45 } 46 if docker.Goos == "" { 47 docker.Goos = "linux" 48 } 49 if docker.Goarch == "" { 50 docker.Goarch = "amd64" 51 } 52 if docker.Dockerfile == "" { 53 docker.Dockerfile = "Dockerfile" 54 } 55 if docker.Buildx { 56 deprecate.Notice(ctx, "docker.use_buildx") 57 if docker.Use == "" { 58 docker.Use = useBuildx 59 } 60 } 61 if docker.Use == "" { 62 docker.Use = useDocker 63 } 64 if err := validateImager(docker.Use); err != nil { 65 return err 66 } 67 for _, f := range docker.Files { 68 if f == "." || strings.HasPrefix(f, ctx.Config.Dist) { 69 return fmt.Errorf("invalid docker.files: can't be . or inside dist folder: %s", f) 70 } 71 } 72 } 73 return ids.Validate() 74 } 75 76 func validateImager(use string) error { 77 valid := make([]string, 0, len(imagers)) 78 for k := range imagers { 79 valid = append(valid, k) 80 } 81 for _, s := range valid { 82 if s == use { 83 return nil 84 } 85 } 86 sort.Strings(valid) 87 return fmt.Errorf("docker: invalid use: %s, valid options are %v", use, valid) 88 } 89 90 // Publish the docker images. 91 func (Pipe) Publish(ctx *context.Context) error { 92 images := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableDockerImage)).List() 93 for _, image := range images { 94 if err := dockerPush(ctx, image); err != nil { 95 return err 96 } 97 } 98 return nil 99 } 100 101 // Run the pipe. 102 func (Pipe) Run(ctx *context.Context) error { 103 g := semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism)) 104 for _, docker := range ctx.Config.Dockers { 105 docker := docker 106 g.Go(func() error { 107 log.WithField("docker", docker).Debug("looking for artifacts matching") 108 filters := []artifact.Filter{ 109 artifact.ByGoos(docker.Goos), 110 artifact.ByGoarch(docker.Goarch), 111 artifact.ByGoarm(docker.Goarm), 112 artifact.Or( 113 artifact.ByType(artifact.Binary), 114 artifact.ByType(artifact.LinuxPackage), 115 ), 116 } 117 if len(docker.IDs) > 0 { 118 filters = append(filters, artifact.ByIDs(docker.IDs...)) 119 } 120 artifacts := ctx.Artifacts.Filter(artifact.And(filters...)) 121 log.WithField("artifacts", artifacts.Paths()).Debug("found artifacts") 122 return process(ctx, docker, artifacts.List()) 123 }) 124 } 125 return g.Wait() 126 } 127 128 func process(ctx *context.Context, docker config.Docker, artifacts []*artifact.Artifact) error { 129 tmp, err := ioutil.TempDir(ctx.Config.Dist, "goreleaserdocker") 130 if err != nil { 131 return fmt.Errorf("failed to create temporary dir: %w", err) 132 } 133 134 images, err := processImageTemplates(ctx, docker) 135 if err != nil { 136 return err 137 } 138 139 if len(images) == 0 { 140 return pipe.Skip("no image templates found") 141 } 142 143 log := log.WithField("image", images[0]) 144 log.Debug("tempdir: " + tmp) 145 146 if docker.Use != useBuildPacks { 147 if err := gio.Copy(docker.Dockerfile, filepath.Join(tmp, "Dockerfile")); err != nil { 148 return fmt.Errorf("failed to copy dockerfile: %w", err) 149 } 150 } 151 for _, file := range docker.Files { 152 if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(file)), 0o755); err != nil { 153 return fmt.Errorf("failed to copy extra file '%s': %w", file, err) 154 } 155 if err := gio.Copy(file, filepath.Join(tmp, file)); err != nil { 156 return fmt.Errorf("failed to copy extra file '%s': %w", file, err) 157 } 158 } 159 for _, art := range artifacts { 160 if err := gio.Copy(art.Path, filepath.Join(tmp, filepath.Base(art.Path))); err != nil { 161 return fmt.Errorf("failed to copy artifact: %w", err) 162 } 163 } 164 165 buildFlags, err := processBuildFlagTemplates(ctx, docker) 166 if err != nil { 167 return err 168 } 169 170 log.Info("building docker image") 171 if err := imagers[docker.Use].Build(ctx, tmp, images, buildFlags); err != nil { 172 return err 173 } 174 175 if strings.TrimSpace(docker.SkipPush) == "true" { 176 return pipe.Skip("docker.skip_push is set") 177 } 178 if ctx.SkipPublish { 179 return pipe.ErrSkipPublishEnabled 180 } 181 if strings.TrimSpace(docker.SkipPush) == "auto" && ctx.Semver.Prerelease != "" { 182 return pipe.Skip("prerelease detected with 'auto' push, skipping docker publish") 183 } 184 for _, img := range images { 185 ctx.Artifacts.Add(&artifact.Artifact{ 186 Type: artifact.PublishableDockerImage, 187 Name: img, 188 Path: img, 189 Goarch: docker.Goarch, 190 Goos: docker.Goos, 191 Goarm: docker.Goarm, 192 Extra: map[string]interface{}{ 193 dockerConfigExtra: docker, 194 }, 195 }) 196 } 197 return nil 198 } 199 200 func processImageTemplates(ctx *context.Context, docker config.Docker) ([]string, error) { 201 // nolint:prealloc 202 var images []string 203 for _, imageTemplate := range docker.ImageTemplates { 204 image, err := tmpl.New(ctx).Apply(imageTemplate) 205 if err != nil { 206 return nil, fmt.Errorf("failed to execute image template '%s': %w", imageTemplate, err) 207 } 208 if image == "" { 209 continue 210 } 211 212 images = append(images, image) 213 } 214 215 return images, nil 216 } 217 218 func processBuildFlagTemplates(ctx *context.Context, docker config.Docker) ([]string, error) { 219 // nolint:prealloc 220 var buildFlags []string 221 for _, buildFlagTemplate := range docker.BuildFlagTemplates { 222 buildFlag, err := tmpl.New(ctx).Apply(buildFlagTemplate) 223 if err != nil { 224 return nil, fmt.Errorf("failed to process build flag template '%s': %w", buildFlagTemplate, err) 225 } 226 buildFlags = append(buildFlags, buildFlag) 227 } 228 return buildFlags, nil 229 } 230 231 func dockerPush(ctx *context.Context, image *artifact.Artifact) error { 232 log.WithField("image", image.Name).Info("pushing docker image") 233 docker := image.Extra[dockerConfigExtra].(config.Docker) 234 if err := imagers[docker.Use].Push(ctx, image.Name, docker.PushFlags); err != nil { 235 return err 236 } 237 art := &artifact.Artifact{ 238 Type: artifact.DockerImage, 239 Name: image.Name, 240 Path: image.Path, 241 Goarch: image.Goarch, 242 Goos: image.Goos, 243 Goarm: image.Goarm, 244 Extra: map[string]interface{}{}, 245 } 246 if docker.ID != "" { 247 art.Extra["ID"] = docker.ID 248 } 249 ctx.Artifacts.Add(art) 250 return nil 251 }