Repository: goreleaser/goreleaser
Pull request #1371 (2020-03-04 03:13:26)

fix: http.target template
<!-- If applied, this commit will... -->

Use the standard template package on the http upload pipe.

<!-- Why is this change being made? -->

This way, users can have more fields available on the template, as for instance, the `Os` or `Arch`.

<!-- Provide links to any relevant tickets, URLs or other resources -->

refs #1357

codecov-io: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1371?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@1caf149`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `100%`.

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

```diff
@@            Coverage Diff            @@
##             master    #1371   +/-   ##
=========================================
  Coverage          ?   83.91%           
=========================================
  Files             ?       57           
  Lines             ?     3327           
  Branches          ?        0           
=========================================
  Hits              ?     2792           
  Misses            ?      455           
  Partials          ?       80
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/1371?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [internal/pipe/snapcraft/snapcraft.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1371/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGlwZS9zbmFwY3JhZnQvc25hcGNyYWZ0Lmdv) | `83.73% <100%> (ø)` | |
| [internal/pipe/archive/archive.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1371/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGlwZS9hcmNoaXZlL2FyY2hpdmUuZ28=) | `96.34% <100%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1371?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/1371?src=pr&el=footer). Last update [1caf149...83796ee](https://codecov.io/gh/goreleaser/goreleaser/pull/1371?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 48c7b6da..7c76adb3 100644
--- a/internal/http/http.go
+++ b/internal/http/http.go
@@ -2,11 +2,9 @@
 package http
 
 import (
-	"bytes"
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
-	"html/template"
 	"io"
 	h "net/http"
 	"os"
@@ -19,6 +17,7 @@ import (
 	"github.com/goreleaser/goreleaser/internal/artifact"
 	"github.com/goreleaser/goreleaser/internal/pipe"
 	"github.com/goreleaser/goreleaser/internal/semerrgroup"
+	"github.com/goreleaser/goreleaser/internal/tmpl"
 	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 )
@@ -351,51 +350,15 @@ func executeHTTPRequest(ctx *context.Context, upload *config.Upload, req *h.Requ
 	return resp, err
 }
 
-// targetData is used as a template struct for
-// Artifactory.Target
-type targetData struct {
-	Version      string
-	Tag          string
-	ProjectName  string
-	ArtifactName string
-
-	// Only supported in mode binary
-	Os   string
-	Arch string
-	Arm  string
-}
-
 // resolveTargetTemplate returns the resolved target template with replaced variables
 // Those variables can be replaced by the given context, goos, goarch, goarm and more
-// 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,
-		ArtifactName: artifact.Name,
-	}
-
+	var replacements = map[string]string{}
 	if upload.Mode == ModeBinary {
 		// TODO: multiple archives here
-		data.Os = replace(ctx.Config.Archives[0].Replacements, artifact.Goos)
-		data.Arch = replace(ctx.Config.Archives[0].Replacements, artifact.Goarch)
-		data.Arm = replace(ctx.Config.Archives[0].Replacements, artifact.Goarm)
-	}
-
-	var out bytes.Buffer
-	t, err := template.New(ctx.Config.ProjectName).Parse(upload.Target)
-	if err != nil {
-		return "", err
-	}
-	err = t.Execute(&out, data)
-	return out.String(), err
-}
-
-func replace(replacements map[string]string, original string) string {
-	result := replacements[original]
-	if result == "" {
-		return original
+		replacements = ctx.Config.Archives[0].Replacements
 	}
-	return result
+	return tmpl.New(ctx).
+		WithArtifact(artifact, replacements).
+		Apply(upload.Target)
 }
diff --git a/www/content/upload.md b/www/content/upload.md
index 2ca24074..0b43aaf9 100644
--- a/www/content/upload.md
+++ b/www/content/upload.md
@@ -30,7 +30,7 @@ Prerequisites:
 
 ### Target
 
-The `target` is the URL to upload the artifacts to (_without_ the name of the artifact).
+The `target` is the template of the URL to upload the artifacts to (_without_ the name of the artifact).
 
 An example configuration for `goreleaser` in upload mode `binary` with the target can look like
 
@@ -66,7 +66,7 @@ The configured name of your HTTP server will be used to build the environment
 variable name.
 This way we support auth for multiple instances.
 This also means that the `name` per configured instance needs to be unique
-per goreleaser configuration.
+per GoReleaser configuration.
 
 The name of the environment variable will be `UPLOAD_NAME_USERNAME`.
 If your instance is named `production`, you can store the username in the
@@ -82,7 +82,7 @@ The password will be stored in a environment variable.
 The configured name of your HTTP server will be used.
 This way we support auth for multiple instances.
 This also means that the `name` per configured instance needs to be unique
-per goreleaser configuration.
+per GoReleaser configuration.
 
 The name of the environment variable will be `UPLOAD_NAME_SECRET`.
 If your instance is named `production`, you need to store the secret in the
@@ -144,7 +144,7 @@ uploads:
     # Default is `archive`.
     mode: archive
 
-    # URL to be used as target of the HTTP request
+    # Template of the 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)
@@ -178,3 +178,5 @@ uploads:
 ```
 
 These settings should allow you to push your artifacts into multiple HTTP servers.
