github.com/goreleaser/goreleaser@v1.25.1/internal/pipe/docker/manifest.go (about)

     1  package docker
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/caarlos0/log"
     9  	"github.com/goreleaser/goreleaser/internal/artifact"
    10  	"github.com/goreleaser/goreleaser/internal/ids"
    11  	"github.com/goreleaser/goreleaser/internal/pipe"
    12  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    13  	"github.com/goreleaser/goreleaser/internal/skips"
    14  	"github.com/goreleaser/goreleaser/internal/tmpl"
    15  	"github.com/goreleaser/goreleaser/pkg/config"
    16  	"github.com/goreleaser/goreleaser/pkg/context"
    17  )
    18  
    19  // ManifestPipe is an implementation for the docker manifest feature,
    20  // allowing to publish multi-arch docker images.
    21  type ManifestPipe struct{}
    22  
    23  func (ManifestPipe) String() string { return "docker manifests" }
    24  
    25  func (ManifestPipe) Skip(ctx *context.Context) bool {
    26  	return len(ctx.Config.DockerManifests) == 0 || skips.Any(ctx, skips.Docker)
    27  }
    28  
    29  func (ManifestPipe) Dependencies(ctx *context.Context) []string {
    30  	var cmds []string
    31  	for _, s := range ctx.Config.DockerManifests {
    32  		switch s.Use {
    33  		case useDocker, useBuildx:
    34  			cmds = append(cmds, "docker")
    35  			// TODO: check buildx
    36  		}
    37  	}
    38  	return cmds
    39  }
    40  
    41  // Default sets the pipe defaults.
    42  func (ManifestPipe) Default(ctx *context.Context) error {
    43  	ids := ids.New("docker_manifests")
    44  	for i := range ctx.Config.DockerManifests {
    45  		manifest := &ctx.Config.DockerManifests[i]
    46  		if manifest.ID != "" {
    47  			ids.Inc(manifest.ID)
    48  		}
    49  		if manifest.Use == "" {
    50  			manifest.Use = useDocker
    51  		}
    52  		if err := validateManifester(manifest.Use); err != nil {
    53  			return err
    54  		}
    55  	}
    56  	return ids.Validate()
    57  }
    58  
    59  // Publish the docker manifests.
    60  func (ManifestPipe) Publish(ctx *context.Context) error {
    61  	g := semerrgroup.NewSkipAware(semerrgroup.New(1))
    62  	for _, manifest := range ctx.Config.DockerManifests {
    63  		manifest := manifest
    64  		g.Go(func() error {
    65  			skip, err := tmpl.New(ctx).Apply(manifest.SkipPush)
    66  			if err != nil {
    67  				return err
    68  			}
    69  			if strings.TrimSpace(skip) == "true" {
    70  				return pipe.Skip("docker_manifest.skip_push is set")
    71  			}
    72  
    73  			if strings.TrimSpace(skip) == "auto" && ctx.Semver.Prerelease != "" {
    74  				return pipe.Skip("prerelease detected with 'auto' push, skipping docker manifest")
    75  			}
    76  
    77  			name, err := manifestName(ctx, manifest)
    78  			if err != nil {
    79  				return err
    80  			}
    81  
    82  			images, err := manifestImages(ctx, manifest)
    83  			if err != nil {
    84  				return err
    85  			}
    86  
    87  			manifester := manifesters[manifest.Use]
    88  
    89  			log.WithField("manifest", name).WithField("images", images).Info("creating")
    90  			if err := manifester.Create(ctx, name, images, manifest.CreateFlags); err != nil {
    91  				return err
    92  			}
    93  			art := &artifact.Artifact{
    94  				Type:  artifact.DockerManifest,
    95  				Name:  name,
    96  				Path:  name,
    97  				Extra: map[string]interface{}{},
    98  			}
    99  			if manifest.ID != "" {
   100  				art.Extra[artifact.ExtraID] = manifest.ID
   101  			}
   102  
   103  			log.WithField("manifest", name).Info("pushing")
   104  			digest, err := manifester.Push(ctx, name, manifest.PushFlags)
   105  			if err != nil {
   106  				return err
   107  			}
   108  			art.Extra[artifact.ExtraDigest] = digest
   109  			ctx.Artifacts.Add(art)
   110  			return nil
   111  		})
   112  	}
   113  	return g.Wait()
   114  }
   115  
   116  func validateManifester(use string) error {
   117  	valid := make([]string, 0, len(manifesters))
   118  	for k := range manifesters {
   119  		valid = append(valid, k)
   120  	}
   121  	for _, s := range valid {
   122  		if s == use {
   123  			return nil
   124  		}
   125  	}
   126  	sort.Strings(valid)
   127  	return fmt.Errorf("docker manifest: invalid use: %s, valid options are %v", use, valid)
   128  }
   129  
   130  func manifestName(ctx *context.Context, manifest config.DockerManifest) (string, error) {
   131  	name, err := tmpl.New(ctx).Apply(manifest.NameTemplate)
   132  	if err != nil {
   133  		return name, err
   134  	}
   135  	if strings.TrimSpace(name) == "" {
   136  		return name, pipe.Skip("manifest name is empty")
   137  	}
   138  	return name, nil
   139  }
   140  
   141  func manifestImages(ctx *context.Context, manifest config.DockerManifest) ([]string, error) {
   142  	artifacts := ctx.Artifacts.Filter(artifact.ByType(artifact.DockerImage)).List()
   143  	imgs := make([]string, 0, len(manifest.ImageTemplates))
   144  	for _, img := range manifest.ImageTemplates {
   145  		str, err := tmpl.New(ctx).Apply(img)
   146  		if err != nil {
   147  			return []string{}, err
   148  		}
   149  		imgs = append(imgs, withDigest(manifest.Use, str, artifacts))
   150  	}
   151  	if strings.TrimSpace(strings.Join(manifest.ImageTemplates, "")) == "" {
   152  		return imgs, pipe.Skip("manifest has no images")
   153  	}
   154  	return imgs, nil
   155  }
   156  
   157  func withDigest(use, name string, images []*artifact.Artifact) string {
   158  	for _, art := range images {
   159  		if art.Name == name {
   160  			if digest := artifact.ExtraOr(*art, artifact.ExtraDigest, ""); digest != "" {
   161  				return name + "@" + digest
   162  			}
   163  			break
   164  		}
   165  	}
   166  	log.Warnf("did not find the digest for %s using %s, defaulting to insecure mode", name, use)
   167  	return name
   168  }