github.com/goreleaser/goreleaser@v1.25.1/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/goreleaser/goreleaser/internal/artifact"
    14  	"github.com/goreleaser/goreleaser/internal/extrafiles"
    15  	"github.com/goreleaser/goreleaser/internal/gio"
    16  	"github.com/goreleaser/goreleaser/internal/logext"
    17  	"github.com/goreleaser/goreleaser/internal/pipe"
    18  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    19  	"github.com/goreleaser/goreleaser/internal/tmpl"
    20  	"github.com/goreleaser/goreleaser/pkg/config"
    21  	"github.com/goreleaser/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("artifact", artifact.Name).
    90  		Debug("executing command")
    91  
    92  	// nolint: gosec
    93  	cmd := exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...)
    94  	cmd.Env = []string{}
    95  	for _, key := range passthroughEnvVars {
    96  		if value := os.Getenv(key); value != "" {
    97  			cmd.Env = append(cmd.Env, key+"="+value)
    98  		}
    99  	}
   100  	cmd.Env = append(cmd.Env, c.Env...)
   101  
   102  	if c.Dir != "" {
   103  		cmd.Dir = c.Dir
   104  	}
   105  
   106  	var b bytes.Buffer
   107  	w := gio.Safe(&b)
   108  	cmd.Stderr = io.MultiWriter(logext.NewWriter(), w)
   109  	cmd.Stdout = io.MultiWriter(logext.NewWriter(), w)
   110  
   111  	log := log.WithField("cmd", c.Args[0]).
   112  		WithField("artifact", artifact.Name)
   113  
   114  	log.Info("publishing")
   115  	if err := cmd.Run(); err != nil {
   116  		return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String())
   117  	}
   118  
   119  	log.Debug("command finished successfully")
   120  	return nil
   121  }
   122  
   123  func filterArtifacts(artifacts *artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact {
   124  	filters := []artifact.Filter{
   125  		artifact.ByType(artifact.UploadableArchive),
   126  		artifact.ByType(artifact.UploadableFile),
   127  		artifact.ByType(artifact.LinuxPackage),
   128  		artifact.ByType(artifact.UploadableBinary),
   129  		artifact.ByType(artifact.DockerImage),
   130  		artifact.ByType(artifact.DockerManifest),
   131  	}
   132  
   133  	if publisher.Checksum {
   134  		filters = append(filters, artifact.ByType(artifact.Checksum))
   135  	}
   136  
   137  	if publisher.Meta {
   138  		filters = append(filters, artifact.ByType(artifact.Metadata))
   139  	}
   140  
   141  	if publisher.Signature {
   142  		filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
   143  	}
   144  
   145  	filter := artifact.Or(filters...)
   146  
   147  	if len(publisher.IDs) > 0 {
   148  		filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...))
   149  	}
   150  
   151  	return artifacts.Filter(filter).List()
   152  }
   153  
   154  type command struct {
   155  	Ctx  *context.Context
   156  	Dir  string
   157  	Env  []string
   158  	Args []string
   159  }
   160  
   161  // resolveCommand returns the a command based on publisher template with replaced variables
   162  // Those variables can be replaced by the given context, goos, goarch, goarm and more.
   163  func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) {
   164  	var err error
   165  	dir := publisher.Dir
   166  
   167  	// nolint:staticcheck
   168  	tpl := tmpl.New(ctx).WithArtifact(artifact)
   169  	if dir != "" {
   170  		dir, err = tpl.Apply(dir)
   171  		if err != nil {
   172  			return nil, err
   173  		}
   174  	}
   175  
   176  	cmd := publisher.Cmd
   177  	if cmd != "" {
   178  		cmd, err = tpl.Apply(cmd)
   179  		if err != nil {
   180  			return nil, err
   181  		}
   182  	}
   183  
   184  	args, err := shellwords.Parse(cmd)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	env := make([]string, len(publisher.Env))
   190  	for i, e := range publisher.Env {
   191  		e, err = tpl.Apply(e)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		env[i] = e
   196  	}
   197  
   198  	return &command{
   199  		Ctx:  ctx,
   200  		Dir:  dir,
   201  		Env:  env,
   202  		Args: args,
   203  	}, nil
   204  }