2017-09-12 04:31:00 +02:00
|
|
|
// Package docker provides a Pipe that creates and pushes a Docker image
|
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
2017-12-05 15:19:44 +02:00
|
|
|
"bytes"
|
2017-09-12 04:31:00 +02:00
|
|
|
"fmt"
|
2017-09-12 12:54:43 +02:00
|
|
|
"os"
|
2017-09-12 04:31:00 +02:00
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2017-12-24 14:12:51 +02:00
|
|
|
"strings"
|
2017-12-05 15:31:51 +02:00
|
|
|
"text/template"
|
2017-09-12 04:31:00 +02:00
|
|
|
|
2017-12-03 04:42:52 +02:00
|
|
|
"github.com/apex/log"
|
2018-01-19 03:55:26 +02:00
|
|
|
"github.com/masterminds/semver"
|
2017-12-17 20:46:45 +02:00
|
|
|
"github.com/pkg/errors"
|
2017-12-26 03:27:06 +02:00
|
|
|
"golang.org/x/sync/errgroup"
|
2017-12-17 20:46:45 +02:00
|
|
|
|
2017-09-12 04:31:00 +02:00
|
|
|
"github.com/goreleaser/goreleaser/config"
|
|
|
|
"github.com/goreleaser/goreleaser/context"
|
2017-12-18 03:11:17 +02:00
|
|
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
2018-02-17 17:43:29 +02:00
|
|
|
"github.com/goreleaser/goreleaser/internal/deprecate"
|
2017-09-12 04:31:00 +02:00
|
|
|
"github.com/goreleaser/goreleaser/pipeline"
|
|
|
|
)
|
|
|
|
|
2017-09-12 05:22:02 +02:00
|
|
|
// ErrNoDocker is shown when docker cannot be found in $PATH
|
|
|
|
var ErrNoDocker = errors.New("docker not present in $PATH")
|
|
|
|
|
|
|
|
// Pipe for docker
|
2017-09-12 04:31:00 +02:00
|
|
|
type Pipe struct{}
|
|
|
|
|
2017-12-02 23:53:19 +02:00
|
|
|
func (Pipe) String() string {
|
|
|
|
return "creating Docker images"
|
2017-09-12 04:31:00 +02:00
|
|
|
}
|
|
|
|
|
2017-12-02 23:53:19 +02:00
|
|
|
// Default sets the pipe defaults
|
|
|
|
func (Pipe) Default(ctx *context.Context) error {
|
2017-12-05 15:19:44 +02:00
|
|
|
for i := range ctx.Config.Dockers {
|
2017-12-17 22:10:38 +02:00
|
|
|
var docker = &ctx.Config.Dockers[i]
|
2018-01-18 21:40:44 +02:00
|
|
|
if docker.OldTagTemplate != "" {
|
2018-02-17 17:43:29 +02:00
|
|
|
deprecate.Notice("docker.tag_template")
|
2018-01-18 21:40:44 +02:00
|
|
|
docker.TagTemplates = append(docker.TagTemplates, docker.OldTagTemplate)
|
|
|
|
}
|
|
|
|
if len(docker.TagTemplates) == 0 {
|
|
|
|
docker.TagTemplates = append(docker.TagTemplates, "{{ .Version }}")
|
2017-12-17 22:10:38 +02:00
|
|
|
}
|
|
|
|
if docker.Goos == "" {
|
|
|
|
docker.Goos = "linux"
|
|
|
|
}
|
|
|
|
if docker.Goarch == "" {
|
|
|
|
docker.Goarch = "amd64"
|
2017-12-05 15:19:44 +02:00
|
|
|
}
|
2018-01-18 21:40:44 +02:00
|
|
|
if docker.Latest {
|
2018-02-17 17:43:29 +02:00
|
|
|
deprecate.Notice("docker.latest")
|
2018-01-18 21:40:44 +02:00
|
|
|
docker.TagTemplates = append(docker.TagTemplates, "latest")
|
|
|
|
}
|
2017-12-05 15:19:44 +02:00
|
|
|
}
|
2017-12-03 04:42:52 +02:00
|
|
|
// only set defaults if there is exacly 1 docker setup in the config file.
|
2017-12-02 23:53:19 +02:00
|
|
|
if len(ctx.Config.Dockers) != 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if ctx.Config.Dockers[0].Binary == "" {
|
|
|
|
ctx.Config.Dockers[0].Binary = ctx.Config.Builds[0].Binary
|
|
|
|
}
|
|
|
|
if ctx.Config.Dockers[0].Dockerfile == "" {
|
|
|
|
ctx.Config.Dockers[0].Dockerfile = "Dockerfile"
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-17 20:46:45 +02:00
|
|
|
// Run the pipe
|
|
|
|
func (Pipe) Run(ctx *context.Context) error {
|
|
|
|
if len(ctx.Config.Dockers) == 0 || ctx.Config.Dockers[0].Image == "" {
|
|
|
|
return pipeline.Skip("docker section is not configured")
|
|
|
|
}
|
|
|
|
_, err := exec.LookPath("docker")
|
|
|
|
if err != nil {
|
|
|
|
return ErrNoDocker
|
|
|
|
}
|
|
|
|
return doRun(ctx)
|
|
|
|
}
|
|
|
|
|
2017-09-15 02:19:56 +02:00
|
|
|
func doRun(ctx *context.Context) error {
|
2017-12-26 03:27:06 +02:00
|
|
|
var g errgroup.Group
|
|
|
|
sem := make(chan bool, ctx.Parallelism)
|
2018-05-23 14:30:35 +02:00
|
|
|
for i, docker := range ctx.Config.Dockers {
|
2017-12-26 03:27:06 +02:00
|
|
|
docker := docker
|
2018-05-23 14:30:35 +02:00
|
|
|
seed := i
|
2017-12-26 03:27:06 +02:00
|
|
|
sem <- true
|
|
|
|
g.Go(func() error {
|
|
|
|
defer func() {
|
|
|
|
<-sem
|
|
|
|
}()
|
|
|
|
log.WithField("docker", docker).Debug("looking for binaries matching")
|
|
|
|
var binaries = ctx.Artifacts.Filter(
|
|
|
|
artifact.And(
|
|
|
|
artifact.ByGoos(docker.Goos),
|
|
|
|
artifact.ByGoarch(docker.Goarch),
|
|
|
|
artifact.ByGoarm(docker.Goarm),
|
|
|
|
artifact.ByType(artifact.Binary),
|
|
|
|
func(a artifact.Artifact) bool {
|
|
|
|
return a.Extra["Binary"] == docker.Binary
|
|
|
|
},
|
|
|
|
),
|
|
|
|
).List()
|
|
|
|
if len(binaries) == 0 {
|
2017-12-26 03:30:39 +02:00
|
|
|
log.Warnf("no binaries found for %s", docker.Binary)
|
2017-09-12 04:31:00 +02:00
|
|
|
}
|
2017-12-26 03:27:06 +02:00
|
|
|
for _, binary := range binaries {
|
2018-05-23 14:30:35 +02:00
|
|
|
if err := process(ctx, docker, binary, seed); err != nil {
|
2017-12-26 03:27:06 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
2017-09-12 04:31:00 +02:00
|
|
|
}
|
2017-12-26 03:27:06 +02:00
|
|
|
return g.Wait()
|
2017-09-12 04:31:00 +02:00
|
|
|
}
|
|
|
|
|
2018-01-18 21:40:44 +02:00
|
|
|
func tagName(ctx *context.Context, tagTemplate string) (string, error) {
|
2017-12-05 15:19:44 +02:00
|
|
|
var out bytes.Buffer
|
2018-01-18 21:40:44 +02:00
|
|
|
t, err := template.New("tag").Option("missingkey=error").Parse(tagTemplate)
|
2017-12-05 15:19:44 +02:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2018-01-19 03:55:26 +02:00
|
|
|
sv, err := semver.NewVersion(ctx.Git.CurrentTag)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2017-12-05 15:19:44 +02:00
|
|
|
data := struct {
|
2018-05-01 05:32:48 +02:00
|
|
|
Version string
|
|
|
|
Tag string
|
|
|
|
Commit string
|
|
|
|
Major int64
|
|
|
|
Minor int64
|
|
|
|
Patch int64
|
|
|
|
Env map[string]string
|
2017-12-05 15:19:44 +02:00
|
|
|
}{
|
|
|
|
Version: ctx.Version,
|
2018-05-01 05:32:48 +02:00
|
|
|
Commit: ctx.Git.Commit,
|
2017-12-05 15:19:44 +02:00
|
|
|
Tag: ctx.Git.CurrentTag,
|
2017-12-06 01:06:37 +02:00
|
|
|
Env: ctx.Env,
|
2018-01-19 03:55:26 +02:00
|
|
|
Major: sv.Major(),
|
|
|
|
Minor: sv.Minor(),
|
|
|
|
Patch: sv.Patch(),
|
2017-12-05 15:19:44 +02:00
|
|
|
}
|
|
|
|
err = t.Execute(&out, data)
|
|
|
|
return out.String(), err
|
|
|
|
}
|
|
|
|
|
2018-05-23 14:30:35 +02:00
|
|
|
func process(ctx *context.Context, docker config.Docker, artifact artifact.Artifact, seed int) error {
|
2017-12-17 20:46:45 +02:00
|
|
|
var root = filepath.Dir(artifact.Path)
|
2018-05-23 14:30:35 +02:00
|
|
|
var dockerfile = filepath.Join(root, filepath.Base(docker.Dockerfile)) + fmt.Sprintf(".%d", seed)
|
2018-01-18 21:40:44 +02:00
|
|
|
var images []string
|
|
|
|
for _, tagTemplate := range docker.TagTemplates {
|
|
|
|
tag, err := tagName(ctx, tagTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to execute tag template '%s'", tagTemplate)
|
|
|
|
}
|
|
|
|
images = append(images, fmt.Sprintf("%s:%s", docker.Image, tag))
|
2017-12-05 15:19:44 +02:00
|
|
|
}
|
2017-09-12 12:54:43 +02:00
|
|
|
if err := os.Link(docker.Dockerfile, dockerfile); err != nil {
|
|
|
|
return errors.Wrap(err, "failed to link dockerfile")
|
2017-09-12 04:31:00 +02:00
|
|
|
}
|
2017-09-26 00:10:04 +02:00
|
|
|
for _, file := range docker.Files {
|
2017-12-20 23:08:35 +02:00
|
|
|
if err := link(file, filepath.Join(root, filepath.Base(file))); err != nil {
|
2017-09-26 00:10:04 +02:00
|
|
|
return errors.Wrapf(err, "failed to link extra file '%s'", file)
|
|
|
|
}
|
|
|
|
}
|
2018-01-18 21:40:44 +02:00
|
|
|
if err := dockerBuild(ctx, root, dockerfile, images[0]); err != nil {
|
2017-09-12 04:31:00 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-01-18 21:40:44 +02:00
|
|
|
for _, img := range images[1:] {
|
|
|
|
if err := dockerTag(ctx, images[0], img); err != nil {
|
2017-09-15 01:16:49 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-01-18 21:40:44 +02:00
|
|
|
return publish(ctx, docker, images)
|
2017-09-26 23:50:00 +02:00
|
|
|
}
|
|
|
|
|
2017-12-24 14:12:51 +02:00
|
|
|
// walks the src, recreating dirs and hard-linking files
|
2017-12-20 23:08:35 +02:00
|
|
|
func link(src, dest string) error {
|
2017-12-24 14:12:51 +02:00
|
|
|
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
|
2017-12-26 18:56:20 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-12-24 14:12:51 +02:00
|
|
|
// We have the following:
|
|
|
|
// - src = "a/b"
|
|
|
|
// - dest = "dist/linuxamd64/b"
|
|
|
|
// - path = "a/b/c.txt"
|
|
|
|
// So we join "a/b" with "c.txt" and use it as the destination.
|
|
|
|
var dst = filepath.Join(dest, strings.Replace(path, src, "", 1))
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"src": path,
|
|
|
|
"dst": dst,
|
|
|
|
}).Info("extra file")
|
2017-12-20 23:08:35 +02:00
|
|
|
if info.IsDir() {
|
2017-12-24 14:12:51 +02:00
|
|
|
return os.MkdirAll(dst, info.Mode())
|
2017-12-20 23:08:35 +02:00
|
|
|
}
|
2017-12-24 14:12:51 +02:00
|
|
|
return os.Link(path, dst)
|
|
|
|
})
|
2017-12-20 23:08:35 +02:00
|
|
|
}
|
|
|
|
|
2018-01-18 21:40:44 +02:00
|
|
|
func publish(ctx *context.Context, docker config.Docker, images []string) error {
|
2018-03-01 06:12:58 +02:00
|
|
|
if ctx.SkipPublish {
|
2018-02-24 22:59:08 +02:00
|
|
|
// TODO: this should be better handled
|
2018-03-01 06:12:58 +02:00
|
|
|
log.Warn(pipeline.ErrSkipPublishEnabled.Error())
|
2017-12-26 03:27:06 +02:00
|
|
|
return nil
|
2017-09-12 05:29:12 +02:00
|
|
|
}
|
2018-03-24 23:41:24 +02:00
|
|
|
if docker.SkipPush {
|
|
|
|
// TODO: this should also be better handled
|
|
|
|
log.Warn(pipeline.Skip("skip_push is set").Error())
|
|
|
|
return nil
|
|
|
|
}
|
2018-01-18 21:40:44 +02:00
|
|
|
for _, image := range images {
|
|
|
|
if err := dockerPush(ctx, docker, image); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-09-22 14:26:19 +02:00
|
|
|
}
|
2018-01-18 21:40:44 +02:00
|
|
|
return nil
|
2017-09-12 04:31:00 +02:00
|
|
|
}
|
|
|
|
|
2017-12-29 21:07:06 +02:00
|
|
|
func dockerBuild(ctx *context.Context, root, dockerfile, image string) error {
|
2017-09-12 04:42:36 +02:00
|
|
|
log.WithField("image", image).Info("building docker image")
|
2017-11-26 16:09:12 +02:00
|
|
|
/* #nosec */
|
2017-12-29 21:07:06 +02:00
|
|
|
var cmd = exec.CommandContext(ctx, "docker", "build", "-f", dockerfile, "-t", image, root)
|
2018-02-13 18:09:03 +02:00
|
|
|
log.WithField("cmd", cmd.Args).Debug("running")
|
2017-09-12 04:31:00 +02:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to build docker image: \n%s", string(out))
|
|
|
|
}
|
|
|
|
log.Debugf("docker build output: \n%s", string(out))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-29 21:07:06 +02:00
|
|
|
func dockerTag(ctx *context.Context, image, tag string) error {
|
2017-09-15 02:35:11 +02:00
|
|
|
log.WithField("image", image).WithField("tag", tag).Info("tagging docker image")
|
2017-11-26 16:09:12 +02:00
|
|
|
/* #nosec */
|
2017-12-29 21:07:06 +02:00
|
|
|
var cmd = exec.CommandContext(ctx, "docker", "tag", image, tag)
|
2018-02-13 18:09:03 +02:00
|
|
|
log.WithField("cmd", cmd.Args).Debug("running")
|
2017-09-15 01:16:49 +02:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to tag docker image: \n%s", string(out))
|
|
|
|
}
|
|
|
|
log.Debugf("docker tag output: \n%s", string(out))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-12-29 19:07:15 +02:00
|
|
|
func dockerPush(ctx *context.Context, docker config.Docker, image string) error {
|
2017-09-12 04:42:36 +02:00
|
|
|
log.WithField("image", image).Info("pushing docker image")
|
2017-11-26 16:09:12 +02:00
|
|
|
/* #nosec */
|
2017-12-29 21:07:06 +02:00
|
|
|
var cmd = exec.CommandContext(ctx, "docker", "push", image)
|
2018-02-13 18:09:03 +02:00
|
|
|
log.WithField("cmd", cmd.Args).Debug("running")
|
2017-09-12 04:31:00 +02:00
|
|
|
out, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrapf(err, "failed to push docker image: \n%s", string(out))
|
|
|
|
}
|
|
|
|
log.Debugf("docker push output: \n%s", string(out))
|
2017-12-17 20:46:45 +02:00
|
|
|
ctx.Artifacts.Add(artifact.Artifact{
|
2017-12-29 19:07:15 +02:00
|
|
|
Type: artifact.DockerImage,
|
|
|
|
Name: image,
|
|
|
|
Path: image,
|
|
|
|
Goarch: docker.Goarch,
|
|
|
|
Goos: docker.Goos,
|
|
|
|
Goarm: docker.Goarm,
|
2017-12-17 20:46:45 +02:00
|
|
|
})
|
2017-09-12 04:31:00 +02:00
|
|
|
return nil
|
|
|
|
}
|