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