You've already forked goreleaser
mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-11-23 22:36:11 +02:00
feat: make goreleaser timeoutable
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
ctx "context"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||
@@ -41,8 +42,18 @@ type Context struct {
|
||||
|
||||
// New context
|
||||
func New(config config.Project) *Context {
|
||||
return wrap(ctx.Background(), config)
|
||||
}
|
||||
|
||||
// NewWithTimeout new context with the given timeout
|
||||
func NewWithTimeout(config config.Project, timeout time.Duration) (*Context, ctx.CancelFunc) {
|
||||
ctx, cancel := ctx.WithTimeout(ctx.Background(), timeout)
|
||||
return wrap(ctx, config), cancel
|
||||
}
|
||||
|
||||
func wrap(ctx ctx.Context, config config.Project) *Context {
|
||||
return &Context{
|
||||
Context: ctx.Background(),
|
||||
Context: ctx,
|
||||
Config: config,
|
||||
Env: splitEnv(os.Environ()),
|
||||
Parallelism: 4,
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/apex/log/handlers/cli"
|
||||
@@ -65,6 +68,7 @@ type Flags interface {
|
||||
String(s string) string
|
||||
Int(s string) int
|
||||
Bool(s string) bool
|
||||
Duration(s string) time.Duration
|
||||
}
|
||||
|
||||
// Release runs the release process with the given flags
|
||||
@@ -84,7 +88,8 @@ func Release(flags Flags) error {
|
||||
}
|
||||
log.WithField("file", file).Warn("could not load config, using defaults")
|
||||
}
|
||||
var ctx = context.New(cfg)
|
||||
ctx, cancel := context.NewWithTimeout(cfg, flags.Duration("timeout"))
|
||||
defer cancel()
|
||||
ctx.Parallelism = flags.Int("parallelism")
|
||||
ctx.Debug = flags.Bool("debug")
|
||||
log.Debugf("parallelism: %v", ctx.Parallelism)
|
||||
@@ -105,16 +110,36 @@ func Release(flags Flags) error {
|
||||
ctx.Publish = false
|
||||
}
|
||||
ctx.RmDist = flags.Bool("rm-dist")
|
||||
for _, pipe := range pipes {
|
||||
cli.Default.Padding = normalPadding
|
||||
log.Infof("\033[1m%s\033[0m", strings.ToUpper(pipe.String()))
|
||||
cli.Default.Padding = increasedPadding
|
||||
if err := handle(pipe.Run(ctx)); err != nil {
|
||||
return err
|
||||
var errs = make(chan error)
|
||||
go func() {
|
||||
for _, pipe := range pipes {
|
||||
restoreOutputPadding()
|
||||
log.Infof("\033[1m%s\033[0m", strings.ToUpper(pipe.String()))
|
||||
cli.Default.Padding = increasedPadding
|
||||
if err := handle(pipe.Run(ctx)); err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
errs <- nil
|
||||
}()
|
||||
defer restoreOutputPadding()
|
||||
var signals = make(chan os.Signal)
|
||||
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
|
||||
select {
|
||||
case err := <-errs:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case sig := <-signals:
|
||||
restoreOutputPadding()
|
||||
log.Infof("stopping: %s", sig)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func restoreOutputPadding() {
|
||||
cli.Default.Padding = normalPadding
|
||||
return nil
|
||||
}
|
||||
|
||||
func handle(err error) error {
|
||||
|
||||
5
main.go
5
main.go
@@ -63,6 +63,11 @@ func main() {
|
||||
Name: "debug",
|
||||
Usage: "Enable debug mode",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Usage: "How much time the entire release process is allowed to take",
|
||||
Value: 30 * time.Minute,
|
||||
},
|
||||
}
|
||||
app.Action = func(c *cli.Context) error {
|
||||
start := time.Now()
|
||||
|
||||
@@ -74,7 +74,7 @@ func buildWithDefaults(ctx *context.Context, build config.Build) config.Build {
|
||||
}
|
||||
|
||||
func runPipeOnBuild(ctx *context.Context, build config.Build) error {
|
||||
if err := runHook(build.Env, build.Hooks.Pre); err != nil {
|
||||
if err := runHook(ctx, build.Env, build.Hooks.Pre); err != nil {
|
||||
return errors.Wrap(err, "pre hook failed")
|
||||
}
|
||||
sem := make(chan bool, ctx.Parallelism)
|
||||
@@ -93,16 +93,16 @@ func runPipeOnBuild(ctx *context.Context, build config.Build) error {
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.Wrap(runHook(build.Env, build.Hooks.Post), "post hook failed")
|
||||
return errors.Wrap(runHook(ctx, build.Env, build.Hooks.Post), "post hook failed")
|
||||
}
|
||||
|
||||
func runHook(env []string, hook string) error {
|
||||
func runHook(ctx *context.Context, env []string, hook string) error {
|
||||
if hook == "" {
|
||||
return nil
|
||||
}
|
||||
log.WithField("hook", hook).Info("running hook")
|
||||
cmd := strings.Fields(hook)
|
||||
return run(buildtarget.Runtime, cmd, env)
|
||||
return run(ctx, buildtarget.Runtime, cmd, env)
|
||||
}
|
||||
|
||||
func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target) error {
|
||||
@@ -119,7 +119,7 @@ func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target
|
||||
return err
|
||||
}
|
||||
cmd = append(cmd, "-ldflags="+flags, "-o", binary, build.Main)
|
||||
if err := run(target, cmd, build.Env); err != nil {
|
||||
if err := run(ctx, target, cmd, build.Env); err != nil {
|
||||
return errors.Wrapf(err, "failed to build for %s", target)
|
||||
}
|
||||
ctx.Artifacts.Add(artifact.Artifact{
|
||||
@@ -137,9 +137,9 @@ func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(target buildtarget.Target, command, env []string) error {
|
||||
func run(ctx *context.Context, target buildtarget.Target, command, env []string) error {
|
||||
/* #nosec */
|
||||
var cmd = exec.Command(command[0], command[1:]...)
|
||||
var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
|
||||
env = append(env, target.Env()...)
|
||||
var log = log.WithField("target", target.PrettyString()).
|
||||
WithField("env", env).
|
||||
|
||||
@@ -141,11 +141,11 @@ func process(ctx *context.Context, docker config.Docker, artifact artifact.Artif
|
||||
return errors.Wrapf(err, "failed to link extra file '%s'", file)
|
||||
}
|
||||
}
|
||||
if err := dockerBuild(root, dockerfile, image); err != nil {
|
||||
if err := dockerBuild(ctx, root, dockerfile, image); err != nil {
|
||||
return err
|
||||
}
|
||||
if docker.Latest {
|
||||
if err := dockerTag(image, latest); err != nil {
|
||||
if err := dockerTag(ctx, image, latest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -187,16 +187,16 @@ func publish(ctx *context.Context, docker config.Docker, image, latest string) e
|
||||
if !docker.Latest {
|
||||
return nil
|
||||
}
|
||||
if err := dockerTag(image, latest); err != nil {
|
||||
if err := dockerTag(ctx, image, latest); err != nil {
|
||||
return err
|
||||
}
|
||||
return dockerPush(ctx, docker, latest)
|
||||
}
|
||||
|
||||
func dockerBuild(root, dockerfile, image string) error {
|
||||
func dockerBuild(ctx *context.Context, root, dockerfile, image string) error {
|
||||
log.WithField("image", image).Info("building docker image")
|
||||
/* #nosec */
|
||||
var cmd = exec.Command("docker", "build", "-f", dockerfile, "-t", image, root)
|
||||
var cmd = exec.CommandContext(ctx, "docker", "build", "-f", dockerfile, "-t", image, root)
|
||||
log.WithField("cmd", cmd).Debug("executing")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -206,10 +206,10 @@ func dockerBuild(root, dockerfile, image string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerTag(image, tag string) error {
|
||||
func dockerTag(ctx *context.Context, image, tag string) error {
|
||||
log.WithField("image", image).WithField("tag", tag).Info("tagging docker image")
|
||||
/* #nosec */
|
||||
var cmd = exec.Command("docker", "tag", image, tag)
|
||||
var cmd = exec.CommandContext(ctx, "docker", "tag", image, tag)
|
||||
log.WithField("cmd", cmd).Debug("executing")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -222,7 +222,7 @@ func dockerTag(image, tag string) error {
|
||||
func dockerPush(ctx *context.Context, docker config.Docker, image string) error {
|
||||
log.WithField("image", image).Info("pushing docker image")
|
||||
/* #nosec */
|
||||
var cmd = exec.Command("docker", "push", image)
|
||||
var cmd = exec.CommandContext(ctx, "docker", "push", image)
|
||||
log.WithField("cmd", cmd).Debug("executing")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
|
||||
@@ -128,7 +128,7 @@ func create(ctx *context.Context, format, arch string, binaries []artifact.Artif
|
||||
}
|
||||
|
||||
log.WithField("args", options).Debug("creating fpm package")
|
||||
if out, err := cmd(options).CombinedOutput(); err != nil {
|
||||
if out, err := cmd(ctx, options).CombinedOutput(); err != nil {
|
||||
return errors.Wrap(err, string(out))
|
||||
}
|
||||
ctx.Artifacts.Add(artifact.Artifact{
|
||||
@@ -142,9 +142,9 @@ func create(ctx *context.Context, format, arch string, binaries []artifact.Artif
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmd(options []string) *exec.Cmd {
|
||||
func cmd(ctx *context.Context, options []string) *exec.Cmd {
|
||||
/* #nosec */
|
||||
var cmd = exec.Command("fpm", options...)
|
||||
var cmd = exec.CommandContext(ctx, "fpm", options...)
|
||||
cmd.Env = []string{fmt.Sprintf("PATH=%s:%s", gnuTarPath, os.Getenv("PATH"))}
|
||||
for _, env := range os.Environ() {
|
||||
if strings.HasPrefix(env, "PATH=") {
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestInvalidNameTemplate(t *testing.T) {
|
||||
Config: config.Project{
|
||||
FPM: config.FPM{
|
||||
NameTemplate: "{{.Foo}",
|
||||
Formats: []string{"deb"},
|
||||
Formats: []string{"deb"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -109,7 +109,6 @@ func TestInvalidNameTemplate(t *testing.T) {
|
||||
assert.Contains(t, Pipe{}.Run(ctx).Error(), `template: {{.Foo}:1: unexpected "}" in operand`)
|
||||
}
|
||||
|
||||
|
||||
func TestCreateFileDoesntExist(t *testing.T) {
|
||||
folder, err := ioutil.TempDir("", "archivetest")
|
||||
assert.NoError(t, err)
|
||||
@@ -141,7 +140,7 @@ func TestCreateFileDoesntExist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCmd(t *testing.T) {
|
||||
cmd := cmd([]string{"--help"})
|
||||
cmd := cmd(context.New(config.Project{}), []string{"--help"})
|
||||
assert.NotEmpty(t, cmd.Env)
|
||||
assert.Contains(t, cmd.Env[0], gnuTarPath)
|
||||
}
|
||||
@@ -161,7 +160,7 @@ func TestDefaultSet(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Config: config.Project{
|
||||
FPM: config.FPM{
|
||||
Bindir: "/bin",
|
||||
Bindir: "/bin",
|
||||
NameTemplate: "foo",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ Built with {{ .GoVersion }}`
|
||||
|
||||
func describeBody(ctx *context.Context) (bytes.Buffer, error) {
|
||||
/* #nosec */
|
||||
bts, err := exec.Command("go", "version").CombinedOutput()
|
||||
bts, err := exec.CommandContext(ctx, "go", "version").CombinedOutput()
|
||||
if err != nil {
|
||||
return bytes.Buffer{}, err
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func signone(ctx *context.Context, artifact artifact.Artifact) (string, error) {
|
||||
// However, this works as intended. The nosec annotation
|
||||
// tells the scanner to ignore this.
|
||||
// #nosec
|
||||
cmd := exec.Command(cfg.Cmd, args...)
|
||||
cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("sign: %s failed with %q", cfg.Cmd, string(output))
|
||||
|
||||
@@ -163,7 +163,7 @@ func create(ctx *context.Context, arch string, binaries []artifact.Artifact) err
|
||||
|
||||
var snap = filepath.Join(ctx.Config.Dist, folder+".snap")
|
||||
/* #nosec */
|
||||
var cmd = exec.Command("snapcraft", "snap", primeDir, "--output", snap)
|
||||
var cmd = exec.CommandContext(ctx, "snapcraft", "snap", primeDir, "--output", snap)
|
||||
if out, err = cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("failed to generate snap package: %s", string(out))
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ func TestRunPipe(t *testing.T) {
|
||||
Dist: dist,
|
||||
Snapcraft: config.Snapcraft{
|
||||
NameTemplate: "foo_{{.Arch}}",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -77,8 +77,8 @@ func TestRunPipeInvalidNameTemplate(t *testing.T) {
|
||||
Dist: dist,
|
||||
Snapcraft: config.Snapcraft{
|
||||
NameTemplate: "foo_{{.Arch}",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -100,9 +100,9 @@ func TestRunPipeWithName(t *testing.T) {
|
||||
Dist: dist,
|
||||
Snapcraft: config.Snapcraft{
|
||||
NameTemplate: "foo_{{.Arch}}",
|
||||
Name: "testsnapname",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
Name: "testsnapname",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -130,8 +130,8 @@ func TestRunPipeWithPlugsAndDaemon(t *testing.T) {
|
||||
Dist: dist,
|
||||
Snapcraft: config.Snapcraft{
|
||||
NameTemplate: "foo_{{.Arch}}",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
Summary: "test summary",
|
||||
Description: "test description",
|
||||
Apps: map[string]config.SnapcraftAppMetadata{
|
||||
"mybin": {
|
||||
Plugs: []string{"home", "network"},
|
||||
@@ -171,7 +171,7 @@ func TestNoSnapcraftInPath(t *testing.T) {
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
var ctx = context.New(config.Project{})
|
||||
assert.NoError(t,Pipe{}.Default(ctx))
|
||||
assert.NoError(t, Pipe{}.Default(ctx))
|
||||
assert.Equal(t, defaultNameTemplate, ctx.Config.Snapcraft.NameTemplate)
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestDefaultSet(t *testing.T) {
|
||||
NameTemplate: "foo",
|
||||
},
|
||||
})
|
||||
assert.NoError(t,Pipe{}.Default(ctx))
|
||||
assert.NoError(t, Pipe{}.Default(ctx))
|
||||
assert.Equal(t, "foo", ctx.Config.Snapcraft.NameTemplate)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user