diff --git a/config/config.go b/config/config.go index 4a1a35846..65df8219d 100644 --- a/config/config.go +++ b/config/config.go @@ -198,6 +198,7 @@ type Docker struct { // Artifactory server configuration type Artifactory struct { Target string `yaml:",omitempty"` + Name string `yaml:",omitempty"` Username string `yaml:",omitempty"` // Capture all undefined fields and should be empty after loading diff --git a/docs/160-artifactory.md b/docs/160-artifactory.md index 5cbfa0eaa..9189b3157 100644 --- a/docs/160-artifactory.md +++ b/docs/160-artifactory.md @@ -17,7 +17,8 @@ upload target and a usernameto your `.goreleaser.yml` file: ```yaml artifactories: - - target: http://:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }} + - name: production + target: http://:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }} username: goreleaser ``` @@ -56,10 +57,15 @@ Support variables: Your configured username needs to be authenticated against your Artifactory. The password or API key will be stored in a environment variable. -If you have only one Artifactory configured, you will store the secret in `ARTIFACTORY_0_SECRET`. +The confgured name of your Artifactory instance will be used. +With this way we support auth for multiple instances. +This also means that the `name` per configured instance needs to be unique +per goreleaser configuration. -If you have multiple instances configured, you store the password for the second instance in `ARTIFACTORY_1_SECRET`, -for the third in `ARTIFACTORY_2_SECRET` and so on. +The name of the environment variable will be `ARTIFACTORY_NAME_SECRET`. +If your instance is named `production`, you need to store the secret in the +environment variable `ARTIFACTORY_PRODUCTION_SECRET`. +The name will be transformed to uppercase. ## Customization @@ -70,6 +76,8 @@ Of course, you can customize a lot of things: artifactories: # You can have multiple Artifactory instances. - + # Unique name of your artifactory instance. Used to identify the instance + name: production # URL of your Artifactory instance + path to deploy to target: http://artifacts.company.com:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }} # User that will be used for the deployment diff --git a/pipeline/artifactory/artifactory.go b/pipeline/artifactory/artifactory.go index 8aa0c42e1..b3b77eebb 100644 --- a/pipeline/artifactory/artifactory.go +++ b/pipeline/artifactory/artifactory.go @@ -58,26 +58,28 @@ func (Pipe) String() string { // // Docs: https://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API#ArtifactoryRESTAPI-Example-DeployinganArtifact func (Pipe) Run(ctx *context.Context) error { - instances := len(ctx.Config.Artifactories) - if instances == 0 { + if l := len(ctx.Config.Artifactories); l == 0 { return pipeline.Skip("artifactory section is not configured") } - // Check if for every instance we have a the target, - // the username and a secret (password or api key) - // If not, we can skip this pipeline - for i := 0; i < instances; i++ { - if ctx.Config.Artifactories[i].Target == "" { - return pipeline.Skip(fmt.Sprintf("artifactory section is not configured properly (missing target in artifactory %d)", i)) + // Check requirements for every instance we have configured. + // If not fulfilled, we can skip this pipeline + for _, instance := range ctx.Config.Artifactories { + if instance.Target == "" { + return pipeline.Skip("artifactory section is not configured properly (missing target)") } - if ctx.Config.Artifactories[i].Username == "" { - return pipeline.Skip(fmt.Sprintf("artifactory section is not configured properly (missing username in artifactory %d)", i)) + if instance.Username == "" { + return pipeline.Skip("artifactory section is not configured properly (missing username)") } - envName := fmt.Sprintf("ARTIFACTORY_%d_SECRET", i) + if instance.Name == "" { + return pipeline.Skip("artifactory section is not configured properly (missing name)") + } + + envName := fmt.Sprintf("ARTIFACTORY_%s_SECRET", strings.ToUpper(instance.Name)) if os.Getenv(envName) == "" { - return pipeline.Skip(fmt.Sprintf("missing secret for artifactory %d: %s", i, ctx.Config.Artifactories[i].Target)) + return pipeline.Skip(fmt.Sprintf("missing secret for artifactory instance %s", instance.Name)) } } @@ -137,16 +139,15 @@ func upload(ctx *context.Context, build config.Build, target buildtarget.Target) } // Loop over all configured Artifactory instances - instances := len(ctx.Config.Artifactories) - for i := 0; i < instances; i++ { - artifactory := ctx.Config.Artifactories[i] - secret := os.Getenv(fmt.Sprintf("ARTIFACTORY_%d_SECRET", i)) + for _, instance := range ctx.Config.Artifactories { + secret := os.Getenv(fmt.Sprintf("ARTIFACTORY_%s_SECRET", strings.ToUpper(instance.Name))) // Generate name of target - uploadTarget, err := buildTargetName(ctx, artifactory, target) + uploadTarget, err := buildTargetName(ctx, instance, target) if err != nil { - log.WithError(err).Error("Artifactory: Error while building the target name") - return errors.Wrap(err, "Artifactory: Error while building the target name") + msg := "artifactory: error while building the target name" + log.WithError(err).Error(msg) + return errors.Wrap(err, msg) } // The upload url to Artifactory needs the binary name @@ -163,19 +164,22 @@ func upload(ctx *context.Context, build config.Build, target buildtarget.Target) } defer file.Close() // nolint: errcheck - artifact, resp, err := uploadBinaryToArtifactory(ctx, uploadTarget, artifactory.Username, secret, file) + artifact, _, err := uploadBinaryToArtifactory(ctx, uploadTarget, instance.Username, secret, file) if err != nil { - var msg string - if resp != nil { - msg = fmt.Sprintf("Artifactory: Upload to target %s failed (HTTP Status: %s)", uploadTarget, resp.Status) - } else { - msg = fmt.Sprintf("Artifactory: Upload to target %s failed", uploadTarget) - } - log.WithError(err).Error(msg) + msg := "artifactory: upload failed" + log.WithError(err).WithFields(log.Fields{ + "instance": instance.Name, + "username": instance.Username, + }).Error(msg) return errors.Wrap(err, msg) } - log.WithField("uri", artifact.DownloadURI).WithField("target", target.PrettyString()).Info("uploaded successful") + log.WithFields(log.Fields{ + "instance": instance.Name, + "target": target.PrettyString(), + "username": instance.Username, + "uri": artifact.DownloadURI, + }).Info("uploaded successful") } return nil diff --git a/pipeline/artifactory/artifactory_test.go b/pipeline/artifactory/artifactory_test.go index ec2ed0f39..2cf2eeaa6 100644 --- a/pipeline/artifactory/artifactory_test.go +++ b/pipeline/artifactory/artifactory_test.go @@ -168,10 +168,10 @@ func TestRunPipe(t *testing.T) { }) // Set secrets for artifactory instances - os.Setenv("ARTIFACTORY_0_SECRET", "deployuser-secret") - defer os.Unsetenv("ARTIFACTORY_0_SECRET") - os.Setenv("ARTIFACTORY_1_SECRET", "productionuser-apikey") - defer os.Unsetenv("ARTIFACTORY_1_SECRET") + os.Setenv("ARTIFACTORY_PRODUCTION-US_SECRET", "deployuser-secret") + defer os.Unsetenv("ARTIFACTORY_PRODUCTION-US_SECRET") + os.Setenv("ARTIFACTORY_PRODUCTION-EU_SECRET", "productionuser-apikey") + defer os.Unsetenv("ARTIFACTORY_PRODUCTION-EU_SECRET") var ctx = &context.Context{ Version: "1.0.0", @@ -188,10 +188,12 @@ func TestRunPipe(t *testing.T) { }, Artifactories: []config.Artifactory{ { + Name: "production-us", Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL), Username: "deployuser", }, { + Name: "production-eu", Target: fmt.Sprintf("%s/production-repo-remote/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL), Username: "productionuser", }, @@ -237,8 +239,8 @@ func TestRunPipe_BadCredentials(t *testing.T) { }) // Set secrets for artifactory instances - os.Setenv("ARTIFACTORY_0_SECRET", "deployuser-secret") - defer os.Unsetenv("ARTIFACTORY_0_SECRET") + os.Setenv("ARTIFACTORY_PRODUCTION_SECRET", "deployuser-secret") + defer os.Unsetenv("ARTIFACTORY_PRODUCTION_SECRET") var ctx = &context.Context{ Version: "1.0.0", @@ -255,6 +257,7 @@ func TestRunPipe_BadCredentials(t *testing.T) { }, Artifactories: []config.Artifactory{ { + Name: "production", Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL), Username: "deployuser", }, @@ -271,8 +274,8 @@ func TestRunPipe_BadCredentials(t *testing.T) { func TestRunPipe_NoFile(t *testing.T) { // Set secrets for artifactory instances - os.Setenv("ARTIFACTORY_0_SECRET", "deployuser-secret") - defer os.Unsetenv("ARTIFACTORY_0_SECRET") + os.Setenv("ARTIFACTORY_PRODUCTION_SECRET", "deployuser-secret") + defer os.Unsetenv("ARTIFACTORY_PRODUCTION_SECRET") var ctx = &context.Context{ Version: "1.0.0", @@ -289,6 +292,7 @@ func TestRunPipe_NoFile(t *testing.T) { }, Artifactories: []config.Artifactory{ { + Name: "production", Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", Username: "deployuser", }, @@ -315,8 +319,8 @@ func TestRunPipe_UnparsableTarget(t *testing.T) { assert.NoError(t, err) // Set secrets for artifactory instances - os.Setenv("ARTIFACTORY_0_SECRET", "deployuser-secret") - defer os.Unsetenv("ARTIFACTORY_0_SECRET") + os.Setenv("ARTIFACTORY_PRODUCTION_SECRET", "deployuser-secret") + defer os.Unsetenv("ARTIFACTORY_PRODUCTION_SECRET") var ctx = &context.Context{ Version: "1.0.0", @@ -333,6 +337,7 @@ func TestRunPipe_UnparsableTarget(t *testing.T) { }, Artifactories: []config.Artifactory{ { + Name: "production", Target: "://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", Username: "deployuser", }, @@ -354,14 +359,10 @@ func TestRunPipe_DirUpload(t *testing.T) { assert.NoError(t, os.Mkdir(dist, 0755)) assert.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) var binPath = filepath.Join(dist, "mybin") - /* - d1 := []byte("hello\ngo\n") - err = ioutil.WriteFile(binPath, d1, 0666) - assert.NoError(t, err) - */ + // Set secrets for artifactory instances - os.Setenv("ARTIFACTORY_0_SECRET", "deployuser-secret") - defer os.Unsetenv("ARTIFACTORY_0_SECRET") + os.Setenv("ARTIFACTORY_PRODUCTION_SECRET", "deployuser-secret") + defer os.Unsetenv("ARTIFACTORY_PRODUCTION_SECRET") var ctx = &context.Context{ Version: "1.0.0", @@ -378,6 +379,7 @@ func TestRunPipe_DirUpload(t *testing.T) { }, Artifactories: []config.Artifactory{ { + Name: "production", Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", Username: "deployuser", }, @@ -404,6 +406,7 @@ func TestNoArtifactoriesWithoutTarget(t *testing.T) { assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{ Artifactories: []config.Artifactory{ { + Name: "production", Username: "deployuser", }, }, @@ -414,16 +417,29 @@ func TestNoArtifactoriesWithoutUsername(t *testing.T) { assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{ Artifactories: []config.Artifactory{ { + Name: "production", Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", }, }, })))) } +func TestNoArtifactoriesWithoutName(t *testing.T) { + assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{ + Artifactories: []config.Artifactory{ + { + Username: "deployuser", + Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", + }, + }, + })))) +} + func TestNoArtifactoriesWithoutSecret(t *testing.T) { assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{ Artifactories: []config.Artifactory{ { + Name: "production", Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", Username: "deployuser", },