1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2024-12-29 01:44:39 +02:00
goreleaser/internal/exec/exec.go
Carlos Alexandro Becker ec2db4a727
feat!: rename module to /v2 (#4894)
<!--

Hi, thanks for contributing!

Please make sure you read our CONTRIBUTING guide.

Also, add tests and the respective documentation changes as well.

-->


<!-- If applied, this commit will... -->

...

<!-- Why is this change being made? -->

...

<!-- # Provide links to any relevant tickets, URLs or other resources
-->

...

---------

Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2024-05-26 15:02:57 -03:00

203 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/v2/internal/artifact"
"github.com/goreleaser/goreleaser/v2/internal/extrafiles"
"github.com/goreleaser/goreleaser/v2/internal/gio"
"github.com/goreleaser/goreleaser/v2/internal/logext"
"github.com/goreleaser/goreleaser/v2/internal/pipe"
"github.com/goreleaser/goreleaser/v2/internal/semerrgroup"
"github.com/goreleaser/goreleaser/v2/internal/tmpl"
"github.com/goreleaser/goreleaser/v2/pkg/config"
"github.com/goreleaser/goreleaser/v2/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 {
skips := pipe.SkipMemento{}
for _, p := range publishers {
log.WithField("name", p.Name).Debug("executing custom publisher")
err := executePublisher(ctx, p)
if err != nil && pipe.IsSkip(err) {
skips.Remember(err)
continue
}
if err != nil {
return err
}
}
return skips.Evaluate()
}
func executePublisher(ctx *context.Context, publisher config.Publisher) error {
disabled, err := tmpl.New(ctx).Bool(publisher.Disable)
if err != nil {
return err
}
if disabled {
return pipe.Skip("publisher is disabled")
}
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 {
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("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
}
var b bytes.Buffer
w := gio.Safe(&b)
cmd.Stderr = io.MultiWriter(logext.NewWriter(), w)
cmd.Stdout = io.MultiWriter(logext.NewWriter(), w)
log := log.WithField("cmd", c.Args[0]).
WithField("artifact", artifact.Name)
log.Info("publishing")
if err := cmd.Run(); err != nil {
return fmt.Errorf("publishing: %s failed: %w: %s", c.Args[0], err, b.String())
}
log.Debug("command finished successfully")
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.Meta {
filters = append(filters, artifact.ByType(artifact.Metadata))
}
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
dir := publisher.Dir
tpl := tmpl.New(ctx).WithArtifact(artifact)
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
}