github.com/joselitofilho/goreleaser@v0.155.1-0.20210123221854-e4891856c593/internal/exec/exec.go (about)

     1  package exec
     2  
     3  import (
     4  	"fmt"
     5  	"os/exec"
     6  
     7  	"github.com/apex/log"
     8  	"github.com/goreleaser/goreleaser/internal/artifact"
     9  	"github.com/goreleaser/goreleaser/internal/logext"
    10  	"github.com/goreleaser/goreleaser/internal/pipe"
    11  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    12  	"github.com/goreleaser/goreleaser/internal/tmpl"
    13  	"github.com/goreleaser/goreleaser/pkg/config"
    14  	"github.com/goreleaser/goreleaser/pkg/context"
    15  	"github.com/mattn/go-shellwords"
    16  )
    17  
    18  func Execute(ctx *context.Context, publishers []config.Publisher) error {
    19  	if ctx.SkipPublish {
    20  		return pipe.ErrSkipPublishEnabled
    21  	}
    22  
    23  	for _, p := range publishers {
    24  		log.WithField("name", p.Name).Debug("executing custom publisher")
    25  		err := executePublisher(ctx, p)
    26  		if err != nil {
    27  			return err
    28  		}
    29  	}
    30  
    31  	return nil
    32  }
    33  
    34  func executePublisher(ctx *context.Context, publisher config.Publisher) error {
    35  	log.Debugf("filtering %d artifacts", len(ctx.Artifacts.List()))
    36  	artifacts := filterArtifacts(ctx.Artifacts, publisher)
    37  	log.Debugf("will execute custom publisher with %d artifacts", len(artifacts))
    38  
    39  	var g = semerrgroup.New(ctx.Parallelism)
    40  	for _, artifact := range artifacts {
    41  		artifact := artifact
    42  		g.Go(func() error {
    43  			c, err := resolveCommand(ctx, publisher, artifact)
    44  			if err != nil {
    45  				return err
    46  			}
    47  
    48  			return executeCommand(c)
    49  		})
    50  	}
    51  
    52  	return g.Wait()
    53  }
    54  
    55  func executeCommand(c *command) error {
    56  	log.WithField("args", c.Args).
    57  		WithField("env", c.Env).
    58  		Debug("executing command")
    59  
    60  	// nolint: gosec
    61  	var cmd = exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...)
    62  	cmd.Env = c.Env
    63  	if c.Dir != "" {
    64  		cmd.Dir = c.Dir
    65  	}
    66  
    67  	entry := log.WithField("cmd", c.Args[0])
    68  	cmd.Stderr = logext.NewErrWriter(entry)
    69  	cmd.Stdout = logext.NewWriter(entry)
    70  
    71  	log.WithField("cmd", cmd.Args).Info("publishing")
    72  	if err := cmd.Run(); err != nil {
    73  		return fmt.Errorf("publishing: %s failed: %w",
    74  			c.Args[0], err)
    75  	}
    76  
    77  	log.Debugf("command %s finished successfully", c.Args[0])
    78  	return nil
    79  }
    80  
    81  func filterArtifacts(artifacts artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact {
    82  	filters := []artifact.Filter{
    83  		artifact.ByType(artifact.UploadableArchive),
    84  		artifact.ByType(artifact.UploadableFile),
    85  		artifact.ByType(artifact.LinuxPackage),
    86  		artifact.ByType(artifact.UploadableBinary),
    87  	}
    88  
    89  	if publisher.Checksum {
    90  		filters = append(filters, artifact.ByType(artifact.Checksum))
    91  	}
    92  
    93  	if publisher.Signature {
    94  		filters = append(filters, artifact.ByType(artifact.Signature))
    95  	}
    96  
    97  	var filter = artifact.Or(filters...)
    98  
    99  	if len(publisher.IDs) > 0 {
   100  		filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...))
   101  	}
   102  
   103  	return artifacts.Filter(filter).List()
   104  }
   105  
   106  type command struct {
   107  	Ctx  *context.Context
   108  	Dir  string
   109  	Env  []string
   110  	Args []string
   111  }
   112  
   113  // resolveCommand returns the a command based on publisher template with replaced variables
   114  // Those variables can be replaced by the given context, goos, goarch, goarm and more.
   115  func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) {
   116  	var err error
   117  
   118  	replacements := make(map[string]string)
   119  	// TODO: Replacements should be associated only with relevant artifacts/archives
   120  	archives := ctx.Config.Archives
   121  	if len(archives) > 0 {
   122  		replacements = archives[0].Replacements
   123  	}
   124  
   125  	dir := publisher.Dir
   126  	if dir != "" {
   127  		dir, err = tmpl.New(ctx).
   128  			WithArtifact(artifact, replacements).
   129  			Apply(dir)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  
   135  	cmd := publisher.Cmd
   136  	if cmd != "" {
   137  		cmd, err = tmpl.New(ctx).
   138  			WithArtifact(artifact, replacements).
   139  			Apply(cmd)
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  	}
   144  
   145  	args, err := shellwords.Parse(cmd)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	env := make([]string, len(publisher.Env))
   151  	for i, e := range publisher.Env {
   152  		e, err = tmpl.New(ctx).
   153  			WithArtifact(artifact, replacements).
   154  			Apply(e)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  		env[i] = e
   159  	}
   160  
   161  	return &command{
   162  		Ctx:  ctx,
   163  		Dir:  dir,
   164  		Env:  env,
   165  		Args: args,
   166  	}, nil
   167  }