Repository: goreleaser/goreleaser
Pull request #1338 (2020-02-10 22:19:42)

feat: Add the ability to place artifact name
This commit exposes the Artifact name as a template variable so that the
artifact name can be moved around within the `targetURL`. This enable users to
manipulate the target URL as desired in order to pass in PUT parameters that
doesn't always following the currently defined `targetURL` which append the
artifact name to the end.

This is needed to address[1] by enable the ability to add metadata to
Artifactory REST API [2] as the URL parameters, which need to be after the
artifact names.

[1]:https://github.com/goreleaser/goreleaser/issues/1336
[2]:https://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API#ArtifactoryRESTAPI-Example-DeployinganArtifact

<!--

Hi, thanks for contributing!

Please make sure you read our CONTRIBUTING guide.

Please fill the fields above:

-->

<!-- If applied, this commit will... -->

<!-- Why is this change being made? -->

<!-- # Provide links to any relevant tickets, URLs or other resources -->


sysbot: @caarlos0 thanks for looking! So I made a change to have this coming from a new Variable and provided the tests and documentations for it. This way, it has to be opt-in to no affect existing configuration and avoiding the edge case of the filename is the same as one of the path name.
codecov-io: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=h1) Report
> Merging [#1338](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=desc) into [master](https://codecov.io/gh/goreleaser/goreleaser/commit/1435e2bcc540b674df5754f824f9ce3c73a7076c?src=pr&el=desc) will **increase** coverage by `0.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/goreleaser/pull/1338/graphs/tree.svg?width=650&token=3bJvv6b7hN&height=150&src=pr)](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1338      +/-   ##
==========================================
+ Coverage    83.7%   83.72%   +0.01%     
==========================================
  Files          57       57              
  Lines        3295     3299       +4     
==========================================
+ Hits         2758     2762       +4     
  Misses        460      460              
  Partials       77       77
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/config/config.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1338/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZy9jb25maWcuZ28=) | `100% <ø> (ø)` | :arrow_up: |
| [internal/http/http.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1338/diff?src=pr&el=tree#diff-aW50ZXJuYWwvaHR0cC9odHRwLmdv) | `90.43% <100%> (+0.18%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=footer). Last update [1435e2b...c88db4b](https://codecov.io/gh/goreleaser/goreleaser/pull/1338?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/internal/http/http.go b/internal/http/http.go
index 6540ccb6..48c7b6da 100644
--- a/internal/http/http.go
+++ b/internal/http/http.go
@@ -231,11 +231,15 @@ func uploadAsset(ctx *context.Context, upload *config.Upload, artifact *artifact
 	}
 	defer asset.ReadCloser.Close() // nolint: errcheck
 
-	// The target url needs to contain the artifact name
-	if !strings.HasSuffix(targetURL, "/") {
-		targetURL += "/"
+	// target url need to contain the artifact name unless the custom
+	// artifact name is used
+	if !upload.CustomArtifactName {
+		if !strings.HasSuffix(targetURL, "/") {
+			targetURL += "/"
+		}
+		targetURL += artifact.Name
 	}
-	targetURL += artifact.Name
+	log.Debugf("generated target url: %s", targetURL)
 
 	var headers = map[string]string{}
 	if upload.ChecksumHeader != "" {
@@ -350,9 +354,10 @@ func executeHTTPRequest(ctx *context.Context, upload *config.Upload, req *h.Requ
 // targetData is used as a template struct for
 // Artifactory.Target
 type targetData struct {
-	Version     string
-	Tag         string
-	ProjectName string
+	Version      string
+	Tag          string
+	ProjectName  string
+	ArtifactName string
 
 	// Only supported in mode binary
 	Os   string
@@ -365,9 +370,10 @@ type targetData struct {
 // TODO: replace this with our internal template pkg
 func resolveTargetTemplate(ctx *context.Context, upload *config.Upload, artifact *artifact.Artifact) (string, error) {
 	data := targetData{
-		Version:     ctx.Version,
-		Tag:         ctx.Git.CurrentTag,
-		ProjectName: ctx.Config.ProjectName,
+		Version:      ctx.Version,
+		Tag:          ctx.Git.CurrentTag,
+		ProjectName:  ctx.Config.ProjectName,
+		ArtifactName: artifact.Name,
 	}
 
 	if upload.Mode == ModeBinary {
diff --git a/pkg/config/config.go b/pkg/config/config.go
index e714c5ff..ac492f21 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -330,16 +330,17 @@ type Blob struct {
 
 // Upload configuration
 type Upload struct {
-	Name           string   `yaml:",omitempty"`
-	IDs            []string `yaml:"ids,omitempty"`
-	Target         string   `yaml:",omitempty"`
-	Username       string   `yaml:",omitempty"`
-	Mode           string   `yaml:",omitempty"`
-	Method         string   `yaml:",omitempty"`
-	ChecksumHeader string   `yaml:"checksum_header,omitempty"`
-	TrustedCerts   string   `yaml:"trusted_certificates,omitempty"`
-	Checksum       bool     `yaml:",omitempty"`
-	Signature      bool     `yaml:",omitempty"`
+	Name               string   `yaml:",omitempty"`
+	IDs                []string `yaml:"ids,omitempty"`
+	Target             string   `yaml:",omitempty"`
+	Username           string   `yaml:",omitempty"`
+	Mode               string   `yaml:",omitempty"`
+	Method             string   `yaml:",omitempty"`
+	ChecksumHeader     string   `yaml:"checksum_header,omitempty"`
+	TrustedCerts       string   `yaml:"trusted_certificates,omitempty"`
+	Checksum           bool     `yaml:",omitempty"`
+	Signature          bool     `yaml:",omitempty"`
+	CustomArtifactName bool     `yaml:"custom_artifact_name,omitempty"`
 }
 
 // Project includes all project configuration
diff --git a/www/content/upload.md b/www/content/upload.md
index 5ae392a2..2ca24074 100644
--- a/www/content/upload.md
+++ b/www/content/upload.md
@@ -46,12 +46,16 @@ Supported variables:
 - Version
 - Tag
 - ProjectName
+- ArtifactName
 - Os
 - Arch
 - Arm
 
 > **Warning**: Variables `Os`, `Arch` and `Arm` are only supported in upload mode `binary`.
 
+For `archive` mode, it will also included the `LinuxPackage` type which is
+generated by `nfpm` and the like.
+
 ### Username
 
 Your configured username needs to be valid against your HTTP server.
@@ -143,6 +147,13 @@ uploads:
     # URL to be used as target of the HTTP request
     target: https://some.server/some/path/example-repo-local/{{ .ProjectName }}/{{ .Version }}/
 
+    # Custom artifact name (defaults to false)
+    # If enable, you must supply the name of the Artifact as part of the Target
+    # URL as it will not be automatically append to the end of the URL, its
+    # pre-computed name is available as _ArtifactName_ for example
+    # target: https://some.server/some/path/example-repo-local/{{ .ArtifactName }};deb.distribution=xenial
+    custom_artifact_name: true
+
     # User that will be used for the deployment
     username: deployuser
 


Test patch:
diff --git a/internal/pipe/upload/upload_test.go b/internal/pipe/upload/upload_test.go
index 1092e32c..a337fd0b 100644
--- a/internal/pipe/upload/upload_test.go
+++ b/internal/pipe/upload/upload_test.go
@@ -215,6 +215,145 @@ func TestRunPipe_ModeArchive(t *testing.T) {
 	assert.True(t, ok, "deb file was not uploaded")
 }
 
+func TestRunPipe_ModeBinary_CustomArtifactName(t *testing.T) {
+	setup()
+	defer teardown()
+
+	folder, err := ioutil.TempDir("", "archivetest")
+	assert.NoError(t, err)
+	var dist = filepath.Join(folder, "dist")
+	assert.NoError(t, os.Mkdir(dist, 0755))
+	assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755))
+	var binPath = filepath.Join(dist, "mybin", "mybin")
+	d1 := []byte("hello\ngo\n")
+	err = ioutil.WriteFile(binPath, d1, 0666)
+	assert.NoError(t, err)
+
+	// Dummy http server
+	mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		testHeader(t, r, "Content-Length", "9")
+		// Basic auth of user "deployuser" with secret "deployuser-secret"
+		testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
+
+		w.Header().Set("Location", "/production-repo-remote/mybin/linux/amd64/mybin;deb.distribution=xenial")
+		w.WriteHeader(http.StatusCreated)
+	})
+	mux.HandleFunc("/example-repo-local/mybin/linux/amd64/mybin;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		testHeader(t, r, "Content-Length", "9")
+		// Basic auth of user "deployuser" with secret "deployuser-secret"
+		testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
+
+		w.Header().Set("Location", "/example-repo-local/mybin/linux/amd64/mybin;deb.distribution=xenial")
+		w.WriteHeader(http.StatusCreated)
+	})
+
+	var ctx = context.New(config.Project{
+		ProjectName: "mybin",
+		Dist:        dist,
+		Uploads: []config.Upload{
+			{
+				Method:             h.MethodPut,
+				Name:               "production-us",
+				Mode:               "binary",
+				Target:             fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}/{{ .ArtifactName }};deb.distribution=xenial", server.URL),
+				Username:           "deployuser",
+				CustomArtifactName: true,
+			},
+		},
+		Archives: []config.Archive{
+			{},
+		},
+	})
+	ctx.Env = map[string]string{
+		"UPLOAD_PRODUCTION-US_SECRET": "deployuser-secret",
+	}
+	for _, goos := range []string{"linux", "darwin"} {
+		ctx.Artifacts.Add(&artifact.Artifact{
+			Name:   "mybin",
+			Path:   binPath,
+			Goarch: "amd64",
+			Goos:   goos,
+			Type:   artifact.UploadableBinary,
+		})
+	}
+
+	assert.NoError(t, Pipe{}.Publish(ctx))
+}
+
+func TestRunPipe_ModeArchive_CustomArtifactName(t *testing.T) {
+	setup()
+	defer teardown()
+
+	folder, err := ioutil.TempDir("", "goreleasertest")
+	assert.NoError(t, err)
+	tarfile, err := os.Create(filepath.Join(folder, "bin.tar.gz"))
+	assert.NoError(t, err)
+	debfile, err := os.Create(filepath.Join(folder, "bin.deb"))
+	assert.NoError(t, err)
+
+	var ctx = context.New(config.Project{
+		ProjectName: "goreleaser",
+		Dist:        folder,
+		Uploads: []config.Upload{
+			{
+				Method:             h.MethodPut,
+				Name:               "production",
+				Mode:               "archive",
+				Target:             fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Version }}/{{ .ArtifactName }};deb.distribution=xenial", server.URL),
+				Username:           "deployuser",
+				CustomArtifactName: true,
+			},
+		},
+		Archives: []config.Archive{
+			{},
+		},
+	})
+	ctx.Env = map[string]string{
+		"UPLOAD_PRODUCTION_SECRET": "deployuser-secret",
+	}
+	ctx.Version = "1.0.0"
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Type: artifact.UploadableArchive,
+		Name: "bin.tar.gz",
+		Path: tarfile.Name(),
+	})
+	ctx.Artifacts.Add(&artifact.Artifact{
+		Type: artifact.LinuxPackage,
+		Name: "bin.deb",
+		Path: debfile.Name(),
+	})
+
+	var uploads sync.Map
+
+	// Dummy http server
+	mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.tar.gz;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		// Basic auth of user "deployuser" with secret "deployuser-secret"
+		testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
+
+		w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.tar.gz;deb.distribution=xenial")
+		w.WriteHeader(http.StatusCreated)
+		uploads.Store("targz", true)
+	})
+	mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.deb;deb.distribution=xenial", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, http.MethodPut)
+		// Basic auth of user "deployuser" with secret "deployuser-secret"
+		testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
+
+		w.Header().Set("Location", "/example-repo-local/goreleaser/1.0.0/bin.deb;deb.distribution=xenial")
+		w.WriteHeader(http.StatusCreated)
+		uploads.Store("deb", true)
+	})
+
+	assert.NoError(t, Pipe{}.Publish(ctx))
+	_, ok := uploads.Load("targz")
+	assert.True(t, ok, "tar.gz file was not uploaded")
+	_, ok = uploads.Load("deb")
+	assert.True(t, ok, "deb file was not uploaded")
+}
+
 func TestRunPipe_ArtifactoryDown(t *testing.T) {
 	folder, err := ioutil.TempDir("", "goreleasertest")
 	assert.NoError(t, err)