Repository: goreleaser/goreleaser
Pull request #1474 (2020-04-28 01:20:08)

feat: improve skip-publish behavior
Today the `--skip-publish` flag is kind of useless for the purpose of seeing what would have happened.

On #1425 I actually made a step into the wrong direction, fixed a bug and caused another.

The idea here is:

- we don't skip the entire publishing phase on the publish pipe anymore
- each pipe deals with it in their own way, ideally, logging what would have been uploaded 

In this PR:

- revert of most of #1425, keeping the token fixes which was the problem in the end - closes #1464 #1463 
- logs what would have been uploaded in the blob pipe (and some refactor there as well)
- we can improve the other publish implementations in further PRs, otherwise this will be really big 👎 


Patch:
diff --git a/go.mod b/go.mod
index d4a3a6e7..edf53f0b 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/goreleaser/goreleaser
 go 1.14
 
 require (
-	code.gitea.io/sdk/gitea v0.11.2
+	code.gitea.io/sdk/gitea v0.11.3
 	github.com/Masterminds/semver/v3 v3.1.0
 	github.com/apex/log v1.1.4
 	github.com/aws/aws-sdk-go v1.25.11 // indirect
diff --git a/go.sum b/go.sum
index 0b035a7e..938bf110 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@ cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 code.gitea.io/sdk/gitea v0.11.2 h1:D0xIRlHv3IckzdYOWzHK1bPvlkXdA4LD909UYyBdi1o=
 code.gitea.io/sdk/gitea v0.11.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
+code.gitea.io/sdk/gitea v0.11.3 h1:CdI3J82Mqn0mElyEKa5DUSr3Wi2R+qm/6uVtCkSSqSM=
+code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
 contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
 contrib.go.opencensus.io/exporter/ocagent v0.5.0 h1:TKXjQSRS0/cCDrP7KvkgU6SmILtF/yV2TOs/02K/WZQ=
 contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
diff --git a/internal/artifact/artifact.go b/internal/artifact/artifact.go
index a5205c8f..bb07aa78 100644
--- a/internal/artifact/artifact.go
+++ b/internal/artifact/artifact.go
@@ -29,8 +29,6 @@ const (
 	UploadableBinary
 	// UploadableFile is any file that can be uploaded
 	UploadableFile
-	// UploadableBrewTap is a .rb file that can be uploaded
-	UploadableBrewTap
 	// Binary is a binary (output of a gobuild)
 	Binary
 	// LinuxPackage is a linux package generated by nfpm
@@ -60,8 +58,6 @@ func (t Type) String() string {
 	case UploadableBinary:
 	case Binary:
 		return "Binary"
-	case UploadableBrewTap:
-		return "Brew Tap"
 	case LinuxPackage:
 		return "Linux Package"
 	case DockerImage:
diff --git a/internal/builders/golang/build.go b/internal/builders/golang/build.go
index 3812213f..b0f4fd64 100644
--- a/internal/builders/golang/build.go
+++ b/internal/builders/golang/build.go
@@ -81,7 +81,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti
 		Goarm:  target.arm,
 		Gomips: target.mips,
 		Extra: map[string]interface{}{
-			"Binary": filepath.Base(build.Binary),
+			"Binary": filepath.Base(options.Path),
 			"Ext":    options.Ext,
 			"ID":     build.ID,
 		},
diff --git a/internal/pipe/blob/blob.go b/internal/pipe/blob/blob.go
index 69035faa..f19babfb 100644
--- a/internal/pipe/blob/blob.go
+++ b/internal/pipe/blob/blob.go
@@ -37,13 +37,16 @@ func (Pipe) Publish(ctx *context.Context) error {
 	if len(ctx.Config.Blobs) == 0 {
 		return pipe.Skip("Blob section is not configured")
 	}
-	// Openning connection to the list of buckets
-	o := newOpenBucket()
+	var up uploader = productionUploader{}
+	if ctx.SkipPublish {
+		up = skipUploader{}
+	}
+
 	var g = semerrgroup.New(ctx.Parallelism)
 	for _, conf := range ctx.Config.Blobs {
 		conf := conf
 		g.Go(func() error {
-			return o.Upload(ctx, conf)
+			return doUpload(ctx, conf, up)
 		})
 	}
 	return g.Wait()
diff --git a/internal/pipe/blob/openbucket.go b/internal/pipe/blob/upload.go
similarity index 66%
rename from internal/pipe/blob/openbucket.go
rename to internal/pipe/blob/upload.go
index 37614a96..81679159 100644
--- a/internal/pipe/blob/openbucket.go
+++ b/internal/pipe/blob/upload.go
@@ -27,32 +27,7 @@ import (
 	_ "gocloud.dev/secrets/gcpkms"
 )
 
-// OpenBucket is the interface that wraps the BucketConnect and UploadBucket method
-type OpenBucket interface {
-	Connect(ctx *context.Context, bucketURL string) (*blob.Bucket, error)
-	Upload(ctx *context.Context, conf config.Blob) error
-}
-
-// Bucket is object which holds connection for Go Bucker Provider
-type Bucket struct {
-	BucketConn *blob.Bucket
-}
-
-// returns openbucket connection for list of providers
-func newOpenBucket() OpenBucket {
-	return Bucket{}
-}
-
-// Connect makes connection with provider
-func (b Bucket) Connect(ctx *context.Context, bucketURL string) (*blob.Bucket, error) {
-	conn, err := blob.OpenBucket(ctx, bucketURL)
-	if err != nil {
-		return nil, err
-	}
-	return conn, nil
-}
-
-func (b Bucket) url(ctx *context.Context, conf config.Blob) (string, error) {
+func urlFor(ctx *context.Context, conf config.Blob) (string, error) {
 	bucket, err := tmpl.New(ctx).Apply(conf.Bucket)
 	if err != nil {
 		return "", err
@@ -83,26 +58,20 @@ func (b Bucket) url(ctx *context.Context, conf config.Blob) (string, error) {
 	return bucketURL, nil
 }
 
-// Upload takes connection initilized from newOpenBucket to upload goreleaser artifacts
-// Takes goreleaser context(which includes artificats) and bucketURL for upload destination (gs://gorelease-bucket)
-func (b Bucket) Upload(ctx *context.Context, conf config.Blob) error {
+// Takes goreleaser context(which includes artificats) and bucketURL for
+// upload to destination (eg: gs://gorelease-bucket) using the given uploader
+// implementation
+func doUpload(ctx *context.Context, conf config.Blob, up uploader) error {
 	folder, err := tmpl.New(ctx).Apply(conf.Folder)
 	if err != nil {
 		return err
 	}
 
-	bucketURL, err := b.url(ctx, conf)
+	bucketURL, err := urlFor(ctx, conf)
 	if err != nil {
 		return err
 	}
 
-	// Get the openbucket connection for specific provider
-	conn, err := b.Connect(ctx, bucketURL)
-	if err != nil {
-		return err
-	}
-	defer conn.Close()
-
 	var filter = artifact.Or(
 		artifact.ByType(artifact.UploadableArchive),
 		artifact.ByType(artifact.UploadableBinary),
@@ -119,34 +88,18 @@ func (b Bucket) Upload(ctx *context.Context, conf config.Blob) error {
 	for _, artifact := range ctx.Artifacts.Filter(filter).List() {
 		artifact := artifact
 		g.Go(func() error {
-			log.WithFields(log.Fields{
-				"provider": bucketURL,
-				"folder":   folder,
-				"artifact": artifact.Name,
-			}).Info("uploading")
-
 			// TODO: replace this with ?prefix=folder on the bucket url
-			w, err := conn.NewWriter(ctx, filepath.Join(folder, artifact.Name), nil)
-			if err != nil {
-				return errors.Wrap(err, "failed to obtain writer")
-			}
 			data, err := getData(ctx, conf, artifact.Path)
 			if err != nil {
 				return err
 			}
-			_, err = w.Write(data)
-			if err != nil {
+
+			if err := up.Upload(ctx, bucketURL, filepath.Join(folder, artifact.Name), data); err != nil {
 				switch {
 				case errorContains(err, "NoSuchBucket", "ContainerNotFound", "notFound"):
 					return errors.Wrapf(err, "provided bucket does not exist: %s", bucketURL)
 				case errorContains(err, "NoCredentialProviders"):
 					return errors.Wrapf(err, "check credentials and access to bucket: %s", bucketURL)
-				default:
-					return errors.Wrapf(err, "failed to write to bucket")
-				}
-			}
-			if err = w.Close(); err != nil {
-				switch {
 				case errorContains(err, "InvalidAccessKeyId"):
 					return errors.Wrap(err, "aws access key id you provided does not exist in our records")
 				case errorContains(err, "AuthenticationFailed"):
@@ -155,14 +108,10 @@ func (b Bucket) Upload(ctx *context.Context, conf config.Blob) error {
 					return errors.Wrap(err, "google app credentials you provided is not valid")
 				case errorContains(err, "no such host"):
 					return errors.Wrap(err, "azure storage account you provided is not valid")
-				case errorContains(err, "NoSuchBucket", "ContainerNotFound", "notFound"):
-					return errors.Wrapf(err, "provided bucket does not exist: %s", bucketURL)
-				case errorContains(err, "NoCredentialProviders"):
-					return errors.Wrapf(err, "check credentials and access to bucket %s", bucketURL)
 				case errorContains(err, "ServiceCode=ResourceNotFound"):
 					return errors.Wrapf(err, "missing azure storage key for provided bucket %s", bucketURL)
 				default:
-					return errors.Wrap(err, "failed to close Bucket writer")
+					return errors.Wrap(err, "failed to write to bucket")
 				}
 			}
 			return err
@@ -190,3 +139,52 @@ func getData(ctx *context.Context, conf config.Blob, path string) ([]byte, error
 	}
 	return data, err
 }
+
+// uploader implements upload
+type uploader interface {
+	Upload(ctx *context.Context, url, path string, data []byte) error
+}
+
+// skipUploader is used when --skip-upload is set and will just log
+// things without really doing anything
+type skipUploader struct{}
+
+func (u skipUploader) Upload(_ *context.Context, url, path string, _ []byte) error {
+	log.WithFields(log.Fields{
+		"bucket": url,
+		"path":   path,
+	}).Warn("doUpload skipped because skip-publish is set")
+	return nil
+}
+
+// productionUploader actually do upload to
+type productionUploader struct{}
+
+func (u productionUploader) Upload(ctx *context.Context, url, path string, data []byte) (err error) {
+	log.WithFields(log.Fields{
+		"bucket": url,
+		"path":   path,
+	}).Info("uploading")
+
+	// TODO: its not so great that we open one connection for each file
+	conn, err := blob.OpenBucket(ctx, url)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if cerr := conn.Close(); err == nil {
+			err = cerr
+		}
+	}()
+	w, err := conn.NewWriter(ctx, path, nil)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if cerr := w.Close(); err == nil {
+			err = cerr
+		}
+	}()
+	_, err = w.Write(data)
+	return
+}
diff --git a/internal/pipe/brew/brew.go b/internal/pipe/brew/brew.go
index d95646e8..7c633e2c 100644
--- a/internal/pipe/brew/brew.go
+++ b/internal/pipe/brew/brew.go
@@ -36,64 +36,14 @@ func (Pipe) String() string {
 	return "homebrew tap formula"
 }
 
-func (Pipe) Run(ctx *context.Context) error {
-	for _, brew := range ctx.Config.Brews {
-		if err := doRun(ctx, brew); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 // Publish brew formula
 func (Pipe) Publish(ctx *context.Context) error {
 	client, err := client.New(ctx)
 	if err != nil {
 		return err
 	}
-
-	return doPublish(ctx, client)
-}
-
-func doPublish(ctx *context.Context, client client.Client) error {
-	if ctx.SkipPublish {
-		return pipe.ErrSkipPublishEnabled
-	}
-	var taps = ctx.Artifacts.Filter(artifact.ByType(artifact.UploadableBrewTap)).List()
-	if len(taps) == 0 {
-		return pipe.Skip("no brew taps found")
-	}
-	for _, tap := range taps {
-		brew := tap.Extra["config"].(config.Homebrew)
-		if strings.TrimSpace(brew.SkipUpload) == "true" {
-			return pipe.Skip("brew.skip_upload is set")
-		}
-		if strings.TrimSpace(brew.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
-			return pipe.Skip("prerelease detected with 'auto' upload, skipping homebrew publish")
-		}
-
-		var repo config.Repo
-		switch ctx.TokenType {
-		case context.TokenTypeGitHub:
-			repo = brew.GitHub
-		case context.TokenTypeGitLab:
-			repo = brew.GitLab
-		default:
-			return ErrTokenTypeNotImplementedForBrew
-		}
-
-		var gpath = buildFormulaPath(brew.Folder, tap.Name)
-		log.WithField("formula", gpath).
-			WithField("repo", repo.String()).
-			Info("pushing")
-
-		content, err := ioutil.ReadFile(tap.Path)
-		if err != nil {
-			return errors.Wrap(err, "failed to read tap")
-		}
-
-		var msg = fmt.Sprintf("Brew formula update for %s version %s", ctx.Config.ProjectName, ctx.Git.CurrentTag)
-		if err := client.CreateFile(ctx, brew.CommitAuthor, repo, content, gpath, msg); err != nil {
+	for _, brew := range ctx.Config.Brews {
+		if err := doRun(ctx, brew, client); err != nil {
 			return err
 		}
 	}
@@ -155,7 +105,7 @@ func contains(ss []string, s string) bool {
 	return false
 }
 
-func doRun(ctx *context.Context, brew config.Homebrew) error {
+func doRun(ctx *context.Context, brew config.Homebrew, client client.Client) error {
 	if brew.GitHub.Name == "" && brew.GitLab.Name == "" {
 		return pipe.Skip("brew section is not configured")
 	}
@@ -198,16 +148,33 @@ func doRun(ctx *context.Context, brew config.Homebrew) error {
 		return errors.Wrap(err, "failed to write brew tap")
 	}
 
-	ctx.Artifacts.Add(&artifact.Artifact{
-		Name: filename,
-		Path: path,
-		Type: artifact.UploadableBrewTap,
-		Extra: map[string]interface{}{
-			"config": brew,
-		},
-	})
+	if strings.TrimSpace(brew.SkipUpload) == "true" {
+		return pipe.Skip("brew.skip_upload is set")
+	}
+	if ctx.SkipPublish {
+		return pipe.ErrSkipPublishEnabled
+	}
+	if strings.TrimSpace(brew.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
+		return pipe.Skip("prerelease detected with 'auto' upload, skipping homebrew publish")
+	}
 
-	return nil
+	var repo config.Repo
+	switch ctx.TokenType {
+	case context.TokenTypeGitHub:
+		repo = brew.GitHub
+	case context.TokenTypeGitLab:
+		repo = brew.GitLab
+	default:
+		return ErrTokenTypeNotImplementedForBrew
+	}
+
+	var gpath = buildFormulaPath(brew.Folder, filename)
+	log.WithField("formula", gpath).
+		WithField("repo", repo.String()).
+		Info("pushing")
+
+	var msg = fmt.Sprintf("Brew formula update for %s version %s", ctx.Config.ProjectName, ctx.Git.CurrentTag)
+	return client.CreateFile(ctx, brew.CommitAuthor, repo, []byte(content), gpath, msg)
 }
 
 func buildFormulaPath(folder, filename string) string {
@@ -259,6 +226,13 @@ func dataFor(ctx *context.Context, cfg config.Homebrew, tokenType context.TokenT
 
 		if cfg.URLTemplate == "" {
 			switch tokenType {
+			case context.TokenTypeGitHub:
+				cfg.URLTemplate = fmt.Sprintf(
+					"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
+					ctx.Config.GitHubURLs.Download,
+					ctx.Config.Release.GitHub.Owner,
+					ctx.Config.Release.GitHub.Name,
+				)
 			case context.TokenTypeGitLab:
 				cfg.URLTemplate = fmt.Sprintf(
 					"%s/%s/%s/uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}",
@@ -267,13 +241,7 @@ func dataFor(ctx *context.Context, cfg config.Homebrew, tokenType context.TokenT
 					ctx.Config.Release.GitLab.Name,
 				)
 			default:
-				log.Warn("no url_template set and not github/gitlab/gitea token found, defaulting to github url template")
-				cfg.URLTemplate = fmt.Sprintf(
-					"%s/%s/%s/releases/download/{{ .Tag }}/{{ .ArtifactName }}",
-					ctx.Config.GitHubURLs.Download,
-					ctx.Config.Release.GitHub.Owner,
-					ctx.Config.Release.GitHub.Name,
-				)
+				return result, ErrTokenTypeNotImplementedForBrew
 			}
 		}
 		url, err := tmpl.New(ctx).WithArtifact(artifact, map[string]string{}).Apply(cfg.URLTemplate)
diff --git a/internal/pipe/docker/docker.go b/internal/pipe/docker/docker.go
index b2a4aea3..1b413d05 100644
--- a/internal/pipe/docker/docker.go
+++ b/internal/pipe/docker/docker.go
@@ -75,6 +75,9 @@ func (Pipe) Run(ctx *context.Context) error {
 
 // Publish the docker images
 func (Pipe) Publish(ctx *context.Context) error {
+	if ctx.SkipPublish {
+		return pipe.ErrSkipPublishEnabled
+	}
 	var images = ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableDockerImage)).List()
 	for _, image := range images {
 		if err := dockerPush(ctx, image); err != nil {
diff --git a/internal/pipe/publish/publish.go b/internal/pipe/publish/publish.go
index 1435bbae..2721dce9 100644
--- a/internal/pipe/publish/publish.go
+++ b/internal/pipe/publish/publish.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/goreleaser/goreleaser/internal/middleware"
-	"github.com/goreleaser/goreleaser/internal/pipe"
 	"github.com/goreleaser/goreleaser/internal/pipe/artifactory"
 	"github.com/goreleaser/goreleaser/internal/pipe/blob"
 	"github.com/goreleaser/goreleaser/internal/pipe/brew"
@@ -49,9 +48,6 @@ var publishers = []Publisher{
 
 // Run the pipe
 func (Pipe) Run(ctx *context.Context) error {
-	if ctx.SkipPublish {
-		return pipe.ErrSkipPublishEnabled
-	}
 	for _, publisher := range publishers {
 		if err := middleware.Logging(
 			publisher.String(),
diff --git a/internal/pipe/release/release.go b/internal/pipe/release/release.go
index 572586cf..d368ff0e 100644
--- a/internal/pipe/release/release.go
+++ b/internal/pipe/release/release.go
@@ -99,6 +99,9 @@ func (Pipe) Default(ctx *context.Context) error {
 
 // Publish github release
 func (Pipe) Publish(ctx *context.Context) error {
+	if ctx.SkipPublish {
+		return pipe.ErrSkipPublishEnabled
+	}
 	c, err := client.New(ctx)
 	if err != nil {
 		return err
diff --git a/internal/pipe/scoop/scoop.go b/internal/pipe/scoop/scoop.go
index 288d4d18..387ac9e1 100644
--- a/internal/pipe/scoop/scoop.go
+++ b/internal/pipe/scoop/scoop.go
@@ -32,7 +32,9 @@ func (Pipe) String() string {
 
 // Publish scoop manifest
 func (Pipe) Publish(ctx *context.Context) error {
-	// TODO(caarlos0): split the creation of the json file and the publishing in two steps, like the brew pipe.
+	if ctx.SkipPublish {
+		return pipe.ErrSkipPublishEnabled
+	}
 	client, err := client.New(ctx)
 	if err != nil {
 		return err
diff --git a/internal/pipeline/pipeline.go b/internal/pipeline/pipeline.go
index 55123b34..d087d752 100644
--- a/internal/pipeline/pipeline.go
+++ b/internal/pipeline/pipeline.go
@@ -4,7 +4,6 @@ package pipeline
 import (
 	"fmt"
 
-	"github.com/goreleaser/goreleaser/internal/pipe/brew"
 	"github.com/goreleaser/goreleaser/internal/pipe/semver"
 	"github.com/goreleaser/goreleaser/internal/pipe/sourcearchive"
 
@@ -55,6 +54,5 @@ var Pipeline = []Piper{
 	checksums.Pipe{},       // checksums of the files
 	sign.Pipe{},            // sign artifacts
 	docker.Pipe{},          // create and push docker images
-	brew.Pipe{},            // create the formula file on dist
 	publish.Pipe{},         // publishes artifacts
 }


Test patch:
diff --git a/internal/builders/golang/build_test.go b/internal/builders/golang/build_test.go
index aa76a30b..a9629223 100644
--- a/internal/builders/golang/build_test.go
+++ b/internal/builders/golang/build_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 var runtimeTarget = runtime.GOOS + "_" + runtime.GOARCH
@@ -91,7 +92,7 @@ func TestBuild(t *testing.T) {
 			{
 				ID:     "foo",
 				Env:    []string{"GO111MODULE=off"},
-				Binary: "bin/foo",
+				Binary: "bin/foo-{{ .Version }}",
 				Targets: []string{
 					"linux_amd64",
 					"darwin_amd64",
@@ -110,6 +111,7 @@ func TestBuild(t *testing.T) {
 	var ctx = context.New(config)
 	ctx.Env["GO_FLAGS"] = "-v"
 	ctx.Git.CurrentTag = "5.6.7"
+	ctx.Version = "v" + ctx.Git.CurrentTag
 	var build = ctx.Config.Builds[0]
 	for _, target := range build.Targets {
 		var ext string
@@ -119,99 +121,101 @@ func TestBuild(t *testing.T) {
 		if target == "js_wasm" {
 			ext = ".wasm"
 		}
+		bin, terr := tmpl.New(ctx).Apply(build.Binary)
+		require.NoError(t, terr)
 		var err = Default.Build(ctx, build, api.Options{
 			Target: target,
-			Name:   build.Binary,
-			Path:   filepath.Join(folder, "dist", target, build.Binary),
+			Name:   bin,
+			Path:   filepath.Join(folder, "dist", target, bin),
 			Ext:    ext,
 		})
 		assert.NoError(t, err)
 	}
 	assert.ElementsMatch(t, ctx.Artifacts.List(), []*artifact.Artifact{
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "linux_amd64", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "linux_amd64", "bin", "foo-v5.6.7"),
 			Goos:   "linux",
 			Goarch: "amd64",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    "",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "linux_mips_softfloat", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "linux_mips_softfloat", "bin", "foo-v5.6.7"),
 			Goos:   "linux",
 			Goarch: "mips",
 			Gomips: "softfloat",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    "",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "linux_mips64le_softfloat", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "linux_mips64le_softfloat", "bin", "foo-v5.6.7"),
 			Goos:   "linux",
 			Goarch: "mips64le",
 			Gomips: "softfloat",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    "",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "darwin_amd64", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "darwin_amd64", "bin", "foo-v5.6.7"),
 			Goos:   "darwin",
 			Goarch: "amd64",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    "",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "linux_arm_6", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "linux_arm_6", "bin", "foo-v5.6.7"),
 			Goos:   "linux",
 			Goarch: "arm",
 			Goarm:  "6",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    "",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "windows_amd64", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "windows_amd64", "bin", "foo-v5.6.7"),
 			Goos:   "windows",
 			Goarch: "amd64",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    ".exe",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
 		{
-			Name:   "bin/foo",
-			Path:   filepath.Join(folder, "dist", "js_wasm", "bin", "foo"),
+			Name:   "bin/foo-v5.6.7",
+			Path:   filepath.Join(folder, "dist", "js_wasm", "bin", "foo-v5.6.7"),
 			Goos:   "js",
 			Goarch: "wasm",
 			Type:   artifact.Binary,
 			Extra: map[string]interface{}{
 				"Ext":    ".wasm",
-				"Binary": "foo",
+				"Binary": "foo-v5.6.7",
 				"ID":     "foo",
 			},
 		},
diff --git a/internal/pipe/blob/blob_minio_test.go b/internal/pipe/blob/blob_minio_test.go
index fdb926ed..050ef326 100644
--- a/internal/pipe/blob/blob_minio_test.go
+++ b/internal/pipe/blob/blob_minio_test.go
@@ -5,6 +5,7 @@ package blob
 // the test setup and teardown
 
 import (
+	"io"
 	"io/ioutil"
 	"net"
 	"os"
@@ -19,6 +20,7 @@ import (
 	"github.com/goreleaser/goreleaser/pkg/context"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	"gocloud.dev/blob"
 )
 
 func TestMinioUpload(t *testing.T) {
@@ -153,7 +155,7 @@ func TestMinioUploadInvalidCustomBucketID(t *testing.T) {
 			},
 		},
 	})
-	ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
+	ctx.Git = context.GitInfo{CurrentTag: "v1.1.0"}
 	ctx.Artifacts.Add(&artifact.Artifact{
 		Type: artifact.UploadableArchive,
 		Name: "bin.tar.gz",
@@ -172,6 +174,77 @@ func TestMinioUploadInvalidCustomBucketID(t *testing.T) {
 	assert.Error(t, Pipe{}.Publish(ctx))
 }
 
+func TestMinioUploadSkipPublish(t *testing.T) {
+	var listen = randomListen(t)
+	folder, err := ioutil.TempDir("", "goreleasertest")
+	assert.NoError(t, err)
+	srcpath := filepath.Join(folder, "source.tar.gz")
+	tgzpath := filepath.Join(folder, "bin.tar.gz")
+	debpath := filepath.Join(folder, "bin.deb")
+	checkpath := filepath.Join(folder, "check.txt")
+	assert.NoError(t, ioutil.WriteFile(checkpath, []byte("fake checksums"), 0744))
+	assert.NoError(t, ioutil.WriteFile(srcpath, []byte("fake\nsrc"), 0744))
+	assert.NoError(t, ioutil.WriteFile(tgzpath, []byte("fake\ntargz"), 0744))
+	assert.NoError(t, ioutil.WriteFile(debpath, []byte("fake\ndeb"), 0744))
+	var ctx = context.New(config.Project{
+		Dist:        folder,
+		ProjectName: "testupload",
+		Blobs: []config.Blob{
+			{
+				Provider: "s3",
+				Bucket:   "test",
+				Region:   "us-east",
+				Endpoint: "http://" + listen,
+				IDs:      []string{"foo", "bar"},
+			},
+		},
+	})
+	ctx.SkipPublish = true
+	ctx.Git = context.GitInfo{CurrentTag: "v1.2.0"}
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Type: artifact.Checksum,
+		Name: "checksum.txt",
+		Path: checkpath,
+	})
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Type: artifact.UploadableSourceArchive,
+		Name: "source.tar.gz",
+		Path: srcpath,
+		Extra: map[string]interface{}{
+			"Format": "tar.gz",
+		},
+	})
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Type: artifact.UploadableArchive,
+		Name: "bin.tar.gz",
+		Path: tgzpath,
+		Extra: map[string]interface{}{
+			"ID": "foo",
+		},
+	})
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Type: artifact.LinuxPackage,
+		Name: "bin.deb",
+		Path: debpath,
+		Extra: map[string]interface{}{
+			"ID": "bar",
+		},
+	})
+	var name = "test_upload"
+	defer stop(t, name)
+	start(t, name, listen)
+	prepareEnv(t, listen)
+	assert.NoError(t, Pipe{}.Default(ctx))
+	assert.NoError(t, Pipe{}.Publish(ctx))
+
+	require.NotContains(t, getFiles(t, ctx, ctx.Config.Blobs[0]), []string{
+		"testupload/v1.2.0/bin.deb",
+		"testupload/v1.2.0/bin.tar.gz",
+		"testupload/v1.2.0/checksum.txt",
+		"testupload/v1.2.0/source.tar.gz",
+	})
+}
+
 func randomListen(t *testing.T) string {
 	listener, err := net.Listen("tcp", "127.0.0.1:0")
 	require.NoError(t, err)
@@ -229,19 +302,20 @@ func removeTestData(t *testing.T) {
 	_ = os.RemoveAll("./testdata/data/test/testupload") // dont care if it fails
 }
 
-func getFiles(t *testing.T, ctx *context.Context, blob config.Blob) []string {
-	var bucket = Bucket{}
-	url, err := bucket.url(ctx, blob)
+func getFiles(t *testing.T, ctx *context.Context, cfg config.Blob) []string {
+	url, err := urlFor(ctx, cfg)
 	require.NoError(t, err)
-	conn, err := bucket.Connect(ctx, url)
+	conn, err := blob.OpenBucket(ctx, url)
 	require.NoError(t, err)
+	defer conn.Close()
 	var iter = conn.List(nil)
 	var files []string
 	for {
 		file, err := iter.Next(ctx)
-		if err != nil {
+		if err != nil && err == io.EOF {
 			break
 		}
+		require.NoError(t, err)
 		files = append(files, file.Key)
 	}
 	return files
diff --git a/internal/pipe/blob/blob_test.go b/internal/pipe/blob/blob_test.go
index b00e7593..fe9acb1b 100644
--- a/internal/pipe/blob/blob_test.go
+++ b/internal/pipe/blob/blob_test.go
@@ -240,10 +240,8 @@ func TestPipe_Publish(t *testing.T) {
 }
 
 func TestURL(t *testing.T) {
-	var buck = Bucket{}
-
 	t.Run("s3 with opts", func(t *testing.T) {
-		url, err := buck.url(context.New(config.Project{}), config.Blob{
+		url, err := urlFor(context.New(config.Project{}), config.Blob{
 			Bucket:     "foo",
 			Provider:   "s3",
 			Region:     "us-west-1",
@@ -256,7 +254,7 @@ func TestURL(t *testing.T) {
 	})
 
 	t.Run("s3 with some opts", func(t *testing.T) {
-		url, err := buck.url(context.New(config.Project{}), config.Blob{
+		url, err := urlFor(context.New(config.Project{}), config.Blob{
 			Bucket:     "foo",
 			Provider:   "s3",
 			Region:     "us-west-1",
@@ -267,7 +265,7 @@ func TestURL(t *testing.T) {
 	})
 
 	t.Run("gs with opts", func(t *testing.T) {
-		url, err := buck.url(context.New(config.Project{}), config.Blob{
+		url, err := urlFor(context.New(config.Project{}), config.Blob{
 			Bucket:     "foo",
 			Provider:   "gs",
 			Region:     "us-west-1",
@@ -280,7 +278,7 @@ func TestURL(t *testing.T) {
 	})
 
 	t.Run("s3 no opts", func(t *testing.T) {
-		url, err := buck.url(context.New(config.Project{}), config.Blob{
+		url, err := urlFor(context.New(config.Project{}), config.Blob{
 			Bucket:   "foo",
 			Provider: "s3",
 		})
@@ -289,7 +287,7 @@ func TestURL(t *testing.T) {
 	})
 
 	t.Run("gs no opts", func(t *testing.T) {
-		url, err := buck.url(context.New(config.Project{}), config.Blob{
+		url, err := urlFor(context.New(config.Project{}), config.Blob{
 			Bucket:   "foo",
 			Provider: "gs",
 		})
diff --git a/internal/pipe/brew/brew_test.go b/internal/pipe/brew/brew_test.go
index 73b2a479..cd21bbab 100644
--- a/internal/pipe/brew/brew_test.go
+++ b/internal/pipe/brew/brew_test.go
@@ -13,7 +13,6 @@ import (
 	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
 var update = flag.Bool("update", false, "update .golden files")
@@ -249,8 +248,7 @@ func TestRunPipe(t *testing.T) {
 			client := &DummyClient{}
 			var distFile = filepath.Join(folder, name+".rb")
 
-			require.NoError(t, Pipe{}.Run(ctx))
-			assert.NoError(t, doPublish(ctx, client))
+			assert.NoError(t, doRun(ctx, ctx.Config.Brews[0], client))
 			assert.True(t, client.CreatedFile)
 			var golden = fmt.Sprintf("testdata/%s.rb.golden", name)
 			if *update {
@@ -377,8 +375,7 @@ func TestRunPipeForMultipleArmVersions(t *testing.T) {
 		client := &DummyClient{}
 		var distFile = filepath.Join(folder, name+".rb")
 
-		require.NoError(t, Pipe{}.Run(ctx))
-		assert.NoError(t, doPublish(ctx, client))
+		assert.NoError(t, doRun(ctx, ctx.Config.Brews[0], client))
 		assert.True(t, client.CreatedFile)
 		var golden = fmt.Sprintf("testdata/%s.rb.golden", name)
 		if *update {
@@ -409,9 +406,8 @@ func TestRunPipeNoDarwin64Build(t *testing.T) {
 		},
 	}
 	client := &DummyClient{}
-	require.Equal(t, ErrNoArchivesFound, Pipe{}.Run(ctx))
-	testlib.AssertSkipped(t, doPublish(ctx, client))
-	require.False(t, client.CreatedFile)
+	assert.Equal(t, ErrNoArchivesFound, doRun(ctx, ctx.Config.Brews[0], client))
+	assert.False(t, client.CreatedFile)
 }
 
 func TestRunPipeMultipleArchivesSameOsBuild(t *testing.T) {
@@ -554,9 +550,8 @@ func TestRunPipeMultipleArchivesSameOsBuild(t *testing.T) {
 			})
 		}
 		client := &DummyClient{}
-		require.Equal(t, test.expectedError, Pipe{}.Run(ctx))
-		testlib.AssertSkipped(t, doPublish(ctx, client))
-		require.False(t, client.CreatedFile)
+		assert.Equal(t, test.expectedError, doRun(ctx, ctx.Config.Brews[0], client))
+		assert.False(t, client.CreatedFile)
 		// clean the artifacts for the next run
 		ctx.Artifacts = artifact.New()
 	}
@@ -567,8 +562,7 @@ func TestRunPipeBrewNotSetup(t *testing.T) {
 		Config: config.Project{},
 	}
 	client := &DummyClient{}
-	require.NoError(t, Pipe{}.Run(ctx))
-	testlib.AssertSkipped(t, doPublish(ctx, client))
+	testlib.AssertSkipped(t, doRun(ctx, config.Homebrew{}, client))
 	assert.False(t, client.CreatedFile)
 }
 
@@ -593,73 +587,97 @@ func TestRunPipeBinaryRelease(t *testing.T) {
 		Type:   artifact.Binary,
 	})
 	client := &DummyClient{}
-	require.Equal(t, ErrNoArchivesFound, Pipe{}.Run(ctx))
-	testlib.AssertSkipped(t, doPublish(ctx, client))
-	require.False(t, client.CreatedFile)
+	assert.Equal(t, ErrNoArchivesFound, doRun(ctx, ctx.Config.Brews[0], client))
+	assert.False(t, client.CreatedFile)
 }
 
 func TestRunPipeNoUpload(t *testing.T) {
 	folder, err := ioutil.TempDir("", "goreleasertest")
 	assert.NoError(t, err)
-
-	var newCtx = func() *context.Context {
-		var ctx = context.New(config.Project{
-			Dist:        folder,
-			ProjectName: "foo",
-			Release:     config.Release{},
-			Brews: []config.Homebrew{
-				{
-					GitHub: config.Repo{
-						Owner: "test",
-						Name:  "test",
-					},
+	var ctx = context.New(config.Project{
+		Dist:        folder,
+		ProjectName: "foo",
+		Release:     config.Release{},
+		Brews: []config.Homebrew{
+			{
+				GitHub: config.Repo{
+					Owner: "test",
+					Name:  "test",
 				},
 			},
-		})
-		ctx.TokenType = context.TokenTypeGitHub
-		ctx.Git = context.GitInfo{CurrentTag: "v1.0.1"}
-		var path = filepath.Join(folder, "whatever.tar.gz")
-		_, err = os.Create(path)
-		assert.NoError(t, err)
-		ctx.Artifacts.Add(&artifact.Artifact{
-			Name:   "bin",
-			Path:   path,
-			Goos:   "darwin",
-			Goarch: "amd64",
-			Type:   artifact.UploadableArchive,
-			Extra: map[string]interface{}{
-				"ID":     "foo",
-				"Format": "tar.gz",
-			},
-		})
-		return ctx
-	}
-	var client = &DummyClient{}
+		},
+	})
+	ctx.TokenType = context.TokenTypeGitHub
+	ctx.Git = context.GitInfo{CurrentTag: "v1.0.1"}
+	var path = filepath.Join(folder, "whatever.tar.gz")
+	_, err = os.Create(path)
+	assert.NoError(t, err)
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Name:   "bin",
+		Path:   path,
+		Goos:   "darwin",
+		Goarch: "amd64",
+		Type:   artifact.UploadableArchive,
+		Extra: map[string]interface{}{
+			"ID":     "foo",
+			"Format": "tar.gz",
+		},
+	})
+	client := &DummyClient{}
 
-	var assertNoPublish = func(t *testing.T, ctx *context.Context) {
-		require.NoError(t, Pipe{}.Run(ctx))
-		testlib.AssertSkipped(t, doPublish(ctx, client))
+	var assertNoPublish = func(t *testing.T) {
+		testlib.AssertSkipped(t, doRun(ctx, ctx.Config.Brews[0], client))
 		assert.False(t, client.CreatedFile)
 	}
-
 	t.Run("skip upload", func(tt *testing.T) {
-		var ctx = newCtx()
 		ctx.Config.Release.Draft = false
 		ctx.Config.Brews[0].SkipUpload = "true"
 		ctx.SkipPublish = false
-		require.NoError(t, Pipe{}.Run(ctx))
-		assertNoPublish(tt, ctx)
+		assertNoPublish(tt)
 	})
 	t.Run("skip publish", func(tt *testing.T) {
-		var ctx = newCtx()
 		ctx.Config.Release.Draft = false
 		ctx.Config.Brews[0].SkipUpload = "false"
 		ctx.SkipPublish = true
-		require.NoError(t, Pipe{}.Run(ctx))
-		assertNoPublish(tt, ctx)
+		assertNoPublish(tt)
 	})
 }
 
+func TestRunTokenTypeNotImplementedForBrew(t *testing.T) {
+	folder, err := ioutil.TempDir("", "goreleasertest")
+	assert.NoError(t, err)
+	var ctx = context.New(config.Project{
+		Dist:        folder,
+		ProjectName: "foo",
+		Release:     config.Release{},
+		Brews: []config.Homebrew{
+			{
+				GitHub: config.Repo{
+					Owner: "test",
+					Name:  "test",
+				},
+			},
+		},
+	})
+	ctx.Git = context.GitInfo{CurrentTag: "v1.0.1"}
+	var path = filepath.Join(folder, "whatever.tar.gz")
+	_, err = os.Create(path)
+	assert.NoError(t, err)
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Name:   "bin",
+		Path:   path,
+		Goos:   "darwin",
+		Goarch: "amd64",
+		Type:   artifact.UploadableArchive,
+		Extra: map[string]interface{}{
+			"ID":     "foo",
+			"Format": "tar.gz",
+		},
+	})
+	client := &DummyClient{}
+	assert.Equal(t, ErrTokenTypeNotImplementedForBrew, doRun(ctx, ctx.Config.Brews[0], client))
+}
+
 func TestDefault(t *testing.T) {
 	_, back := testlib.Mktmp(t)
 	defer back()
diff --git a/internal/pipe/build/build_test.go b/internal/pipe/build/build_test.go
index f007cf43..2e4070c4 100644
--- a/internal/pipe/build/build_test.go
+++ b/internal/pipe/build/build_test.go
@@ -18,10 +18,6 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-var fakeArtifact = &artifact.Artifact{
-	Name: "fake",
-}
-
 type fakeBuilder struct {
 	fail bool
 }
@@ -42,7 +38,9 @@ func (f *fakeBuilder) Build(ctx *context.Context, build config.Build, options ap
 	if err := ioutil.WriteFile(options.Path, []byte("foo"), 0755); err != nil {
 		return err
 	}
-	ctx.Artifacts.Add(fakeArtifact)
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Name: options.Name,
+	})
 	return nil
 }
 
@@ -104,7 +102,9 @@ func TestRunPipe(t *testing.T) {
 	var ctx = context.New(config)
 	ctx.Git.CurrentTag = "2.4.5"
 	assert.NoError(t, Pipe{}.Run(ctx))
-	assert.Equal(t, ctx.Artifacts.List(), []*artifact.Artifact{fakeArtifact})
+	assert.Equal(t, ctx.Artifacts.List(), []*artifact.Artifact{{
+		Name: "testing",
+	}})
 }
 
 func TestRunFullPipe(t *testing.T) {
@@ -136,7 +136,9 @@ func TestRunFullPipe(t *testing.T) {
 	var ctx = context.New(config)
 	ctx.Git.CurrentTag = "2.4.5"
 	assert.NoError(t, Pipe{}.Run(ctx))
-	assert.Equal(t, ctx.Artifacts.List(), []*artifact.Artifact{fakeArtifact})
+	assert.Equal(t, ctx.Artifacts.List(), []*artifact.Artifact{{
+		Name: "testing",
+	}})
 	assert.FileExists(t, post)
 	assert.FileExists(t, pre)
 	assert.FileExists(t, filepath.Join(folder, "build1_whatever", "testing"))
diff --git a/internal/pipe/publish/publish_test.go b/internal/pipe/publish/publish_test.go
index d9fa1b51..86a80b27 100644
--- a/internal/pipe/publish/publish_test.go
+++ b/internal/pipe/publish/publish_test.go
@@ -3,7 +3,6 @@ package publish
 import (
 	"testing"
 
-	"github.com/goreleaser/goreleaser/internal/pipe"
 	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 	"github.com/stretchr/testify/require"
@@ -13,12 +12,6 @@ func TestDescription(t *testing.T) {
 	require.NotEmpty(t, Pipe{}.String())
 }
 
-func TestPublishDisable(t *testing.T) {
-	var ctx = context.New(config.Project{})
-	ctx.SkipPublish = true
-	require.EqualError(t, Pipe{}.Run(ctx), pipe.ErrSkipPublishEnabled.Error())
-}
-
 func TestPublish(t *testing.T) {
 	var ctx = context.New(config.Project{})
 	ctx.Config.Release.Disable = true