mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-04 03:11:55 +02:00
2634fbdad4
I have no idea why this never happened before... the lock was ineffective in `artifacts.List`, which should have caused at least some race condition at some point. Anyway, got it once locally while working on another feature, and couldn't believe my eyes. Fixed, thank goodness! Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
200 lines
4.9 KiB
Go
200 lines
4.9 KiB
Go
// Package exec can execute commands on the OS.
|
|
package exec
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
|
|
"github.com/caarlos0/go-shellwords"
|
|
"github.com/caarlos0/log"
|
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
|
"github.com/goreleaser/goreleaser/internal/extrafiles"
|
|
"github.com/goreleaser/goreleaser/internal/gio"
|
|
"github.com/goreleaser/goreleaser/internal/logext"
|
|
"github.com/goreleaser/goreleaser/internal/semerrgroup"
|
|
"github.com/goreleaser/goreleaser/internal/tmpl"
|
|
"github.com/goreleaser/goreleaser/pkg/config"
|
|
"github.com/goreleaser/goreleaser/pkg/context"
|
|
)
|
|
|
|
// Environment variables to pass through to exec
|
|
var passthroughEnvVars = []string{"HOME", "USER", "USERPROFILE", "TMPDIR", "TMP", "TEMP", "PATH"}
|
|
|
|
// Execute the given publisher
|
|
func Execute(ctx *context.Context, publishers []config.Publisher) error {
|
|
for _, p := range publishers {
|
|
log.WithField("name", p.Name).Debug("executing custom publisher")
|
|
err := executePublisher(ctx, p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func executePublisher(ctx *context.Context, publisher config.Publisher) error {
|
|
log.Debugf("filtering %d artifacts", len(ctx.Artifacts.List()))
|
|
artifacts := filterArtifacts(ctx.Artifacts, publisher)
|
|
|
|
extraFiles, err := extrafiles.Find(ctx, publisher.ExtraFiles)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for name, path := range extraFiles {
|
|
artifacts = append(artifacts, &artifact.Artifact{
|
|
Name: name,
|
|
Path: path,
|
|
Type: artifact.UploadableFile,
|
|
})
|
|
}
|
|
|
|
log.Debugf("will execute custom publisher with %d artifacts", len(artifacts))
|
|
|
|
g := semerrgroup.New(ctx.Parallelism)
|
|
for _, artifact := range artifacts {
|
|
artifact := artifact
|
|
g.Go(func() error {
|
|
c, err := resolveCommand(ctx, publisher, artifact)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return executeCommand(c, artifact)
|
|
})
|
|
}
|
|
|
|
return g.Wait()
|
|
}
|
|
|
|
func executeCommand(c *command, artifact *artifact.Artifact) error {
|
|
log.WithField("args", c.Args).
|
|
WithField("env", c.Env).
|
|
WithField("artifact", artifact.Name).
|
|
Debug("executing command")
|
|
|
|
// nolint: gosec
|
|
cmd := exec.CommandContext(c.Ctx, c.Args[0], c.Args[1:]...)
|
|
cmd.Env = []string{}
|
|
for _, key := range passthroughEnvVars {
|
|
if value := os.Getenv(key); value != "" {
|
|
cmd.Env = append(cmd.Env, key+"="+value)
|
|
}
|
|
}
|
|
cmd.Env = append(cmd.Env, c.Env...)
|
|
|
|
if c.Dir != "" {
|
|
cmd.Dir = c.Dir
|
|
}
|
|
|
|
fields := log.Fields{
|
|
"cmd": c.Args[0],
|
|
"artifact": artifact.Name,
|
|
}
|
|
var b bytes.Buffer
|
|
w := gio.Safe(&b)
|
|
cmd.Stderr = io.MultiWriter(logext.NewWriter(), w)
|
|
cmd.Stdout = io.MultiWriter(logext.NewWriter(), w)
|
|
|
|
log.WithFields(fields).Info("publishing")
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String())
|
|
}
|
|
|
|
log.WithFields(fields).Debugf("command %s finished successfully", c.Args[0])
|
|
return nil
|
|
}
|
|
|
|
func filterArtifacts(artifacts *artifact.Artifacts, publisher config.Publisher) []*artifact.Artifact {
|
|
filters := []artifact.Filter{
|
|
artifact.ByType(artifact.UploadableArchive),
|
|
artifact.ByType(artifact.UploadableFile),
|
|
artifact.ByType(artifact.LinuxPackage),
|
|
artifact.ByType(artifact.UploadableBinary),
|
|
artifact.ByType(artifact.DockerImage),
|
|
artifact.ByType(artifact.DockerManifest),
|
|
}
|
|
|
|
if publisher.Checksum {
|
|
filters = append(filters, artifact.ByType(artifact.Checksum))
|
|
}
|
|
|
|
if publisher.Signature {
|
|
filters = append(filters, artifact.ByType(artifact.Signature), artifact.ByType(artifact.Certificate))
|
|
}
|
|
|
|
filter := artifact.Or(filters...)
|
|
|
|
if len(publisher.IDs) > 0 {
|
|
filter = artifact.And(filter, artifact.ByIDs(publisher.IDs...))
|
|
}
|
|
|
|
return artifacts.Filter(filter).List()
|
|
}
|
|
|
|
type command struct {
|
|
Ctx *context.Context
|
|
Dir string
|
|
Env []string
|
|
Args []string
|
|
}
|
|
|
|
// resolveCommand returns the a command based on publisher template with replaced variables
|
|
// Those variables can be replaced by the given context, goos, goarch, goarm and more.
|
|
func resolveCommand(ctx *context.Context, publisher config.Publisher, artifact *artifact.Artifact) (*command, error) {
|
|
var err error
|
|
|
|
replacements := make(map[string]string)
|
|
// TODO: Replacements should be associated only with relevant artifacts/archives
|
|
// this is pretty much all wrong and will be removed soon.
|
|
archives := ctx.Config.Archives
|
|
if len(archives) > 0 {
|
|
replacements = archives[0].Replacements
|
|
}
|
|
|
|
dir := publisher.Dir
|
|
|
|
// nolint:staticcheck
|
|
tpl := tmpl.New(ctx).
|
|
WithArtifactReplacements(artifact, replacements)
|
|
if dir != "" {
|
|
dir, err = tpl.Apply(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
cmd := publisher.Cmd
|
|
if cmd != "" {
|
|
cmd, err = tpl.Apply(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
args, err := shellwords.Parse(cmd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
env := make([]string, len(publisher.Env))
|
|
for i, e := range publisher.Env {
|
|
e, err = tpl.Apply(e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
env[i] = e
|
|
}
|
|
|
|
return &command{
|
|
Ctx: ctx,
|
|
Dir: dir,
|
|
Env: env,
|
|
Args: args,
|
|
}, nil
|
|
}
|