github.com/ahmet2mir/goreleaser@v0.180.3-0.20210927151101-8e5ee5a9b8c5/internal/pipe/docker/docker.go (about)

     1  package docker
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/apex/log"
    12  	"github.com/goreleaser/goreleaser/internal/artifact"
    13  	"github.com/goreleaser/goreleaser/internal/deprecate"
    14  	"github.com/goreleaser/goreleaser/internal/gio"
    15  	"github.com/goreleaser/goreleaser/internal/ids"
    16  	"github.com/goreleaser/goreleaser/internal/pipe"
    17  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    18  	"github.com/goreleaser/goreleaser/internal/tmpl"
    19  	"github.com/goreleaser/goreleaser/pkg/config"
    20  	"github.com/goreleaser/goreleaser/pkg/context"
    21  )
    22  
    23  const (
    24  	dockerConfigExtra = "DockerConfig"
    25  
    26  	useBuildx     = "buildx"
    27  	useDocker     = "docker"
    28  	useBuildPacks = "buildpacks"
    29  )
    30  
    31  // Pipe for docker.
    32  type Pipe struct{}
    33  
    34  func (Pipe) String() string                 { return "docker images" }
    35  func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.Dockers) == 0 }
    36  
    37  // Default sets the pipe defaults.
    38  func (Pipe) Default(ctx *context.Context) error {
    39  	ids := ids.New("dockers")
    40  	for i := range ctx.Config.Dockers {
    41  		docker := &ctx.Config.Dockers[i]
    42  
    43  		if docker.ID != "" {
    44  			ids.Inc(docker.ID)
    45  		}
    46  		if docker.Goos == "" {
    47  			docker.Goos = "linux"
    48  		}
    49  		if docker.Goarch == "" {
    50  			docker.Goarch = "amd64"
    51  		}
    52  		if docker.Dockerfile == "" {
    53  			docker.Dockerfile = "Dockerfile"
    54  		}
    55  		if docker.Buildx {
    56  			deprecate.Notice(ctx, "docker.use_buildx")
    57  			if docker.Use == "" {
    58  				docker.Use = useBuildx
    59  			}
    60  		}
    61  		if docker.Use == "" {
    62  			docker.Use = useDocker
    63  		}
    64  		if err := validateImager(docker.Use); err != nil {
    65  			return err
    66  		}
    67  		for _, f := range docker.Files {
    68  			if f == "." || strings.HasPrefix(f, ctx.Config.Dist) {
    69  				return fmt.Errorf("invalid docker.files: can't be . or inside dist folder: %s", f)
    70  			}
    71  		}
    72  	}
    73  	return ids.Validate()
    74  }
    75  
    76  func validateImager(use string) error {
    77  	valid := make([]string, 0, len(imagers))
    78  	for k := range imagers {
    79  		valid = append(valid, k)
    80  	}
    81  	for _, s := range valid {
    82  		if s == use {
    83  			return nil
    84  		}
    85  	}
    86  	sort.Strings(valid)
    87  	return fmt.Errorf("docker: invalid use: %s, valid options are %v", use, valid)
    88  }
    89  
    90  // Publish the docker images.
    91  func (Pipe) Publish(ctx *context.Context) error {
    92  	images := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableDockerImage)).List()
    93  	for _, image := range images {
    94  		if err := dockerPush(ctx, image); err != nil {
    95  			return err
    96  		}
    97  	}
    98  	return nil
    99  }
   100  
   101  // Run the pipe.
   102  func (Pipe) Run(ctx *context.Context) error {
   103  	g := semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism))
   104  	for _, docker := range ctx.Config.Dockers {
   105  		docker := docker
   106  		g.Go(func() error {
   107  			log.WithField("docker", docker).Debug("looking for artifacts matching")
   108  			filters := []artifact.Filter{
   109  				artifact.ByGoos(docker.Goos),
   110  				artifact.ByGoarch(docker.Goarch),
   111  				artifact.ByGoarm(docker.Goarm),
   112  				artifact.Or(
   113  					artifact.ByType(artifact.Binary),
   114  					artifact.ByType(artifact.LinuxPackage),
   115  				),
   116  			}
   117  			if len(docker.IDs) > 0 {
   118  				filters = append(filters, artifact.ByIDs(docker.IDs...))
   119  			}
   120  			artifacts := ctx.Artifacts.Filter(artifact.And(filters...))
   121  			log.WithField("artifacts", artifacts.Paths()).Debug("found artifacts")
   122  			return process(ctx, docker, artifacts.List())
   123  		})
   124  	}
   125  	return g.Wait()
   126  }
   127  
   128  func process(ctx *context.Context, docker config.Docker, artifacts []*artifact.Artifact) error {
   129  	tmp, err := ioutil.TempDir(ctx.Config.Dist, "goreleaserdocker")
   130  	if err != nil {
   131  		return fmt.Errorf("failed to create temporary dir: %w", err)
   132  	}
   133  
   134  	images, err := processImageTemplates(ctx, docker)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	if len(images) == 0 {
   140  		return pipe.Skip("no image templates found")
   141  	}
   142  
   143  	log := log.WithField("image", images[0])
   144  	log.Debug("tempdir: " + tmp)
   145  
   146  	if docker.Use != useBuildPacks {
   147  		if err := gio.Copy(docker.Dockerfile, filepath.Join(tmp, "Dockerfile")); err != nil {
   148  			return fmt.Errorf("failed to copy dockerfile: %w", err)
   149  		}
   150  	}
   151  	for _, file := range docker.Files {
   152  		if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(file)), 0o755); err != nil {
   153  			return fmt.Errorf("failed to copy extra file '%s': %w", file, err)
   154  		}
   155  		if err := gio.Copy(file, filepath.Join(tmp, file)); err != nil {
   156  			return fmt.Errorf("failed to copy extra file '%s': %w", file, err)
   157  		}
   158  	}
   159  	for _, art := range artifacts {
   160  		if err := gio.Copy(art.Path, filepath.Join(tmp, filepath.Base(art.Path))); err != nil {
   161  			return fmt.Errorf("failed to copy artifact: %w", err)
   162  		}
   163  	}
   164  
   165  	buildFlags, err := processBuildFlagTemplates(ctx, docker)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	log.Info("building docker image")
   171  	if err := imagers[docker.Use].Build(ctx, tmp, images, buildFlags); err != nil {
   172  		return err
   173  	}
   174  
   175  	if strings.TrimSpace(docker.SkipPush) == "true" {
   176  		return pipe.Skip("docker.skip_push is set")
   177  	}
   178  	if ctx.SkipPublish {
   179  		return pipe.ErrSkipPublishEnabled
   180  	}
   181  	if strings.TrimSpace(docker.SkipPush) == "auto" && ctx.Semver.Prerelease != "" {
   182  		return pipe.Skip("prerelease detected with 'auto' push, skipping docker publish")
   183  	}
   184  	for _, img := range images {
   185  		ctx.Artifacts.Add(&artifact.Artifact{
   186  			Type:   artifact.PublishableDockerImage,
   187  			Name:   img,
   188  			Path:   img,
   189  			Goarch: docker.Goarch,
   190  			Goos:   docker.Goos,
   191  			Goarm:  docker.Goarm,
   192  			Extra: map[string]interface{}{
   193  				dockerConfigExtra: docker,
   194  			},
   195  		})
   196  	}
   197  	return nil
   198  }
   199  
   200  func processImageTemplates(ctx *context.Context, docker config.Docker) ([]string, error) {
   201  	// nolint:prealloc
   202  	var images []string
   203  	for _, imageTemplate := range docker.ImageTemplates {
   204  		image, err := tmpl.New(ctx).Apply(imageTemplate)
   205  		if err != nil {
   206  			return nil, fmt.Errorf("failed to execute image template '%s': %w", imageTemplate, err)
   207  		}
   208  		if image == "" {
   209  			continue
   210  		}
   211  
   212  		images = append(images, image)
   213  	}
   214  
   215  	return images, nil
   216  }
   217  
   218  func processBuildFlagTemplates(ctx *context.Context, docker config.Docker) ([]string, error) {
   219  	// nolint:prealloc
   220  	var buildFlags []string
   221  	for _, buildFlagTemplate := range docker.BuildFlagTemplates {
   222  		buildFlag, err := tmpl.New(ctx).Apply(buildFlagTemplate)
   223  		if err != nil {
   224  			return nil, fmt.Errorf("failed to process build flag template '%s': %w", buildFlagTemplate, err)
   225  		}
   226  		buildFlags = append(buildFlags, buildFlag)
   227  	}
   228  	return buildFlags, nil
   229  }
   230  
   231  func dockerPush(ctx *context.Context, image *artifact.Artifact) error {
   232  	log.WithField("image", image.Name).Info("pushing docker image")
   233  	docker := image.Extra[dockerConfigExtra].(config.Docker)
   234  	if err := imagers[docker.Use].Push(ctx, image.Name, docker.PushFlags); err != nil {
   235  		return err
   236  	}
   237  	art := &artifact.Artifact{
   238  		Type:   artifact.DockerImage,
   239  		Name:   image.Name,
   240  		Path:   image.Path,
   241  		Goarch: image.Goarch,
   242  		Goos:   image.Goos,
   243  		Goarm:  image.Goarm,
   244  		Extra:  map[string]interface{}{},
   245  	}
   246  	if docker.ID != "" {
   247  		art.Extra["ID"] = docker.ID
   248  	}
   249  	ctx.Artifacts.Add(art)
   250  	return nil
   251  }