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