1
0
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:
Carlos Alexandro Becker
2017-12-29 17:07:06 -02:00
parent b61aec7a1c
commit a84148c620
11 changed files with 85 additions and 45 deletions

View File

@@ -10,6 +10,7 @@ import (
ctx "context" ctx "context"
"os" "os"
"strings" "strings"
"time"
"github.com/goreleaser/goreleaser/config" "github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/internal/artifact" "github.com/goreleaser/goreleaser/internal/artifact"
@@ -41,8 +42,18 @@ type Context struct {
// New context // New context
func New(config config.Project) *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{ return &Context{
Context: ctx.Background(), Context: ctx,
Config: config, Config: config,
Env: splitEnv(os.Environ()), Env: splitEnv(os.Environ()),
Parallelism: 4, Parallelism: 4,

View File

@@ -4,7 +4,10 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/signal"
"strings" "strings"
"syscall"
"time"
"github.com/apex/log" "github.com/apex/log"
"github.com/apex/log/handlers/cli" "github.com/apex/log/handlers/cli"
@@ -65,6 +68,7 @@ type Flags interface {
String(s string) string String(s string) string
Int(s string) int Int(s string) int
Bool(s string) bool Bool(s string) bool
Duration(s string) time.Duration
} }
// Release runs the release process with the given flags // 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") 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.Parallelism = flags.Int("parallelism")
ctx.Debug = flags.Bool("debug") ctx.Debug = flags.Bool("debug")
log.Debugf("parallelism: %v", ctx.Parallelism) log.Debugf("parallelism: %v", ctx.Parallelism)
@@ -105,16 +110,36 @@ func Release(flags Flags) error {
ctx.Publish = false ctx.Publish = false
} }
ctx.RmDist = flags.Bool("rm-dist") ctx.RmDist = flags.Bool("rm-dist")
for _, pipe := range pipes { var errs = make(chan error)
cli.Default.Padding = normalPadding go func() {
log.Infof("\033[1m%s\033[0m", strings.ToUpper(pipe.String())) for _, pipe := range pipes {
cli.Default.Padding = increasedPadding restoreOutputPadding()
if err := handle(pipe.Run(ctx)); err != nil { log.Infof("\033[1m%s\033[0m", strings.ToUpper(pipe.String()))
return err 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 cli.Default.Padding = normalPadding
return nil
} }
func handle(err error) error { func handle(err error) error {

View File

@@ -63,6 +63,11 @@ func main() {
Name: "debug", Name: "debug",
Usage: "Enable debug mode", 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 { app.Action = func(c *cli.Context) error {
start := time.Now() start := time.Now()

View File

@@ -74,7 +74,7 @@ func buildWithDefaults(ctx *context.Context, build config.Build) config.Build {
} }
func runPipeOnBuild(ctx *context.Context, build config.Build) error { 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") return errors.Wrap(err, "pre hook failed")
} }
sem := make(chan bool, ctx.Parallelism) 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 { if err := g.Wait(); err != nil {
return err 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 == "" { if hook == "" {
return nil return nil
} }
log.WithField("hook", hook).Info("running hook") log.WithField("hook", hook).Info("running hook")
cmd := strings.Fields(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 { 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 return err
} }
cmd = append(cmd, "-ldflags="+flags, "-o", binary, build.Main) 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) return errors.Wrapf(err, "failed to build for %s", target)
} }
ctx.Artifacts.Add(artifact.Artifact{ ctx.Artifacts.Add(artifact.Artifact{
@@ -137,9 +137,9 @@ func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target
return nil return nil
} }
func run(target buildtarget.Target, command, env []string) error { func run(ctx *context.Context, target buildtarget.Target, command, env []string) error {
/* #nosec */ /* #nosec */
var cmd = exec.Command(command[0], command[1:]...) var cmd = exec.CommandContext(ctx, command[0], command[1:]...)
env = append(env, target.Env()...) env = append(env, target.Env()...)
var log = log.WithField("target", target.PrettyString()). var log = log.WithField("target", target.PrettyString()).
WithField("env", env). WithField("env", env).

View File

@@ -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) 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 return err
} }
if docker.Latest { if docker.Latest {
if err := dockerTag(image, latest); err != nil { if err := dockerTag(ctx, image, latest); err != nil {
return err return err
} }
} }
@@ -187,16 +187,16 @@ func publish(ctx *context.Context, docker config.Docker, image, latest string) e
if !docker.Latest { if !docker.Latest {
return nil return nil
} }
if err := dockerTag(image, latest); err != nil { if err := dockerTag(ctx, image, latest); err != nil {
return err return err
} }
return dockerPush(ctx, docker, latest) 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") log.WithField("image", image).Info("building docker image")
/* #nosec */ /* #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") log.WithField("cmd", cmd).Debug("executing")
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@@ -206,10 +206,10 @@ func dockerBuild(root, dockerfile, image string) error {
return nil 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") log.WithField("image", image).WithField("tag", tag).Info("tagging docker image")
/* #nosec */ /* #nosec */
var cmd = exec.Command("docker", "tag", image, tag) var cmd = exec.CommandContext(ctx, "docker", "tag", image, tag)
log.WithField("cmd", cmd).Debug("executing") log.WithField("cmd", cmd).Debug("executing")
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
@@ -222,7 +222,7 @@ func dockerTag(image, tag string) error {
func dockerPush(ctx *context.Context, docker config.Docker, image string) error { func dockerPush(ctx *context.Context, docker config.Docker, image string) error {
log.WithField("image", image).Info("pushing docker image") log.WithField("image", image).Info("pushing docker image")
/* #nosec */ /* #nosec */
var cmd = exec.Command("docker", "push", image) var cmd = exec.CommandContext(ctx, "docker", "push", image)
log.WithField("cmd", cmd).Debug("executing") log.WithField("cmd", cmd).Debug("executing")
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {

View File

@@ -128,7 +128,7 @@ func create(ctx *context.Context, format, arch string, binaries []artifact.Artif
} }
log.WithField("args", options).Debug("creating fpm package") 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)) return errors.Wrap(err, string(out))
} }
ctx.Artifacts.Add(artifact.Artifact{ ctx.Artifacts.Add(artifact.Artifact{
@@ -142,9 +142,9 @@ func create(ctx *context.Context, format, arch string, binaries []artifact.Artif
return nil return nil
} }
func cmd(options []string) *exec.Cmd { func cmd(ctx *context.Context, options []string) *exec.Cmd {
/* #nosec */ /* #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"))} cmd.Env = []string{fmt.Sprintf("PATH=%s:%s", gnuTarPath, os.Getenv("PATH"))}
for _, env := range os.Environ() { for _, env := range os.Environ() {
if strings.HasPrefix(env, "PATH=") { if strings.HasPrefix(env, "PATH=") {

View File

@@ -96,7 +96,7 @@ func TestInvalidNameTemplate(t *testing.T) {
Config: config.Project{ Config: config.Project{
FPM: config.FPM{ FPM: config.FPM{
NameTemplate: "{{.Foo}", 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`) assert.Contains(t, Pipe{}.Run(ctx).Error(), `template: {{.Foo}:1: unexpected "}" in operand`)
} }
func TestCreateFileDoesntExist(t *testing.T) { func TestCreateFileDoesntExist(t *testing.T) {
folder, err := ioutil.TempDir("", "archivetest") folder, err := ioutil.TempDir("", "archivetest")
assert.NoError(t, err) assert.NoError(t, err)
@@ -141,7 +140,7 @@ func TestCreateFileDoesntExist(t *testing.T) {
} }
func TestCmd(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.NotEmpty(t, cmd.Env)
assert.Contains(t, cmd.Env[0], gnuTarPath) assert.Contains(t, cmd.Env[0], gnuTarPath)
} }
@@ -161,7 +160,7 @@ func TestDefaultSet(t *testing.T) {
var ctx = &context.Context{ var ctx = &context.Context{
Config: config.Project{ Config: config.Project{
FPM: config.FPM{ FPM: config.FPM{
Bindir: "/bin", Bindir: "/bin",
NameTemplate: "foo", NameTemplate: "foo",
}, },
}, },

View File

@@ -25,7 +25,7 @@ Built with {{ .GoVersion }}`
func describeBody(ctx *context.Context) (bytes.Buffer, error) { func describeBody(ctx *context.Context) (bytes.Buffer, error) {
/* #nosec */ /* #nosec */
bts, err := exec.Command("go", "version").CombinedOutput() bts, err := exec.CommandContext(ctx, "go", "version").CombinedOutput()
if err != nil { if err != nil {
return bytes.Buffer{}, err return bytes.Buffer{}, err
} }

View File

@@ -92,7 +92,7 @@ func signone(ctx *context.Context, artifact artifact.Artifact) (string, error) {
// However, this works as intended. The nosec annotation // However, this works as intended. The nosec annotation
// tells the scanner to ignore this. // tells the scanner to ignore this.
// #nosec // #nosec
cmd := exec.Command(cfg.Cmd, args...) cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
return "", fmt.Errorf("sign: %s failed with %q", cfg.Cmd, string(output)) return "", fmt.Errorf("sign: %s failed with %q", cfg.Cmd, string(output))

View File

@@ -163,7 +163,7 @@ func create(ctx *context.Context, arch string, binaries []artifact.Artifact) err
var snap = filepath.Join(ctx.Config.Dist, folder+".snap") var snap = filepath.Join(ctx.Config.Dist, folder+".snap")
/* #nosec */ /* #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 { if out, err = cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to generate snap package: %s", string(out)) return fmt.Errorf("failed to generate snap package: %s", string(out))
} }

View File

@@ -54,8 +54,8 @@ func TestRunPipe(t *testing.T) {
Dist: dist, Dist: dist,
Snapcraft: config.Snapcraft{ Snapcraft: config.Snapcraft{
NameTemplate: "foo_{{.Arch}}", NameTemplate: "foo_{{.Arch}}",
Summary: "test summary", Summary: "test summary",
Description: "test description", Description: "test description",
}, },
}, },
} }
@@ -77,8 +77,8 @@ func TestRunPipeInvalidNameTemplate(t *testing.T) {
Dist: dist, Dist: dist,
Snapcraft: config.Snapcraft{ Snapcraft: config.Snapcraft{
NameTemplate: "foo_{{.Arch}", NameTemplate: "foo_{{.Arch}",
Summary: "test summary", Summary: "test summary",
Description: "test description", Description: "test description",
}, },
}, },
} }
@@ -100,9 +100,9 @@ func TestRunPipeWithName(t *testing.T) {
Dist: dist, Dist: dist,
Snapcraft: config.Snapcraft{ Snapcraft: config.Snapcraft{
NameTemplate: "foo_{{.Arch}}", NameTemplate: "foo_{{.Arch}}",
Name: "testsnapname", Name: "testsnapname",
Summary: "test summary", Summary: "test summary",
Description: "test description", Description: "test description",
}, },
}, },
} }
@@ -130,8 +130,8 @@ func TestRunPipeWithPlugsAndDaemon(t *testing.T) {
Dist: dist, Dist: dist,
Snapcraft: config.Snapcraft{ Snapcraft: config.Snapcraft{
NameTemplate: "foo_{{.Arch}}", NameTemplate: "foo_{{.Arch}}",
Summary: "test summary", Summary: "test summary",
Description: "test description", Description: "test description",
Apps: map[string]config.SnapcraftAppMetadata{ Apps: map[string]config.SnapcraftAppMetadata{
"mybin": { "mybin": {
Plugs: []string{"home", "network"}, Plugs: []string{"home", "network"},
@@ -171,7 +171,7 @@ func TestNoSnapcraftInPath(t *testing.T) {
func TestDefault(t *testing.T) { func TestDefault(t *testing.T) {
var ctx = context.New(config.Project{}) 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) assert.Equal(t, defaultNameTemplate, ctx.Config.Snapcraft.NameTemplate)
} }
@@ -181,7 +181,7 @@ func TestDefaultSet(t *testing.T) {
NameTemplate: "foo", NameTemplate: "foo",
}, },
}) })
assert.NoError(t,Pipe{}.Default(ctx)) assert.NoError(t, Pipe{}.Default(ctx))
assert.Equal(t, "foo", ctx.Config.Snapcraft.NameTemplate) assert.Equal(t, "foo", ctx.Config.Snapcraft.NameTemplate)
} }