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