1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-10 03:47:03 +02:00
goreleaser/internal/pipe/docker/docker.go

284 lines
8.1 KiB
Go
Raw Normal View History

2017-09-12 04:31:00 +02:00
// Package docker provides a Pipe that creates and pushes a Docker image
package docker
import (
"fmt"
"io/ioutil"
2017-09-12 12:54:43 +02:00
"os"
2017-09-12 04:31:00 +02:00
"os/exec"
"path/filepath"
"regexp"
"strings"
2017-09-12 04:31:00 +02:00
"github.com/apex/log"
"github.com/pkg/errors"
2017-12-18 03:11:17 +02:00
"github.com/goreleaser/goreleaser/internal/artifact"
2018-09-12 19:18:01 +02:00
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/internal/semerrgroup"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
2017-09-12 04:31:00 +02:00
)
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{}
func (Pipe) String() string {
return "creating Docker images"
2017-09-12 04:31:00 +02:00
}
// Default sets the pipe defaults
func (Pipe) Default(ctx *context.Context) error {
for i := range ctx.Config.Dockers {
2017-12-17 22:10:38 +02:00
var docker = &ctx.Config.Dockers[i]
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"
}
}
// only set defaults if there is exactly 1 docker setup in the config file.
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
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
if len(ctx.Config.Dockers) == 0 || ctx.Config.Dockers[0].Image == "" {
2018-09-12 19:18:01 +02:00
return pipe.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 {
var g = semerrgroup.New(ctx.Parallelism)
for _, docker := range ctx.Config.Dockers {
2017-12-26 03:27:06 +02:00
docker := docker
g.Go(func() error {
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) != 1 {
return fmt.Errorf(
"%d binaries match docker definition: %s: %s_%s_%s",
len(binaries),
docker.Binary, docker.Goos, docker.Goarch, docker.Goarm,
)
2017-09-12 04:31:00 +02:00
}
return process(ctx, docker, binaries[0])
2017-12-26 03:27:06 +02:00
})
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
}
func process(ctx *context.Context, docker config.Docker, artifact artifact.Artifact) error {
tmp, err := ioutil.TempDir(ctx.Config.Dist, "goreleaserdocker")
if err != nil {
return errors.Wrap(err, "failed to create temporary dir")
}
2018-09-04 14:26:45 +02:00
log.Debug("tempdir: " + tmp)
images, err := processTagTemplates(ctx, docker, artifact)
if err != nil {
return err
}
if err := os.Link(docker.Dockerfile, filepath.Join(tmp, "Dockerfile")); err != nil {
2017-09-12 12:54:43 +02:00
return errors.Wrap(err, "failed to link dockerfile")
2017-09-12 04:31:00 +02:00
}
for _, file := range docker.Files {
if err := link(file, filepath.Join(tmp, filepath.Base(file))); err != nil {
return errors.Wrapf(err, "failed to link extra file '%s'", file)
}
}
if err := os.Link(artifact.Path, filepath.Join(tmp, filepath.Base(artifact.Path))); err != nil {
return errors.Wrap(err, "failed to link binary")
}
buildFlags, err := processBuildFlagTemplates(ctx, docker, artifact)
if err != nil {
return err
}
if err := dockerBuild(ctx, tmp, images[0], buildFlags); err != nil {
2017-09-12 04:31:00 +02:00
return err
}
for _, img := range images[1:] {
if err := dockerTag(ctx, images[0], img); err != nil {
2017-09-15 01:16:49 +02:00
return err
}
}
return publish(ctx, docker, images)
2017-09-26 23:50:00 +02:00
}
func processTagTemplates(ctx *context.Context, docker config.Docker, artifact artifact.Artifact) ([]string, error) {
baseRegistry, baseImage := parseRegistry(docker.Image)
registries := []string{baseRegistry}
for _, addRegistry := range docker.AdditionalRegistries {
registries = append(registries, fmt.Sprintf("%s/", addRegistry))
}
// nolint:prealloc
var images []string
for _, tagTemplate := range docker.TagTemplates {
for _, registry := range registries {
imageTemplate := fmt.Sprintf("%s%s:%s", registry, baseImage, tagTemplate)
// TODO: add overrides support to config
image, err := tmpl.New(ctx).
WithArtifact(artifact, map[string]string{}).
Apply(imageTemplate)
if err != nil {
return nil, errors.Wrapf(err, "failed to execute tag template '%s'", tagTemplate)
}
images = append(images, image)
}
}
return images, nil
}
func processBuildFlagTemplates(ctx *context.Context, docker config.Docker, artifact artifact.Artifact) ([]string, error) {
// nolint:prealloc
var buildFlags []string
for _, buildFlagTemplate := range docker.BuildFlagTemplates {
buildFlag, err := tmpl.New(ctx).
WithArtifact(artifact, map[string]string{}).
Apply(buildFlagTemplate)
if err != nil {
return nil, errors.Wrapf(err, "failed to process build flag template '%s'", buildFlagTemplate)
}
buildFlags = append(buildFlags, buildFlag)
}
return buildFlags, nil
}
// walks the src, recreating dirs and hard-linking files
func link(src, dest string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 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,
2018-10-05 14:49:47 +02:00
}).Debug("extra file")
if info.IsDir() {
return os.MkdirAll(dst, info.Mode())
}
return os.Link(path, dst)
})
}
func publish(ctx *context.Context, docker config.Docker, images []string) error {
if ctx.SkipPublish {
// TODO: this should be better handled
2018-09-12 19:18:01 +02:00
log.Warn(pipe.ErrSkipPublishEnabled.Error())
2017-12-26 03:27:06 +02:00
return nil
2017-09-12 05:29:12 +02:00
}
if docker.SkipPush {
// TODO: this should also be better handled
2018-09-12 19:18:01 +02:00
log.Warn(pipe.Skip("skip_push is set").Error())
return nil
}
for _, image := range images {
if err := dockerPush(ctx, docker, image); err != nil {
return err
}
2017-09-22 14:26:19 +02:00
}
return nil
2017-09-12 04:31:00 +02:00
}
func dockerBuild(ctx *context.Context, root, image string, flags []string) error {
2017-09-12 04:42:36 +02:00
log.WithField("image", image).Info("building docker image")
/* #nosec */
var cmd = exec.CommandContext(ctx, "docker", buildCommand(image, flags)...)
cmd.Dir = root
log.WithField("cmd", cmd.Args).WithField("cwd", cmd.Dir).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
}
func buildCommand(image string, flags []string) []string {
base := []string{"build", "-t", image, "."}
base = append(base, flags...)
return base
}
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")
/* #nosec */
2017-12-29 21:07:06 +02:00
var cmd = exec.CommandContext(ctx, "docker", "tag", image, tag)
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
}
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")
/* #nosec */
2017-12-29 21:07:06 +02:00
var cmd = exec.CommandContext(ctx, "docker", "push", image)
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))
ctx.Artifacts.Add(artifact.Artifact{
Type: artifact.DockerImage,
Name: image,
Path: image,
Goarch: docker.Goarch,
Goos: docker.Goos,
Goarm: docker.Goarm,
})
2017-09-12 04:31:00 +02:00
return nil
}
func parseRegistry(image string) (string, string) {
registryMatcher := regexp.MustCompile(`(\w+[.:]\w+/)?(.*)`)
result := registryMatcher.FindStringSubmatch(image)
return result[1], result[2]
}