diff --git a/internal/http/http.go b/internal/http/http.go index 6540ccb6e..48c7b6daf 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/internal/pipe/upload/upload_test.go b/internal/pipe/upload/upload_test.go index 1092e32ce..a337fd0b2 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) diff --git a/pkg/config/config.go b/pkg/config/config.go index e714c5ffc..ac492f217 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 5ae392a2f..2ca24074e 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