gitee.com/mirrors_opencollective/goreleaser@v0.45.0/pipeline/docker/docker.go (about) 1 // Package docker provides a Pipe that creates and pushes a Docker image 2 package docker 3 4 import ( 5 "bytes" 6 "fmt" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "text/template" 12 13 "github.com/apex/log" 14 "github.com/apex/log/handlers/cli" 15 "github.com/fatih/color" 16 "github.com/masterminds/semver" 17 "github.com/pkg/errors" 18 "golang.org/x/sync/errgroup" 19 20 "github.com/goreleaser/goreleaser/config" 21 "github.com/goreleaser/goreleaser/context" 22 "github.com/goreleaser/goreleaser/internal/artifact" 23 "github.com/goreleaser/goreleaser/pipeline" 24 ) 25 26 // ErrNoDocker is shown when docker cannot be found in $PATH 27 var ErrNoDocker = errors.New("docker not present in $PATH") 28 29 // Pipe for docker 30 type Pipe struct{} 31 32 func (Pipe) String() string { 33 return "creating Docker images" 34 } 35 36 func deprecateWarn(format string, a ...interface{}) { 37 cli.Default.Padding *= 2 38 defer func() { 39 cli.Default.Padding /= 2 40 }() 41 log.Warn(color.New(color.Bold, color.FgHiYellow).Sprintf(format, a...)) 42 } 43 44 // Default sets the pipe defaults 45 func (Pipe) Default(ctx *context.Context) error { 46 for i := range ctx.Config.Dockers { 47 var docker = &ctx.Config.Dockers[i] 48 if docker.OldTagTemplate != "" { 49 deprecateWarn("`dockers[%d].tag_template` is deprecated. Please consider using `dockers[%d].tag_templates` instead", i, i) 50 docker.TagTemplates = append(docker.TagTemplates, docker.OldTagTemplate) 51 } 52 if len(docker.TagTemplates) == 0 { 53 docker.TagTemplates = append(docker.TagTemplates, "{{ .Version }}") 54 } 55 if docker.Goos == "" { 56 docker.Goos = "linux" 57 } 58 if docker.Goarch == "" { 59 docker.Goarch = "amd64" 60 } 61 if docker.Latest { 62 deprecateWarn("`dockers[%d].latest` is deprecated. Please consider adding a `latest` tag to the `dockers[%d].tag_templates` list instead", i, i) 63 docker.TagTemplates = append(docker.TagTemplates, "latest") 64 } 65 } 66 // only set defaults if there is exacly 1 docker setup in the config file. 67 if len(ctx.Config.Dockers) != 1 { 68 return nil 69 } 70 if ctx.Config.Dockers[0].Binary == "" { 71 ctx.Config.Dockers[0].Binary = ctx.Config.Builds[0].Binary 72 } 73 if ctx.Config.Dockers[0].Dockerfile == "" { 74 ctx.Config.Dockers[0].Dockerfile = "Dockerfile" 75 } 76 return nil 77 } 78 79 // Run the pipe 80 func (Pipe) Run(ctx *context.Context) error { 81 if len(ctx.Config.Dockers) == 0 || ctx.Config.Dockers[0].Image == "" { 82 return pipeline.Skip("docker section is not configured") 83 } 84 _, err := exec.LookPath("docker") 85 if err != nil { 86 return ErrNoDocker 87 } 88 return doRun(ctx) 89 } 90 91 func doRun(ctx *context.Context) error { 92 var g errgroup.Group 93 sem := make(chan bool, ctx.Parallelism) 94 for _, docker := range ctx.Config.Dockers { 95 docker := docker 96 sem <- true 97 g.Go(func() error { 98 defer func() { 99 <-sem 100 }() 101 log.WithField("docker", docker).Debug("looking for binaries matching") 102 var binaries = ctx.Artifacts.Filter( 103 artifact.And( 104 artifact.ByGoos(docker.Goos), 105 artifact.ByGoarch(docker.Goarch), 106 artifact.ByGoarm(docker.Goarm), 107 artifact.ByType(artifact.Binary), 108 func(a artifact.Artifact) bool { 109 return a.Extra["Binary"] == docker.Binary 110 }, 111 ), 112 ).List() 113 if len(binaries) == 0 { 114 log.Warnf("no binaries found for %s", docker.Binary) 115 } 116 for _, binary := range binaries { 117 if err := process(ctx, docker, binary); err != nil { 118 return err 119 } 120 } 121 return nil 122 }) 123 } 124 return g.Wait() 125 } 126 127 func tagName(ctx *context.Context, tagTemplate string) (string, error) { 128 var out bytes.Buffer 129 t, err := template.New("tag").Option("missingkey=error").Parse(tagTemplate) 130 if err != nil { 131 return "", err 132 } 133 sv, err := semver.NewVersion(ctx.Git.CurrentTag) 134 if err != nil { 135 return "", err 136 } 137 data := struct { 138 Version, Tag string 139 Major, Minor, Patch int64 140 Env map[string]string 141 }{ 142 Version: ctx.Version, 143 Tag: ctx.Git.CurrentTag, 144 Env: ctx.Env, 145 Major: sv.Major(), 146 Minor: sv.Minor(), 147 Patch: sv.Patch(), 148 } 149 err = t.Execute(&out, data) 150 return out.String(), err 151 } 152 153 func process(ctx *context.Context, docker config.Docker, artifact artifact.Artifact) error { 154 var root = filepath.Dir(artifact.Path) 155 var dockerfile = filepath.Join(root, filepath.Base(docker.Dockerfile)) 156 var images []string 157 for _, tagTemplate := range docker.TagTemplates { 158 tag, err := tagName(ctx, tagTemplate) 159 if err != nil { 160 return errors.Wrapf(err, "failed to execute tag template '%s'", tagTemplate) 161 } 162 images = append(images, fmt.Sprintf("%s:%s", docker.Image, tag)) 163 } 164 if err := os.Link(docker.Dockerfile, dockerfile); err != nil { 165 return errors.Wrap(err, "failed to link dockerfile") 166 } 167 for _, file := range docker.Files { 168 if err := link(file, filepath.Join(root, filepath.Base(file))); err != nil { 169 return errors.Wrapf(err, "failed to link extra file '%s'", file) 170 } 171 } 172 if err := dockerBuild(ctx, root, dockerfile, images[0]); err != nil { 173 return err 174 } 175 for _, img := range images[1:] { 176 if err := dockerTag(ctx, images[0], img); err != nil { 177 return err 178 } 179 } 180 return publish(ctx, docker, images) 181 } 182 183 // walks the src, recreating dirs and hard-linking files 184 func link(src, dest string) error { 185 return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 186 if err != nil { 187 return err 188 } 189 // We have the following: 190 // - src = "a/b" 191 // - dest = "dist/linuxamd64/b" 192 // - path = "a/b/c.txt" 193 // So we join "a/b" with "c.txt" and use it as the destination. 194 var dst = filepath.Join(dest, strings.Replace(path, src, "", 1)) 195 log.WithFields(log.Fields{ 196 "src": path, 197 "dst": dst, 198 }).Info("extra file") 199 if info.IsDir() { 200 return os.MkdirAll(dst, info.Mode()) 201 } 202 return os.Link(path, dst) 203 }) 204 } 205 206 func publish(ctx *context.Context, docker config.Docker, images []string) error { 207 if !ctx.Publish { 208 log.Warn("skipping push because --skip-publish is set") 209 return nil 210 } 211 for _, image := range images { 212 if err := dockerPush(ctx, docker, image); err != nil { 213 return err 214 } 215 } 216 return nil 217 } 218 219 func dockerBuild(ctx *context.Context, root, dockerfile, image string) error { 220 log.WithField("image", image).Info("building docker image") 221 /* #nosec */ 222 var cmd = exec.CommandContext(ctx, "docker", "build", "-f", dockerfile, "-t", image, root) 223 log.WithField("cmd", cmd).Debug("executing") 224 out, err := cmd.CombinedOutput() 225 if err != nil { 226 return errors.Wrapf(err, "failed to build docker image: \n%s", string(out)) 227 } 228 log.Debugf("docker build output: \n%s", string(out)) 229 return nil 230 } 231 232 func dockerTag(ctx *context.Context, image, tag string) error { 233 log.WithField("image", image).WithField("tag", tag).Info("tagging docker image") 234 /* #nosec */ 235 var cmd = exec.CommandContext(ctx, "docker", "tag", image, tag) 236 log.WithField("cmd", cmd).Debug("executing") 237 out, err := cmd.CombinedOutput() 238 if err != nil { 239 return errors.Wrapf(err, "failed to tag docker image: \n%s", string(out)) 240 } 241 log.Debugf("docker tag output: \n%s", string(out)) 242 return nil 243 } 244 245 func dockerPush(ctx *context.Context, docker config.Docker, image string) error { 246 log.WithField("image", image).Info("pushing docker image") 247 /* #nosec */ 248 var cmd = exec.CommandContext(ctx, "docker", "push", image) 249 log.WithField("cmd", cmd).Debug("executing") 250 out, err := cmd.CombinedOutput() 251 if err != nil { 252 return errors.Wrapf(err, "failed to push docker image: \n%s", string(out)) 253 } 254 log.Debugf("docker push output: \n%s", string(out)) 255 ctx.Artifacts.Add(artifact.Artifact{ 256 Type: artifact.DockerImage, 257 Name: image, 258 Path: image, 259 Goarch: docker.Goarch, 260 Goos: docker.Goos, 261 Goarm: docker.Goarm, 262 }) 263 return nil 264 }