+
+> Learn more about the [name template engine](/templates).


Test patch:
diff --git a/internal/http/http_test.go b/internal/http/http_test.go
index 417db688..c23c2290 100644
--- a/internal/http/http_test.go
+++ b/internal/http/http_test.go
@@ -208,7 +208,11 @@ func TestUpload(t *testing.T) {
 	ctx := context.New(config.Project{
 		ProjectName: "blah",
 		Archives: []config.Archive{
-			{},
+			{
+				Replacements: map[string]string{
+					"linux": "Linux",
+				},
+			},
 		},
 	})
 	ctx.Env["TEST_A_SECRET"] = "x"
@@ -232,9 +236,11 @@ func TestUpload(t *testing.T) {
 		var file = filepath.Join(folder, "a."+a.ext)
 		require.NoError(t, ioutil.WriteFile(file, []byte("lorem ipsum"), 0644))
 		ctx.Artifacts.Add(&artifact.Artifact{
-			Name: "a." + a.ext,
-			Path: file,
-			Type: a.typ,
+			Name:   "a." + a.ext,
+			Goos:   "linux",
+			Goarch: "amd64",
+			Path:   file,
+			Type:   a.typ,
 			Extra: map[string]interface{}{
 				"ID": "foo",
 			},
@@ -307,6 +313,21 @@ func TestUpload(t *testing.T) {
 				check{"/blah/2.1.0/a.tar", "u1", "x", content, map[string]string{}},
 			),
 		},
+		{"archive_with_os_tmpl", true, true, false, false,
+			func(s *httptest.Server) (*context.Context, config.Upload) {
+				return ctx, config.Upload{
+					Mode:         ModeArchive,
+					Name:         "a",
+					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
+					Username:     "u1",
+					TrustedCerts: cert(s),
+				}
+			},
+			checks(
+				check{"/blah/2.1.0/linux/amd64/a.deb", "u1", "x", content, map[string]string{}},
+				check{"/blah/2.1.0/linux/amd64/a.tar", "u1", "x", content, map[string]string{}},
+			),
+		},
 		{"archive_with_ids", true, true, false, false,
 			func(s *httptest.Server) (*context.Context, config.Upload) {
 				return ctx, config.Upload{
@@ -335,6 +356,18 @@ func TestUpload(t *testing.T) {
 			},
 			checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content, map[string]string{}}),
 		},
+		{"binary_with_os_tmpl", true, true, false, false,
+			func(s *httptest.Server) (*context.Context, config.Upload) {
+				return ctx, config.Upload{
+					Mode:         ModeBinary,
+					Name:         "a",
+					Target:       s.URL + "/{{.ProjectName}}/{{.Version}}/{{.Os}}/{{.Arch}}",
+					Username:     "u2",
+					TrustedCerts: cert(s),
+				}
+			},
+			checks(check{"/blah/2.1.0/Linux/amd64/a.ubi", "u2", "x", content, map[string]string{}}),
+		},
 		{"binary_with_ids", true, true, false, false,
 			func(s *httptest.Server) (*context.Context, config.Upload) {
 				return ctx, config.Upload{
diff --git a/internal/pipe/artifactory/artifactory_test.go b/internal/pipe/artifactory/artifactory_test.go
index 0cf28347..551ab41b 100644
--- a/internal/pipe/artifactory/artifactory_test.go
+++ b/internal/pipe/artifactory/artifactory_test.go
@@ -383,7 +383,7 @@ func TestRunPipe_TargetTemplateError(t *testing.T) {
 	})
 
 	assert.NoError(t, Pipe{}.Default(ctx))
-	assert.EqualError(t, Pipe{}.Publish(ctx), `artifactory: error while building the target url: template: mybin:1: unexpected "/" in operand`)
+	assert.EqualError(t, Pipe{}.Publish(ctx), `artifactory: error while building the target url: template: tmpl:1: unexpected "/" in operand`)
 }
 
 func TestRunPipe_BadCredentials(t *testing.T) {
diff --git a/internal/pipe/upload/upload_test.go b/internal/pipe/upload/upload_test.go
index 865bda97..e2edf257 100644
--- a/internal/pipe/upload/upload_test.go
+++ b/internal/pipe/upload/upload_test.go
@@ -422,7 +422,7 @@ func TestRunPipe_TargetTemplateError(t *testing.T) {
 	})
 	err = Pipe{}.Publish(ctx)
 	assert.Error(t, err)
-	assert.Contains(t, err.Error(), `upload: error while building the target url: template: mybin:1: unexpected "/" in operand`)
+	assert.Contains(t, err.Error(), `upload: error while building the target url: template: tmpl:1: unexpected "/" in operand`)
 }
 
 func TestRunPipe_BadCredentials(t *testing.T) {