github.com/windmeup/goreleaser@v1.21.95/internal/exec/exec.go (about)

     1  // Package exec can execute commands on the OS.
     2  package exec
     3  
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  
    11  	"github.com/caarlos0/go-shellwords"
    12  	"github.com/caarlos0/log"
    13  	"github.com/windmeup/goreleaser/internal/artifact"
    14  	"github.com/windmeup/goreleaser/internal/extrafiles"
    15  	"github.com/windmeup/goreleaser/internal/gio"
    16  	"github.com/windmeup/goreleaser/internal/logext"
    17  	"github.com/windmeup/goreleaser/internal/pipe"
    18  	"github.com/windmeup/goreleaser/internal/semerrgroup"
    19  	"github.com/windmeup/goreleaser/internal/tmpl"
    20  	"github.com/windmeup/goreleaser/pkg/config"
    21  	"github.com/windmeup/goreleaser/pkg/context"
    22  )
    23  
    24  // Environment variables to pass through to exec
    25  var passthroughEnvVars = []string{"HOME", "USER", "USERPROFILE", "TMPDIR", "TMP", "TEMP", "PATH"}
    26  
    27  // Execute the given publisher
    28  func Execute(ctx *context.Context, publishers []config.Publisher) error {
    29  	skips := pipe.SkipMemento{}
    30  	for _, p := range publishers {
    31  		log.WithField("name", p.Name).Debug("executing custom publisher")
    32  		err := executePublisher(ctx, p)
    33  		if err != nil && pipe.IsSkip(err) {
    34  			skips.Remember(err)
    35  			continue
    36  		}
    37  		if err != nil {
    38  			return err
    39  		}
    40  	}
    41  	return skips.Evaluate()
    42  }
    43  
    44  func executePublisher(ctx *context.Context, publisher config.Publisher) error {
    45  	disabled, err := tmpl.New(ctx).Bool(publisher.Disable)
    46  	if err != nil {
    47  		return err
    48  	}
    49  	if disabled {
    50  		return pipe.Skip("publisher is disabled")
    51  	}
    52  
    53  	log.Debugf("filtering %d artifacts", len(ctx.Artifacts.List()))
    54  	artifacts := filterArtifacts(ctx.Artifacts, publisher)
    55  
    56  	extraFiles, err := extrafiles.Find(ctx, publisher.ExtraFiles)
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	for name, path := range extraFiles {
    62  		artifacts = append(artifacts, &artifact.Artifact{
    63  			Name: name,
    64  			Path: path,
    65  			Type: artifact.UploadableFile,
    66  		})
    67  	}
    68  
    69  	log.Debugf("will execute custom publisher with %d artifacts", len(artifacts))
    70  
    71  	g := semerrgroup.New(ctx.Parallelism)
    72  	for _, artifact := range artifacts {
    73  		artifact := artifact
    74  		g.Go(func() error {
    75  			c, err := resolveCommand(ctx, publisher, artifact)
    76  			if err != nil {
    77  				return err
    78  			}
    79  
    80  			return executeCommand(c, artifact)
    81  		})
    82  	}
    83  
    84  	return g.Wait()
    85  }
    86  
    87  func executeCommand(c *command, artifact *artifact.Artifact) error {
    88  	log.WithField("args", c.Args).
    89  		WithField("env", c.Env).
    90  		WithField("artifact", artifact.Name).
    91  		Debug("executing command")
    92  
    93  	// nolint: gosec
    94  	cmd := exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...)
    95  	cmd.Env = []string{}
    96  	for _, key := range passthroughEnvVars {
    97  		if value := os.Getenv(key); value != "" {
    98  			cmd.Env = append(cmd.Env, key+"="+value)
    99  		}
   100  	}
   101  	cmd.Env = append(cmd.Env, c.Env...)
   102  
   103  	if c.Dir != "" {
   104  		cmd.Dir = c.Dir
   105  	}
   106  
   107  	var b bytes.Buffer
   108  	w := gio.Safe(&b)
   109  	cmd.Stderr = io.MultiWriter(logext.NewWriter(), w)
   110  	cmd.Stdout = io.MultiWriter(logext.NewWriter(), w)
   111  
   112  	log := log.WithField("cmd", c.Args[0]).
   113  		WithField("artifact", artifact.Name)
   114  
   115  	log.Info("publishing")
   116  	if err := cmd.Run(); err != nil {
   117  		return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String())
   118  	}
   119  
   120  	log.Debug("command finished successfully")
   121  	return nil
   122  }
   123  
   124  func filterArtifacts(artifacts *artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact {
   125  	filters := []artifact.Filter{
   126  		artifact.ByType(artifact.UploadableArchive),
   127  		artifact.ByType(artifact.UploadableFile),
   128  		artifact.ByType(artifact.LinuxPackage),
   129  		artifact.ByType(artifact.UploadableBinary),
   130  		artifact.ByType(artifact.DockerImage),
   131  		artifact.ByType(artifact.DockerManifest),
   132  	}
   133  
   134  	if publisher.Checksum {
   135  		filters = append(filters, artifact.ByType(artifact.Checksum))
   136  	}
   137  
   138  	if publisher.Signature {
   139  		filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
   140  	}
   141  
   142  	filter := artifact.Or(filters...)
   143  
   144  	if len(publisher.IDs) > 0 {
   145  		filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...))
   146  	}
   147  
   148  	return artifacts.Filter(filter).List()
   149  }
   150  
   151  type command struct {
   152  	Ctx  *context.Context
   153  	Dir  string
   154  	Env  []string
   155  	Args []string
   156  }
   157  
   158  // resolveCommand returns the a command based on publisher template with replaced variables
   159  // Those variables can be replaced by the given context, goos, goarch, goarm and more.
   160  func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) {
   161  	var err error
   162  	dir := publisher.Dir
   163  
   164  	// nolint:staticcheck
   165  	tpl := tmpl.New(ctx).WithArtifact(artifact)
   166  	if dir != "" {
   167  		dir, err = tpl.Apply(dir)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  	}
   172  
   173  	cmd := publisher.Cmd
   174  	if cmd != "" {
   175  		cmd, err = tpl.Apply(cmd)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  	}
   180  
   181  	args, err := shellwords.Parse(cmd)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	env := make([]string, len(publisher.Env))
   187  	for i, e := range publisher.Env {
   188  		e, err = tpl.Apply(e)
   189  		if err != nil {
   190  			return nil, err
   191  		}
   192  		env[i] = e
   193  	}
   194  
   195  	return &command{
   196  		Ctx:  ctx,
   197  		Dir:  dir,
   198  		Env:  env,
   199  		Args: args,
   200  	}, nil
   201  }