github.com/joselitofilho/goreleaser@v0.155.1-0.20210123221854-e4891856c593/internal/pipe/docker/docker.go (about) 1 package docker 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 12 "github.com/apex/log" 13 "github.com/goreleaser/goreleaser/internal/artifact" 14 "github.com/goreleaser/goreleaser/internal/deprecate" 15 "github.com/goreleaser/goreleaser/internal/pipe" 16 "github.com/goreleaser/goreleaser/internal/semerrgroup" 17 "github.com/goreleaser/goreleaser/internal/tmpl" 18 "github.com/goreleaser/goreleaser/pkg/config" 19 "github.com/goreleaser/goreleaser/pkg/context" 20 ) 21 22 // ErrNoDocker is shown when docker cannot be found in $PATH. 23 var ErrNoDocker = errors.New("docker not present in $PATH") 24 25 // Pipe for docker. 26 type Pipe struct{} 27 28 func (Pipe) String() string { 29 return "docker images" 30 } 31 32 // Default sets the pipe defaults. 33 func (Pipe) Default(ctx *context.Context) error { 34 for i := range ctx.Config.Dockers { 35 var docker = &ctx.Config.Dockers[i] 36 37 if docker.Goos == "" { 38 docker.Goos = "linux" 39 } 40 if docker.Goarch == "" { 41 docker.Goarch = "amd64" 42 } 43 if docker.Dockerfile == "" { 44 docker.Dockerfile = "Dockerfile" 45 } 46 if len(docker.Binaries) > 0 { 47 deprecate.Notice(ctx, "docker.binaries") 48 } 49 if len(docker.Builds) > 0 { 50 deprecate.Notice(ctx, "docker.builds") 51 docker.IDs = append(docker.IDs, docker.Builds...) 52 } 53 for _, f := range docker.Files { 54 if f == "." || strings.HasPrefix(f, ctx.Config.Dist) { 55 return fmt.Errorf("invalid docker.files: can't be . or inside dist folder: %s", f) 56 } 57 } 58 } 59 return nil 60 } 61 62 // Run the pipe. 63 func (Pipe) Run(ctx *context.Context) error { 64 if len(ctx.Config.Dockers) == 0 || len(ctx.Config.Dockers[0].ImageTemplates) == 0 { 65 return pipe.Skip("docker section is not configured") 66 } 67 _, err := exec.LookPath("docker") 68 if err != nil { 69 return ErrNoDocker 70 } 71 return doRun(ctx) 72 } 73 74 // Publish the docker images. 75 func (Pipe) Publish(ctx *context.Context) error { 76 if ctx.SkipPublish { 77 return pipe.ErrSkipPublishEnabled 78 } 79 var images = ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableDockerImage)).List() 80 for _, image := range images { 81 if err := dockerPush(ctx, image); err != nil { 82 return err 83 } 84 } 85 return nil 86 } 87 88 func doRun(ctx *context.Context) error { 89 var g = semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism)) 90 for _, docker := range ctx.Config.Dockers { 91 docker := docker 92 g.Go(func() error { 93 log.WithField("docker", docker).Debug("looking for artifacts matching") 94 var filters = []artifact.Filter{ 95 artifact.ByGoos(docker.Goos), 96 artifact.ByGoarch(docker.Goarch), 97 artifact.ByGoarm(docker.Goarm), 98 artifact.Or( 99 artifact.ByType(artifact.Binary), 100 artifact.ByType(artifact.LinuxPackage), 101 ), 102 } 103 if len(docker.IDs) > 0 { 104 filters = append(filters, artifact.ByIDs(docker.IDs...)) 105 } 106 var artifacts = ctx.Artifacts.Filter(artifact.And(filters...)) 107 log.WithField("artifacts", artifacts.Paths()).Debug("found artifacts") 108 return process(ctx, docker, artifacts.List()) 109 }) 110 } 111 return g.Wait() 112 } 113 114 func process(ctx *context.Context, docker config.Docker, artifacts []*artifact.Artifact) error { 115 tmp, err := ioutil.TempDir(ctx.Config.Dist, "goreleaserdocker") 116 if err != nil { 117 return fmt.Errorf("failed to create temporary dir: %w", err) 118 } 119 log.Debug("tempdir: " + tmp) 120 121 images, err := processImageTemplates(ctx, docker) 122 if err != nil { 123 return err 124 } 125 126 if err := os.Link(docker.Dockerfile, filepath.Join(tmp, "Dockerfile")); err != nil { 127 return fmt.Errorf("failed to link dockerfile: %w", err) 128 } 129 for _, file := range docker.Files { 130 if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(file)), 0755); err != nil { 131 return fmt.Errorf("failed to link extra file '%s': %w", file, err) 132 } 133 if err := link(file, filepath.Join(tmp, file)); err != nil { 134 return fmt.Errorf("failed to link extra file '%s': %w", file, err) 135 } 136 } 137 for _, art := range artifacts { 138 if err := os.Link(art.Path, filepath.Join(tmp, filepath.Base(art.Path))); err != nil { 139 return fmt.Errorf("failed to link artifact: %w", err) 140 } 141 } 142 143 buildFlags, err := processBuildFlagTemplates(ctx, docker) 144 if err != nil { 145 return err 146 } 147 148 if err := dockerBuild(ctx, tmp, images, buildFlags, docker.Buildx); err != nil { 149 return err 150 } 151 152 if strings.TrimSpace(docker.SkipPush) == "true" { 153 return pipe.Skip("docker.skip_push is set") 154 } 155 if ctx.SkipPublish { 156 return pipe.ErrSkipPublishEnabled 157 } 158 if ctx.Config.Release.Draft { 159 return pipe.Skip("release is marked as draft") 160 } 161 if strings.TrimSpace(docker.SkipPush) == "auto" && ctx.Semver.Prerelease != "" { 162 return pipe.Skip("prerelease detected with 'auto' push, skipping docker publish") 163 } 164 for _, img := range images { 165 ctx.Artifacts.Add(&artifact.Artifact{ 166 Type: artifact.PublishableDockerImage, 167 Name: img, 168 Path: img, 169 Goarch: docker.Goarch, 170 Goos: docker.Goos, 171 Goarm: docker.Goarm, 172 }) 173 } 174 return nil 175 } 176 177 func processImageTemplates(ctx *context.Context, docker config.Docker) ([]string, error) { 178 // nolint:prealloc 179 var images []string 180 for _, imageTemplate := range docker.ImageTemplates { 181 image, err := tmpl.New(ctx).Apply(imageTemplate) 182 if err != nil { 183 return nil, fmt.Errorf("failed to execute image template '%s': %w", imageTemplate, err) 184 } 185 if image == "" { 186 continue 187 } 188 189 images = append(images, image) 190 } 191 192 if len(images) == 0 { 193 return images, errors.New("no image templates found") 194 } 195 196 return images, nil 197 } 198 199 func processBuildFlagTemplates(ctx *context.Context, docker config.Docker) ([]string, error) { 200 // nolint:prealloc 201 var buildFlags []string 202 for _, buildFlagTemplate := range docker.BuildFlagTemplates { 203 buildFlag, err := tmpl.New(ctx).Apply(buildFlagTemplate) 204 if err != nil { 205 return nil, fmt.Errorf("failed to process build flag template '%s': %w", buildFlagTemplate, err) 206 } 207 buildFlags = append(buildFlags, buildFlag) 208 } 209 return buildFlags, nil 210 } 211 212 // walks the src, recreating dirs and hard-linking files. 213 func link(src, dest string) error { 214 return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 215 if err != nil { 216 return err 217 } 218 // We have the following: 219 // - src = "a/b" 220 // - dest = "dist/linuxamd64/b" 221 // - path = "a/b/c.txt" 222 // So we join "a/b" with "c.txt" and use it as the destination. 223 var dst = filepath.Join(dest, strings.Replace(path, src, "", 1)) 224 log.WithFields(log.Fields{ 225 "src": path, 226 "dst": dst, 227 }).Debug("extra file") 228 if info.IsDir() { 229 return os.MkdirAll(dst, info.Mode()) 230 } 231 return os.Link(path, dst) 232 }) 233 } 234 235 func dockerBuild(ctx *context.Context, root string, images, flags []string, buildx bool) error { 236 log.WithField("image", images[0]).WithField("buildx", buildx).Info("building docker image") 237 /* #nosec */ 238 var cmd = exec.CommandContext(ctx, "docker", buildCommand(buildx, images, flags)...) 239 cmd.Dir = root 240 log.WithField("cmd", cmd.Args).WithField("cwd", cmd.Dir).Debug("running") 241 out, err := cmd.CombinedOutput() 242 if err != nil { 243 return fmt.Errorf("failed to build docker image: %s: \n%s: %w", images[0], string(out), err) 244 } 245 log.Debugf("docker build output: \n%s", string(out)) 246 return nil 247 } 248 249 func buildCommand(buildx bool, images, flags []string) []string { 250 base := []string{"build", "."} 251 if buildx { 252 base = []string{"buildx", "build", ".", "--load"} 253 } 254 for _, image := range images { 255 base = append(base, "-t", image) 256 } 257 base = append(base, flags...) 258 return base 259 } 260 261 func dockerPush(ctx *context.Context, image *artifact.Artifact) error { 262 log.WithField("image", image.Name).Info("pushing docker image") 263 /* #nosec */ 264 var cmd = exec.CommandContext(ctx, "docker", "push", image.Name) 265 log.WithField("cmd", cmd.Args).Debug("running") 266 out, err := cmd.CombinedOutput() 267 if err != nil { 268 return fmt.Errorf("failed to push docker image: \n%s: %w", string(out), err) 269 } 270 log.Debugf("docker push output: \n%s", string(out)) 271 ctx.Artifacts.Add(&artifact.Artifact{ 272 Type: artifact.DockerImage, 273 Name: image.Name, 274 Path: image.Path, 275 Goarch: image.Goarch, 276 Goos: image.Goos, 277 Goarm: image.Goarm, 278 }) 279 return nil 280 }