Repository: goreleaser/goreleaser
Pull request #2363 (2021-07-24 23:56:23)

fix: return an err with stdout/err when a command failed
improved output when a `exec.Command` fails, adding its output to the error

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/goreleaser/goreleaser/8QsLEKVJvUqta6FEbv28J9TWHYUj](https://vercel.com/goreleaser/goreleaser/8QsLEKVJvUqta6FEbv28J9TWHYUj)  
✅ Preview: [https://goreleaser-git-cmd-err-output-goreleaser.vercel.app](https://goreleaser-git-cmd-err-output-goreleaser.vercel.app)


codecov[bot]: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) Report
> Merging [#2363](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (2bcff77) into [master](https://codecov.io/gh/goreleaser/goreleaser/commit/2a304f59328956b8413d336eb277c2050ab3edb1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) (2a304f5) will **increase** coverage by `0.01%`.
> The diff coverage is `91.30%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/graphs/tree.svg?width=650&height=150&src=pr&token=3bJvv6b7hN&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser)](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser)

```diff
@@            Coverage Diff             @@
##           master    #2363      +/-   ##
==========================================
+ Coverage   82.90%   82.92%   +0.01%     
==========================================
  Files          83       83              
  Lines        6056     6061       +5     
==========================================
+ Hits         5021     5026       +5     
  Misses        887      887              
  Partials      148      148              
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser) | Coverage Δ | |
|---|---|---|
| [internal/pipe/snapcraft/snapcraft.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9zbmFwY3JhZnQvc25hcGNyYWZ0Lmdv) | `82.25% <50.00%> (ø)` | |
| [internal/pipe/docker/api\_docker.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9kb2NrZXIvYXBpX2RvY2tlci5nbw==) | `93.33% <80.00%> (ø)` | |
| [internal/builders/golang/build.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvYnVpbGRlcnMvZ29sYW5nL2J1aWxkLmdv) | `92.68% <100.00%> (-0.04%)` | :arrow_down: |
| [internal/exec/exec.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvZXhlYy9leGVjLmdv) | `84.87% <100.00%> (+0.25%)` | :arrow_up: |
| [internal/pipe/docker/api.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9kb2NrZXIvYXBpLmdv) | `100.00% <100.00%> (ø)` | |
| [internal/pipe/sign/sign.go](https://codecov.io/gh/goreleaser/goreleaser/pull/2363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser#diff-aW50ZXJuYWwvcGlwZS9zaWduL3NpZ24uZ28=) | `93.61% <100.00%> (+0.09%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser). Last update [2a304f5...2bcff77](https://codecov.io/gh/goreleaser/goreleaser/pull/2363?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=goreleaser).


Patch:
diff --git a/internal/builders/golang/build.go b/internal/builders/golang/build.go
index 4f0afdf2..e322d17a 100644
--- a/internal/builders/golang/build.go
+++ b/internal/builders/golang/build.go
@@ -1,7 +1,6 @@
 package golang
 
 import (
-	"errors"
 	"fmt"
 	"go/ast"
 	"go/parser"
@@ -196,8 +195,7 @@ func run(ctx *context.Context, command, env []string, dir string) error {
 	cmd.Dir = dir
 	log.Debug("running")
 	if out, err := cmd.CombinedOutput(); err != nil {
-		log.WithError(err).Debug("failed")
-		return errors.New(string(out))
+		return fmt.Errorf("%w: %s", err, string(out))
 	}
 	return nil
 }
diff --git a/internal/exec/exec.go b/internal/exec/exec.go
index dabce979..77734ad4 100644
--- a/internal/exec/exec.go
+++ b/internal/exec/exec.go
@@ -2,13 +2,16 @@
 package exec
 
 import (
+	"bytes"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 
 	"github.com/apex/log"
 	"github.com/caarlos0/go-shellwords"
 	"github.com/goreleaser/goreleaser/internal/artifact"
+	"github.com/goreleaser/goreleaser/internal/gio"
 	"github.com/goreleaser/goreleaser/internal/logext"
 	"github.com/goreleaser/goreleaser/internal/pipe"
 	"github.com/goreleaser/goreleaser/internal/semerrgroup"
@@ -78,12 +81,14 @@ func executeCommand(c *command) error {
 	}
 
 	fields := log.Fields{"cmd": c.Args[0]}
-	cmd.Stderr = logext.NewWriter(fields, logext.Error)
-	cmd.Stdout = logext.NewWriter(fields, logext.Info)
+	var b bytes.Buffer
+	w := gio.Safe(&b)
+	cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
+	cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
 
 	log.WithFields(fields).Info("publishing")
 	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("publishing: %s failed: %w", c.Args[0], err)
+		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])
diff --git a/internal/pipe/docker/api.go b/internal/pipe/docker/api.go
index 0ea312ca..16cd2626 100644
--- a/internal/pipe/docker/api.go
+++ b/internal/pipe/docker/api.go
@@ -3,6 +3,7 @@ package docker
 import (
 	"bytes"
 	"context"
+	"fmt"
 	"io"
 	"os/exec"
 	"sync"
@@ -43,7 +44,7 @@ type manifester interface {
 }
 
 // nolint: unparam
-func runCommand(ctx context.Context, dir, binary string, args ...string) ([]byte, error) {
+func runCommand(ctx context.Context, dir, binary string, args ...string) error {
 	fields := log.Fields{
 		"cmd": append([]string{binary}, args[0]),
 		"cwd": dir,
@@ -59,6 +60,8 @@ func runCommand(ctx context.Context, dir, binary string, args ...string) ([]byte
 	cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
 
 	log.WithFields(fields).Debug("running")
-	err := cmd.Run()
-	return b.Bytes(), err
+	if err := cmd.Run(); err != nil {
+		return fmt.Errorf("%w: %s", err, b.String())
+	}
+	return nil
 }
diff --git a/internal/pipe/docker/api_docker.go b/internal/pipe/docker/api_docker.go
index d851704e..5bebdec0 100644
--- a/internal/pipe/docker/api_docker.go
+++ b/internal/pipe/docker/api_docker.go
@@ -17,13 +17,13 @@ func init() {
 type dockerManifester struct{}
 
 func (m dockerManifester) Create(ctx context.Context, manifest string, images, flags []string) error {
-	_, _ = runCommand(ctx, ".", "docker", "manifest", "rm", manifest)
+	_ = runCommand(ctx, ".", "docker", "manifest", "rm", manifest)
 
 	args := []string{"manifest", "create", manifest}
 	args = append(args, images...)
 	args = append(args, flags...)
 
-	if _, err := runCommand(ctx, ".", "docker", args...); err != nil {
+	if err := runCommand(ctx, ".", "docker", args...); err != nil {
 		return fmt.Errorf("failed to create %s: %w", manifest, err)
 	}
 	return nil
@@ -32,7 +32,7 @@ func (m dockerManifester) Create(ctx context.Context, manifest string, images, f
 func (m dockerManifester) Push(ctx context.Context, manifest string, flags []string) error {
 	args := []string{"manifest", "push", manifest}
 	args = append(args, flags...)
-	if _, err := runCommand(ctx, ".", "docker", args...); err != nil {
+	if err := runCommand(ctx, ".", "docker", args...); err != nil {
 		return fmt.Errorf("failed to push %s: %w", manifest, err)
 	}
 	return nil
@@ -43,14 +43,14 @@ type dockerImager struct {
 }
 
 func (i dockerImager) Push(ctx context.Context, image string, flags []string) error {
-	if _, err := runCommand(ctx, ".", "docker", "push", image); err != nil {
+	if err := runCommand(ctx, ".", "docker", "push", image); err != nil {
 		return fmt.Errorf("failed to push %s: %w", image, err)
 	}
 	return nil
 }
 
 func (i dockerImager) Build(ctx context.Context, root string, images, flags []string) error {
-	if _, err := runCommand(ctx, root, "docker", i.buildCommand(images, flags)...); err != nil {
+	if err := runCommand(ctx, root, "docker", i.buildCommand(images, flags)...); err != nil {
 		return fmt.Errorf("failed to build %s: %w", images[0], err)
 	}
 	return nil
diff --git a/internal/pipe/sign/sign.go b/internal/pipe/sign/sign.go
index 0bd40772..e634f230 100644
--- a/internal/pipe/sign/sign.go
+++ b/internal/pipe/sign/sign.go
@@ -1,6 +1,7 @@
 package sign
 
 import (
+	"bytes"
 	"fmt"
 	"io"
 	"os"
@@ -10,6 +11,7 @@ import (
 
 	"github.com/apex/log"
 	"github.com/goreleaser/goreleaser/internal/artifact"
+	"github.com/goreleaser/goreleaser/internal/gio"
 	"github.com/goreleaser/goreleaser/internal/ids"
 	"github.com/goreleaser/goreleaser/internal/logext"
 	"github.com/goreleaser/goreleaser/internal/pipe"
@@ -153,14 +155,16 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
 	// tells the scanner to ignore this.
 	// #nosec
 	cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
-	cmd.Stderr = logext.NewWriter(fields, logext.Error)
-	cmd.Stdout = logext.NewWriter(fields, logext.Info)
+	var b bytes.Buffer
+	w := gio.Safe(&b)
+	cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
+	cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
 	if stdin != nil {
 		cmd.Stdin = stdin
 	}
 	log.WithFields(fields).Info("signing")
 	if err := cmd.Run(); err != nil {
-		return nil, fmt.Errorf("sign: %s failed", cfg.Cmd)
+		return nil, fmt.Errorf("sign: %s failed: %w: %s", cfg.Cmd, err, b.String())
 	}
 
 	artifactPathBase, _ := filepath.Split(a.Path)
diff --git a/internal/pipe/snapcraft/snapcraft.go b/internal/pipe/snapcraft/snapcraft.go
index 5480caf7..f15952a3 100644
--- a/internal/pipe/snapcraft/snapcraft.go
+++ b/internal/pipe/snapcraft/snapcraft.go
@@ -327,7 +327,7 @@ func create(ctx *context.Context, snap config.Snapcraft, arch string, binaries [
 	/* #nosec */
 	cmd := exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snapFile)
 	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: %w: %s", err, string(out))
 	}
 	if !snap.Publish {
 		return nil
@@ -362,7 +362,7 @@ func push(ctx *context.Context, snap *artifact.Artifact) error {
 		if strings.Contains(string(out), reviewWaitMsg) || strings.Contains(string(out), humanReviewMsg) || strings.Contains(string(out), needsReviewMsg) {
 			log.Warn(reviewWaitMsg)
 		} else {
-			return fmt.Errorf("failed to push %s package: %s", snap.Path, string(out))
+			return fmt.Errorf("failed to push %s package: %w: %s", snap.Path, err, string(out))
 		}
 	}
 	snap.Type = artifact.Snapcraft


Test patch:
diff --git a/internal/exec/exec_test.go b/internal/exec/exec_test.go
index 9d8ee24b..9e0c4e73 100644
--- a/internal/exec/exec_test.go
+++ b/internal/exec/exec_test.go
@@ -251,7 +251,7 @@ func TestExecute(t *testing.T) {
 				},
 			},
 			// stderr is sent to output via logger
-			fmt.Errorf(`publishing: %s failed: exit status 1`, MockCmd),
+			fmt.Errorf(`publishing: %s failed: exit status 1: test error`, MockCmd),
 		},
 	}