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

     1  package sign
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/apex/log"
    12  	"github.com/goreleaser/goreleaser/internal/artifact"
    13  	"github.com/goreleaser/goreleaser/internal/ids"
    14  	"github.com/goreleaser/goreleaser/internal/logext"
    15  	"github.com/goreleaser/goreleaser/internal/pipe"
    16  	"github.com/goreleaser/goreleaser/internal/semerrgroup"
    17  	"github.com/goreleaser/goreleaser/internal/tmpl"
    18  	"github.com/goreleaser/goreleaser/pkg/config"
    19  	"github.com/goreleaser/goreleaser/pkg/context"
    20  )
    21  
    22  // Pipe for artifact signing.
    23  type Pipe struct{}
    24  
    25  func (Pipe) String() string {
    26  	return "signing artifacts"
    27  }
    28  
    29  // Default sets the Pipes defaults.
    30  func (Pipe) Default(ctx *context.Context) error {
    31  	var ids = ids.New("signs")
    32  	for i := range ctx.Config.Signs {
    33  		cfg := &ctx.Config.Signs[i]
    34  		if cfg.Cmd == "" {
    35  			cfg.Cmd = "gpg"
    36  		}
    37  		if cfg.Signature == "" {
    38  			cfg.Signature = "${artifact}.sig"
    39  		}
    40  		if len(cfg.Args) == 0 {
    41  			cfg.Args = []string{"--output", "$signature", "--detach-sig", "$artifact"}
    42  		}
    43  		if cfg.Artifacts == "" {
    44  			cfg.Artifacts = "none"
    45  		}
    46  		if cfg.ID == "" {
    47  			cfg.ID = "default"
    48  		}
    49  		ids.Inc(cfg.ID)
    50  	}
    51  	return ids.Validate()
    52  }
    53  
    54  // Run executes the Pipe.
    55  func (Pipe) Run(ctx *context.Context) error {
    56  	if ctx.SkipSign {
    57  		return pipe.ErrSkipSignEnabled
    58  	}
    59  
    60  	var g = semerrgroup.New(ctx.Parallelism)
    61  	for i := range ctx.Config.Signs {
    62  		cfg := ctx.Config.Signs[i]
    63  		g.Go(func() error {
    64  			var filters []artifact.Filter
    65  			switch cfg.Artifacts {
    66  			case "checksum":
    67  				filters = append(filters, artifact.ByType(artifact.Checksum))
    68  				if len(cfg.IDs) > 0 {
    69  					log.Warn("when artifacts is `checksum`, `ids` has no effect. ignoring")
    70  				}
    71  			case "source":
    72  				filters = append(filters, artifact.ByType(artifact.UploadableSourceArchive))
    73  				if len(cfg.IDs) > 0 {
    74  					log.Warn("when artifacts is `source`, `ids` has no effect. ignoring")
    75  				}
    76  			case "all":
    77  				filters = append(filters, artifact.Or(
    78  					artifact.ByType(artifact.UploadableArchive),
    79  					artifact.ByType(artifact.UploadableBinary),
    80  					artifact.ByType(artifact.UploadableSourceArchive),
    81  					artifact.ByType(artifact.Checksum),
    82  					artifact.ByType(artifact.LinuxPackage),
    83  				))
    84  				if len(cfg.IDs) > 0 {
    85  					filters = append(filters, artifact.ByIDs(cfg.IDs...))
    86  				}
    87  			case "none":
    88  				return pipe.ErrSkipSignEnabled
    89  			default:
    90  				return fmt.Errorf("invalid list of artifacts to sign: %s", cfg.Artifacts)
    91  			}
    92  			return sign(ctx, cfg, ctx.Artifacts.Filter(artifact.And(filters...)).List())
    93  		})
    94  	}
    95  	return g.Wait()
    96  }
    97  
    98  func sign(ctx *context.Context, cfg config.Sign, artifacts []*artifact.Artifact) error {
    99  	for _, a := range artifacts {
   100  		artifact, err := signone(ctx, cfg, a)
   101  		if err != nil {
   102  			return err
   103  		}
   104  		ctx.Artifacts.Add(artifact)
   105  	}
   106  	return nil
   107  }
   108  
   109  func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*artifact.Artifact, error) {
   110  	env := ctx.Env.Copy()
   111  	env["artifact"] = a.Path
   112  	env["signature"] = expand(cfg.Signature, env)
   113  
   114  	// nolint:prealloc
   115  	var args []string
   116  	for _, a := range cfg.Args {
   117  		var arg = expand(a, env)
   118  		arg, err := tmpl.New(ctx).WithEnv(env).Apply(arg)
   119  		if err != nil {
   120  			return nil, fmt.Errorf("sign failed: %s: invalid template: %w", a, err)
   121  		}
   122  		args = append(args, arg)
   123  	}
   124  
   125  	var stdin io.Reader
   126  	if cfg.Stdin != nil {
   127  		stdin = strings.NewReader(*cfg.Stdin)
   128  	} else if cfg.StdinFile != "" {
   129  		f, err := os.Open(cfg.StdinFile)
   130  		if err != nil {
   131  			return nil, fmt.Errorf("sign failed: cannot open file %s: %w", cfg.StdinFile, err)
   132  		}
   133  		defer f.Close()
   134  
   135  		stdin = f
   136  	}
   137  
   138  	// The GoASTScanner flags this as a security risk.
   139  	// However, this works as intended. The nosec annotation
   140  	// tells the scanner to ignore this.
   141  	// #nosec
   142  	cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
   143  	cmd.Stderr = logext.NewWriter(log.WithField("cmd", cfg.Cmd))
   144  	cmd.Stdout = cmd.Stderr
   145  	if stdin != nil {
   146  		cmd.Stdin = stdin
   147  	}
   148  	log.WithField("cmd", cmd.Args).Info("signing")
   149  	if err := cmd.Run(); err != nil {
   150  		return nil, fmt.Errorf("sign: %s failed", cfg.Cmd)
   151  	}
   152  
   153  	artifactPathBase, _ := filepath.Split(a.Path)
   154  
   155  	env["artifact"] = a.Name
   156  	name := expand(cfg.Signature, env)
   157  
   158  	sigFilename := filepath.Base(env["signature"])
   159  	return &artifact.Artifact{
   160  		Type: artifact.Signature,
   161  		Name: name,
   162  		Path: filepath.Join(artifactPathBase, sigFilename),
   163  		Extra: map[string]interface{}{
   164  			"ID": cfg.ID,
   165  		},
   166  	}, nil
   167  }
   168  
   169  func expand(s string, env map[string]string) string {
   170  	return os.Expand(s, func(key string) string {
   171  		return env[key]
   172  	})
   173  }