You've already forked goreleaser
mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-09-16 09:26:52 +02:00
Merge pull request #429 from andygrunwald/artifactory-support
Artifactory: Added basic support to push binaries to an Artifactory store
This commit is contained in:
@@ -63,3 +63,4 @@ snapcraft:
|
||||
wrapped in your favorite CI.
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
@@ -195,6 +195,17 @@ type Docker struct {
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// Artifactory server configuration
|
||||
type Artifactory struct {
|
||||
Target string `yaml:",omitempty"`
|
||||
Name string `yaml:",omitempty"`
|
||||
Username string `yaml:",omitempty"`
|
||||
Mode string `yaml:",omitempty"`
|
||||
|
||||
// Capture all undefined fields and should be empty after loading
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
}
|
||||
|
||||
// Filters config
|
||||
type Filters struct {
|
||||
Exclude []string `yaml:",omitempty"`
|
||||
@@ -214,18 +225,19 @@ type Changelog struct {
|
||||
|
||||
// Project includes all project configuration
|
||||
type Project struct {
|
||||
ProjectName string `yaml:"project_name,omitempty"`
|
||||
Release Release `yaml:",omitempty"`
|
||||
Brew Homebrew `yaml:",omitempty"`
|
||||
Builds []Build `yaml:",omitempty"`
|
||||
Archive Archive `yaml:",omitempty"`
|
||||
FPM FPM `yaml:",omitempty"`
|
||||
Snapcraft Snapcraft `yaml:",omitempty"`
|
||||
Snapshot Snapshot `yaml:",omitempty"`
|
||||
Checksum Checksum `yaml:",omitempty"`
|
||||
Dockers []Docker `yaml:",omitempty"`
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
ProjectName string `yaml:"project_name,omitempty"`
|
||||
Release Release `yaml:",omitempty"`
|
||||
Brew Homebrew `yaml:",omitempty"`
|
||||
Builds []Build `yaml:",omitempty"`
|
||||
Archive Archive `yaml:",omitempty"`
|
||||
FPM FPM `yaml:",omitempty"`
|
||||
Snapcraft Snapcraft `yaml:",omitempty"`
|
||||
Snapshot Snapshot `yaml:",omitempty"`
|
||||
Checksum Checksum `yaml:",omitempty"`
|
||||
Dockers []Docker `yaml:",omitempty"`
|
||||
Artifactories []Artifactory `yaml:",omitempty"`
|
||||
Changelog Changelog `yaml:",omitempty"`
|
||||
Dist string `yaml:",omitempty"`
|
||||
|
||||
// this is a hack ¯\_(ツ)_/¯
|
||||
SingleBuild Build `yaml:"build,omitempty"`
|
||||
@@ -290,6 +302,9 @@ func checkOverflows(config Project) error {
|
||||
for i, docker := range config.Dockers {
|
||||
overflow.check(docker.XXX, fmt.Sprintf("docker[%d]", i))
|
||||
}
|
||||
for i, artifactory := range config.Artifactories {
|
||||
overflow.check(artifactory.XXX, fmt.Sprintf("artifactory[%d]", i))
|
||||
}
|
||||
overflow.check(config.Changelog.XXX, "changelog")
|
||||
overflow.check(config.Changelog.Filters.XXX, "changelog.filters")
|
||||
return overflow.err()
|
||||
|
@@ -59,7 +59,7 @@ func TestFileNotFound(t *testing.T) {
|
||||
|
||||
func TestInvalidFields(t *testing.T) {
|
||||
_, err := Load("testdata/invalid_config.yml")
|
||||
assert.EqualError(t, err, "unknown fields in the config file: invalid_root, archive.invalid_archive, archive.format_overrides[0].invalid_archive_fmtoverrides, brew.invalid_brew, brew.github.invalid_brew_github, builds[0].invalid_builds, builds[0].hooks.invalid_builds_hooks, builds[0].ignored_builds[0].invalid_builds_ignore, fpm.invalid_fpm, release.invalid_release, release.github.invalid_release_github, build.invalid_build, builds.hooks.invalid_build_hook, builds.ignored_builds[0].invalid_build_ignore, snapshot.invalid_snapshot, docker[0].invalid_docker, changelog.invalid_changelog, changelog.filters.invalid_filters")
|
||||
assert.EqualError(t, err, "unknown fields in the config file: invalid_root, archive.invalid_archive, archive.format_overrides[0].invalid_archive_fmtoverrides, brew.invalid_brew, brew.github.invalid_brew_github, builds[0].invalid_builds, builds[0].hooks.invalid_builds_hooks, builds[0].ignored_builds[0].invalid_builds_ignore, fpm.invalid_fpm, release.invalid_release, release.github.invalid_release_github, build.invalid_build, builds.hooks.invalid_build_hook, builds.ignored_builds[0].invalid_build_ignore, snapshot.invalid_snapshot, docker[0].invalid_docker, artifactory[0].invalid_artifactory, changelog.invalid_changelog, changelog.filters.invalid_filters")
|
||||
}
|
||||
|
||||
func TestInvalidYaml(t *testing.T) {
|
||||
|
2
config/testdata/invalid_config.yml
vendored
2
config/testdata/invalid_config.yml
vendored
@@ -29,6 +29,8 @@ snapshot:
|
||||
invalid_snapshot: 1
|
||||
dockers:
|
||||
- invalid_docker: 1
|
||||
artifactories:
|
||||
- invalid_artifactory: 1
|
||||
changelog:
|
||||
invalid_changelog: 1
|
||||
filters:
|
||||
|
94
docs/160-artifactory.md
Normal file
94
docs/160-artifactory.md
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Artifactory
|
||||
---
|
||||
|
||||
Since [v0.38.0](https://github.com/goreleaser/goreleaser/releases/tag/v0.38.0),
|
||||
GoReleaser supports building and pushing artifacts into Artifactory.
|
||||
|
||||
## How it works
|
||||
|
||||
You can declare multiple Artifactory instances.
|
||||
All binaries generated by your `builds` section will be pushed to
|
||||
each configured Artifactory.
|
||||
|
||||
If you have only one Artifactory instance,
|
||||
the configuration is as easy as adding the
|
||||
upload target and a usernameto your `.goreleaser.yml` file:
|
||||
|
||||
```yaml
|
||||
artifactories:
|
||||
- name: production
|
||||
target: http://<Your-Instance>:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/
|
||||
username: goreleaser
|
||||
```
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- A running Artifactory instances
|
||||
- A user + password / API key with grants to upload an artifact
|
||||
|
||||
### Target
|
||||
|
||||
The `target` is 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
|
||||
|
||||
```
|
||||
http://artifacts.company.com:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}{{ .Arm }}{{ end }}
|
||||
```
|
||||
|
||||
and will result in a final deployment like
|
||||
|
||||
```
|
||||
http://artifacts.company.com:8081/artifactory/example-repo-local/goreleaser/1.0.0/Darwin/x86_64/goreleaser
|
||||
```
|
||||
|
||||
Support variables:
|
||||
|
||||
- Version
|
||||
- Tag
|
||||
- ProjectName
|
||||
- Os
|
||||
- Arch
|
||||
- Arm
|
||||
|
||||
*Attention*: Variables _Os_, _Arch_ and _Arm_ are only supported in upload mode `binary`.
|
||||
|
||||
### Password / API Key
|
||||
|
||||
Your configured username needs to be authenticated against your Artifactory.
|
||||
|
||||
The password or API key will be stored in a environment variable.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Of course, you can customize a lot of things:
|
||||
|
||||
```yaml
|
||||
# .goreleaser.yml
|
||||
artifactories:
|
||||
# You can have multiple Artifactory instances.
|
||||
-
|
||||
# Unique name of your artifactory instance. Used to identify the instance
|
||||
name: production
|
||||
# Upload mode. Valid options are `binary` and `archive`.
|
||||
# If mode is `archive`, variables _Os_, _Arch_ and _Arm_ for target name are not supported.
|
||||
# In that case these variables are empty.
|
||||
# Default is `archive`.
|
||||
mode: archive
|
||||
# URL of your Artifactory instance + path to deploy to
|
||||
target: http://artifacts.company.com:8081/artifactory/example-repo-local/{{ .ProjectName }}/{{ .Version }}/
|
||||
# User that will be used for the deployment
|
||||
username: deployuser
|
||||
```
|
||||
|
||||
These settings should allow you to push your artifacts into multiple Artifactories.
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/pipeline"
|
||||
"github.com/goreleaser/goreleaser/pipeline/archive"
|
||||
"github.com/goreleaser/goreleaser/pipeline/artifactory"
|
||||
"github.com/goreleaser/goreleaser/pipeline/brew"
|
||||
"github.com/goreleaser/goreleaser/pipeline/build"
|
||||
"github.com/goreleaser/goreleaser/pipeline/changelog"
|
||||
@@ -50,6 +51,7 @@ var pipes = []pipeline.Piper{
|
||||
snapcraft.Pipe{}, // archive via snapcraft (snap)
|
||||
checksums.Pipe{}, // checksums of the files
|
||||
docker.Pipe{}, // create and push docker images
|
||||
artifactory.Pipe{}, // push to artifactory
|
||||
release.Pipe{}, // release to github
|
||||
brew.Pipe{}, // push to brew tap
|
||||
}
|
||||
|
430
pipeline/artifactory/artifactory.go
Normal file
430
pipeline/artifactory/artifactory.go
Normal file
@@ -0,0 +1,430 @@
|
||||
// Package artifactory provides a Pipe that push to artifactory
|
||||
package artifactory
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/internal/buildtarget"
|
||||
"github.com/goreleaser/goreleaser/pipeline"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// artifactoryResponse reflects the response after an upload request
|
||||
// to Artifactory.
|
||||
type artifactoryResponse struct {
|
||||
Repo string `json:"repo,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Created string `json:"created,omitempty"`
|
||||
CreatedBy string `json:"createdBy,omitempty"`
|
||||
DownloadURI string `json:"downloadUri,omitempty"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
Checksums artifactoryChecksums `json:"checksums,omitempty"`
|
||||
OriginalChecksums artifactoryChecksums `json:"originalChecksums,omitempty"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
}
|
||||
|
||||
// artifactoryChecksums reflects the checksums generated by
|
||||
// Artifactory
|
||||
type artifactoryChecksums struct {
|
||||
SHA1 string `json:"sha1,omitempty"`
|
||||
MD5 string `json:"md5,omitempty"`
|
||||
SHA256 string `json:"sha256,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
modeBinary = "binary"
|
||||
modeArchive = "archive"
|
||||
)
|
||||
|
||||
// Pipe for Artifactory
|
||||
type Pipe struct{}
|
||||
|
||||
// String returns the description of the pipe
|
||||
func (Pipe) String() string {
|
||||
return "releasing to Artifactory"
|
||||
}
|
||||
|
||||
// Default sets the pipe defaults
|
||||
func (Pipe) Default(ctx *context.Context) error {
|
||||
if len(ctx.Config.Artifactories) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if a mode was set
|
||||
for i := range ctx.Config.Artifactories {
|
||||
if ctx.Config.Artifactories[i].Mode == "" {
|
||||
ctx.Config.Artifactories[i].Mode = "archive"
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run the pipe
|
||||
//
|
||||
// Docs: https://www.jfrog.com/confluence/display/RTF/Artifactory+REST+API#ArtifactoryRESTAPI-Example-DeployinganArtifact
|
||||
func (Pipe) Run(ctx *context.Context) error {
|
||||
if len(ctx.Config.Artifactories) == 0 {
|
||||
return pipeline.Skip("artifactory section is not configured")
|
||||
}
|
||||
|
||||
// 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 instance.Username == "" {
|
||||
return pipeline.Skip("artifactory section is not configured properly (missing username)")
|
||||
}
|
||||
|
||||
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 _, ok := ctx.Env[envName]; !ok {
|
||||
return pipeline.Skip(fmt.Sprintf("missing secret for artifactory instance %s", instance.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return doRun(ctx)
|
||||
}
|
||||
|
||||
func doRun(ctx *context.Context) error {
|
||||
if !ctx.Publish {
|
||||
return pipeline.Skip("--skip-publish is set")
|
||||
}
|
||||
|
||||
// Handle every configured artifactory instance
|
||||
for _, instance := range ctx.Config.Artifactories {
|
||||
// We support two different modes
|
||||
// - "archive": Upload all artifacts
|
||||
// - "binary": Upload only the raw binaries
|
||||
var err error
|
||||
switch v := strings.ToLower(instance.Mode); v {
|
||||
case modeArchive:
|
||||
err = runPipeForModeArchive(ctx, instance)
|
||||
|
||||
case modeBinary:
|
||||
// Loop over all builds, because we want to publish every build to Artifactory
|
||||
for _, build := range ctx.Config.Builds {
|
||||
if err = runPipeForModeBinary(ctx, instance, build); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("artifactory: mode \"%s\" not supported", v)
|
||||
log.WithFields(log.Fields{
|
||||
"instance": instance.Name,
|
||||
"mode": v,
|
||||
}).Error(err.Error())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runPipeForModeArchive uploads all ctx.Artifacts to instance
|
||||
func runPipeForModeArchive(ctx *context.Context, instance config.Artifactory) error {
|
||||
sem := make(chan bool, ctx.Parallelism)
|
||||
var g errgroup.Group
|
||||
|
||||
// Get all artifacts and upload them
|
||||
for _, artifact := range ctx.Artifacts {
|
||||
sem <- true
|
||||
artifact := artifact
|
||||
g.Go(func() error {
|
||||
defer func() {
|
||||
<-sem
|
||||
}()
|
||||
|
||||
return uploadArchive(ctx, instance, artifact)
|
||||
})
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// uploadArchive will upload artifact in mode archive
|
||||
func uploadArchive(ctx *context.Context, instance config.Artifactory, artifact string) error {
|
||||
var path = filepath.Join(ctx.Config.Dist, artifact)
|
||||
return uploadAssetAndLog(ctx, instance, path, nil)
|
||||
}
|
||||
|
||||
// uploadBinary will upload the current build and the current target in mode binary
|
||||
func uploadBinary(ctx *context.Context, instance config.Artifactory, build config.Build, target buildtarget.Target) error {
|
||||
binary, err := getBinaryForUploadPerBuild(ctx, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return uploadAssetAndLog(ctx, instance, binary.Path, &target)
|
||||
}
|
||||
|
||||
// uploadAssetAndLog uploads file to target and logs all actions
|
||||
func uploadAssetAndLog(ctx *context.Context, instance config.Artifactory, path string, target *buildtarget.Target) error {
|
||||
envName := fmt.Sprintf("ARTIFACTORY_%s_SECRET", strings.ToUpper(instance.Name))
|
||||
secret := ctx.Env[envName]
|
||||
|
||||
// Generate the target url
|
||||
targetURL, err := resolveTargetTemplate(ctx, instance, target)
|
||||
if err != nil {
|
||||
msg := "artifactory: error while building the target url"
|
||||
log.WithField("instance", instance.Name).WithError(err).Error(msg)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
// Handle the artifact
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close() // nolint: errcheck
|
||||
_, name := filepath.Split(path)
|
||||
|
||||
// The target url needs to contain the artifact name
|
||||
if !strings.HasSuffix(targetURL, "/") {
|
||||
targetURL += "/"
|
||||
}
|
||||
targetURL += name
|
||||
|
||||
artifact, _, err := uploadAssetToArtifactory(ctx, targetURL, instance.Username, secret, file)
|
||||
if err != nil {
|
||||
msg := "artifactory: upload failed"
|
||||
log.WithError(err).WithFields(log.Fields{
|
||||
"instance": instance.Name,
|
||||
"username": instance.Username,
|
||||
}).Error(msg)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"instance": instance.Name,
|
||||
"mode": instance.Mode,
|
||||
"uri": artifact.DownloadURI,
|
||||
}).Info("uploaded successful")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runPipeForModeBinary uploads all configured builds to instance
|
||||
func runPipeForModeBinary(ctx *context.Context, instance config.Artifactory, build config.Build) error {
|
||||
sem := make(chan bool, ctx.Parallelism)
|
||||
var g errgroup.Group
|
||||
|
||||
// Lets generate the build matrix, because we want
|
||||
// to publish every target to Artifactory
|
||||
for _, target := range buildtarget.All(build) {
|
||||
sem <- true
|
||||
target := target
|
||||
build := build
|
||||
g.Go(func() error {
|
||||
defer func() {
|
||||
<-sem
|
||||
}()
|
||||
|
||||
return uploadBinary(ctx, instance, build, target)
|
||||
})
|
||||
}
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
// getBinaryForUploadPerBuild determines the correct binary for the upload
|
||||
func getBinaryForUploadPerBuild(ctx *context.Context, target buildtarget.Target) (*context.Binary, error) {
|
||||
var group = ctx.Binaries[target.String()]
|
||||
if group == nil {
|
||||
return nil, fmt.Errorf("binary for build target %s not found", target.String())
|
||||
}
|
||||
|
||||
var binary context.Binary
|
||||
for _, binaries := range group {
|
||||
for _, b := range binaries {
|
||||
binary = b
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return &binary, nil
|
||||
}
|
||||
|
||||
// targetData is used as a template struct for
|
||||
// Artifactory.Target
|
||||
type targetData struct {
|
||||
Version string
|
||||
Tag string
|
||||
ProjectName 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
|
||||
func resolveTargetTemplate(ctx *context.Context, artifactory config.Artifactory, target *buildtarget.Target) (string, error) {
|
||||
data := targetData{
|
||||
Version: ctx.Version,
|
||||
Tag: ctx.Git.CurrentTag,
|
||||
ProjectName: ctx.Config.ProjectName,
|
||||
}
|
||||
|
||||
// Only supported in mode binary
|
||||
if target != nil {
|
||||
data.Os = replace(ctx.Config.Archive.Replacements, target.OS)
|
||||
data.Arch = replace(ctx.Config.Archive.Replacements, target.Arch)
|
||||
data.Arm = replace(ctx.Config.Archive.Replacements, target.Arm)
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
t, err := template.New(ctx.Config.ProjectName).Parse(artifactory.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
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// uploadAssetToArtifactory uploads the asset file to target
|
||||
func uploadAssetToArtifactory(ctx *context.Context, target, username, secret string, file *os.File) (*artifactoryResponse, *http.Response, error) {
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return nil, nil, errors.New("the asset to upload can't be a directory")
|
||||
}
|
||||
|
||||
req, err := newUploadRequest(target, username, secret, file, stat.Size())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
asset := new(artifactoryResponse)
|
||||
resp, err := executeHTTPRequest(ctx, req, asset)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return asset, resp, nil
|
||||
}
|
||||
|
||||
// newUploadRequest creates a new http.Request for uploading
|
||||
func newUploadRequest(target, username, secret string, reader io.Reader, size int64) (*http.Request, error) {
|
||||
u, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", u.String(), reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.ContentLength = size
|
||||
req.SetBasicAuth(username, secret)
|
||||
|
||||
return req, err
|
||||
}
|
||||
|
||||
// executeHTTPRequest processes the http call with respect of context ctx
|
||||
func executeHTTPRequest(ctx *context.Context, req *http.Request, v interface{}) (*http.Response, error) {
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
// If we got an error, and the context has been canceled,
|
||||
// the context's error is probably more useful.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
// even though there was an error, we still return the response
|
||||
// in case the caller wants to inspect it further
|
||||
return resp, err
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(v)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// An ErrorResponse reports one or more errors caused by an API request.
|
||||
type errorResponse struct {
|
||||
Response *http.Response // HTTP response that caused this error
|
||||
Errors []Error `json:"errors"` // more detail on individual errors
|
||||
}
|
||||
|
||||
func (r *errorResponse) Error() string {
|
||||
return fmt.Sprintf("%v %v: %d %+v",
|
||||
r.Response.Request.Method, r.Response.Request.URL,
|
||||
r.Response.StatusCode, r.Errors)
|
||||
}
|
||||
|
||||
// An Error reports more details on an individual error in an ErrorResponse.
|
||||
type Error struct {
|
||||
Status int `json:"status"` // Error code
|
||||
Message string `json:"message"` // Message describing the error.
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%v (%v)", e.Message, e.Status)
|
||||
}
|
||||
|
||||
// checkResponse checks the API response for errors, and returns them if
|
||||
// present. A response is considered an error if it has a status code outside
|
||||
// the 200 range.
|
||||
// API error responses are expected to have either no response
|
||||
// body, or a JSON response body that maps to ErrorResponse. Any other
|
||||
// response body will be silently ignored.
|
||||
func checkResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
errorResponse := &errorResponse{Response: r}
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err == nil && data != nil {
|
||||
err := json.Unmarshal(data, errorResponse)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return errorResponse
|
||||
}
|
835
pipeline/artifactory/artifactory_test.go
Normal file
835
pipeline/artifactory/artifactory_test.go
Normal file
@@ -0,0 +1,835 @@
|
||||
package artifactory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/goreleaser/goreleaser/config"
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/pipeline"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
// mux is the HTTP request multiplexer used with the test server.
|
||||
mux *http.ServeMux
|
||||
|
||||
// server is a test HTTP server used to provide mock API responses.
|
||||
server *httptest.Server
|
||||
)
|
||||
|
||||
func setup() {
|
||||
// test server
|
||||
mux = http.NewServeMux()
|
||||
server = httptest.NewServer(mux)
|
||||
}
|
||||
|
||||
// teardown closes the test HTTP server.
|
||||
func teardown() {
|
||||
server.Close()
|
||||
}
|
||||
|
||||
func testMethod(t *testing.T, r *http.Request, want string) {
|
||||
if got := r.Method; got != want {
|
||||
t.Errorf("Request method: %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func testHeader(t *testing.T, r *http.Request, header string, want string) {
|
||||
if got := r.Header.Get(header); got != want {
|
||||
t.Errorf("Header.Get(%q) returned %q, want %q", header, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunPipe_ModeBinary(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 artifactories
|
||||
mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `{
|
||||
"repo" : "example-repo-local",
|
||||
"path" : "/mybin/darwin/amd64/mybin",
|
||||
"created" : "2017-12-02T19:30:45.436Z",
|
||||
"createdBy" : "deployuser",
|
||||
"downloadUri" : "http://127.0.0.1:56563/example-repo-local/mybin/darwin/amd64/mybin",
|
||||
"mimeType" : "application/octet-stream",
|
||||
"size" : "9",
|
||||
"checksums" : {
|
||||
"sha1" : "65d01857a69f14ade727fe1ceee0f52a264b6e57",
|
||||
"md5" : "a55e303e7327dc871a8e2a84f30b9983",
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"originalChecksums" : {
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"uri" : "http://127.0.0.1:56563/example-repo-local/mybin/darwin/amd64/mybin"
|
||||
}`)
|
||||
})
|
||||
mux.HandleFunc("/example-repo-local/mybin/linux/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `{
|
||||
"repo" : "example-repo-local",
|
||||
"path" : "mybin/linux/amd64/mybin",
|
||||
"created" : "2017-12-02T19:30:46.436Z",
|
||||
"createdBy" : "deployuser",
|
||||
"downloadUri" : "http://127.0.0.1:56563/example-repo-local/mybin/linux/amd64/mybin",
|
||||
"mimeType" : "application/octet-stream",
|
||||
"size" : "9",
|
||||
"checksums" : {
|
||||
"sha1" : "65d01857a69f14ade727fe1ceee0f52a264b6e57",
|
||||
"md5" : "a55e303e7327dc871a8e2a84f30b9983",
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"originalChecksums" : {
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"uri" : "http://127.0.0.1:56563/example-repo-local/mybin/linux/amd64/mybin"
|
||||
}`)
|
||||
})
|
||||
mux.HandleFunc("/production-repo-remote/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "productionuser" with secret "productionuser-apikey"
|
||||
testHeader(t, r, "Authorization", "Basic cHJvZHVjdGlvbnVzZXI6cHJvZHVjdGlvbnVzZXItYXBpa2V5")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `{
|
||||
"repo" : "production-repo-remote",
|
||||
"path" : "mybin/darwin/amd64/mybin",
|
||||
"created" : "2017-12-02T19:30:46.436Z",
|
||||
"createdBy" : "productionuser",
|
||||
"downloadUri" : "http://127.0.0.1:56563/production-repo-remote/mybin/darwin/amd64/mybin",
|
||||
"mimeType" : "application/octet-stream",
|
||||
"size" : "9",
|
||||
"checksums" : {
|
||||
"sha1" : "65d01857a69f14ade727fe1ceee0f52a264b6e57",
|
||||
"md5" : "a55e303e7327dc871a8e2a84f30b9983",
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"originalChecksums" : {
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"uri" : "http://127.0.0.1:56563/production-repo-remote/mybin/darwin/amd64/mybin"
|
||||
}`)
|
||||
})
|
||||
mux.HandleFunc("/production-repo-remote/mybin/linux/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "productionuser" with secret "productionuser-apikey"
|
||||
testHeader(t, r, "Authorization", "Basic cHJvZHVjdGlvbnVzZXI6cHJvZHVjdGlvbnVzZXItYXBpa2V5")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `{
|
||||
"repo" : "production-repo-remote",
|
||||
"path" : "mybin/linux/amd64/mybin",
|
||||
"created" : "2017-12-02T19:30:46.436Z",
|
||||
"createdBy" : "productionuser",
|
||||
"downloadUri" : "http://127.0.0.1:56563/production-repo-remote/mybin/linux/amd64/mybin",
|
||||
"mimeType" : "application/octet-stream",
|
||||
"size" : "9",
|
||||
"checksums" : {
|
||||
"sha1" : "65d01857a69f14ade727fe1ceee0f52a264b6e57",
|
||||
"md5" : "a55e303e7327dc871a8e2a84f30b9983",
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"originalChecksums" : {
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"uri" : "http://127.0.0.1:56563/production-repo-remote/mybin/linux/amd64/mybin"
|
||||
}`)
|
||||
})
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION-US_SECRET": "deployuser-secret",
|
||||
"ARTIFACTORY_PRODUCTION-EU_SECRET": "productionuser-apikey",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"linux", "darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production-us",
|
||||
Mode: "binary",
|
||||
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
|
||||
Username: "deployuser",
|
||||
},
|
||||
{
|
||||
Name: "production-eu",
|
||||
Mode: "binary",
|
||||
Target: fmt.Sprintf("%s/production-repo-remote/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
|
||||
Username: "productionuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, plat := range []string{"linuxamd64", "linux386", "darwinamd64"} {
|
||||
ctx.AddBinary(plat, "mybin", "mybin", binPath)
|
||||
}
|
||||
|
||||
assert.NoError(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_ModeArchive(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.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "goreleaser",
|
||||
Dist: folder,
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "archive",
|
||||
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Version }}/", server.URL),
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx.AddArtifact(tarfile.Name())
|
||||
ctx.AddArtifact(debfile.Name())
|
||||
|
||||
// Dummy artifactories
|
||||
mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.tar.gz", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `{
|
||||
"repo" : "example-repo-local",
|
||||
"path" : "/goreleaser/bin.tar.gz",
|
||||
"created" : "2017-12-02T19:30:45.436Z",
|
||||
"createdBy" : "deployuser",
|
||||
"downloadUri" : "http://127.0.0.1:56563/example-repo-local/goreleaser/bin.tar.gz",
|
||||
"mimeType" : "application/octet-stream",
|
||||
"size" : "9",
|
||||
"checksums" : {
|
||||
"sha1" : "65d01857a69f14ade727fe1ceee0f52a264b6e57",
|
||||
"md5" : "a55e303e7327dc871a8e2a84f30b9983",
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"originalChecksums" : {
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"uri" : "http://127.0.0.1:56563/example-repo-local/goreleaser/bin.tar.gz"
|
||||
}`)
|
||||
})
|
||||
mux.HandleFunc("/example-repo-local/goreleaser/1.0.0/bin.deb", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `{
|
||||
"repo" : "example-repo-local",
|
||||
"path" : "goreleaser/bin.deb",
|
||||
"created" : "2017-12-02T19:30:46.436Z",
|
||||
"createdBy" : "deployuser",
|
||||
"downloadUri" : "http://127.0.0.1:56563/example-repo-local/goreleaser/bin.deb",
|
||||
"mimeType" : "application/octet-stream",
|
||||
"size" : "9",
|
||||
"checksums" : {
|
||||
"sha1" : "65d01857a69f14ade727fe1ceee0f52a264b6e57",
|
||||
"md5" : "a55e303e7327dc871a8e2a84f30b9983",
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"originalChecksums" : {
|
||||
"sha256" : "ead9b172aec5c24ca6c12e85a1e6fc48dd341d8fac38c5ba00a78881eabccf0e"
|
||||
},
|
||||
"uri" : "http://127.0.0.1:56563/example-repo-local/goreleaser/bin.deb"
|
||||
}`)
|
||||
})
|
||||
|
||||
assert.NoError(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_TargetTemplateError(t *testing.T) {
|
||||
folder, err := ioutil.TempDir("", "archivetest")
|
||||
assert.NoError(t, err)
|
||||
var dist = filepath.Join(folder, "dist")
|
||||
var binPath = filepath.Join(dist, "mybin", "mybin")
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
// This template is not correct and should fail
|
||||
Target: "http://storage.company.com/example-repo-local/{{ .ProjectName /{{ .Version }}/",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx.AddBinary("darwinamd64", "mybin", "mybin", binPath)
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_BadCredentials(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 artifactories
|
||||
mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprint(w, `{
|
||||
"errors" : [ {
|
||||
"status" : 401,
|
||||
"message" : "Bad credentials"
|
||||
} ]
|
||||
}`)
|
||||
})
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, plat := range []string{"darwinamd64"} {
|
||||
ctx.AddBinary(plat, "mybin", "mybin", binPath)
|
||||
}
|
||||
|
||||
err = Pipe{}.Run(ctx)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, len(err.Error()) > 0)
|
||||
}
|
||||
|
||||
func TestRunPipe_UnparsableErrorResponse(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 artifactories
|
||||
mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprint(w, `...{
|
||||
"errors" : [ {
|
||||
...
|
||||
} ]
|
||||
}`)
|
||||
})
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, plat := range []string{"darwinamd64"} {
|
||||
ctx.AddBinary(plat, "mybin", "mybin", binPath)
|
||||
}
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_UnparsableResponse(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 artifactory with invalid JSON
|
||||
mux.HandleFunc("/example-repo-local/mybin/darwin/amd64/mybin", func(w http.ResponseWriter, r *http.Request) {
|
||||
testMethod(t, r, "PUT")
|
||||
testHeader(t, r, "Content-Length", "9")
|
||||
// Basic auth of user "deployuser" with secret "deployuser-secret"
|
||||
testHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==")
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
fmt.Fprint(w, `invalid-json{
|
||||
"repo" : "example-repo-local",
|
||||
...
|
||||
}`)
|
||||
})
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx.AddBinary("darwinamd64", "mybin", "mybin", binPath)
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_WithoutBinaryTarget(t *testing.T) {
|
||||
folder, err := ioutil.TempDir("", "archivetest")
|
||||
assert.NoError(t, err)
|
||||
var dist = filepath.Join(folder, "dist")
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_NoFile(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: "archivetest/dist",
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx.AddBinary("darwinamd64", "mybin", "mybin", "archivetest/dist/mybin/mybin")
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_UnparsableTarget(t *testing.T) {
|
||||
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)
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: "://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, plat := range []string{"darwinamd64"} {
|
||||
ctx.AddBinary(plat, "mybin", "mybin", binPath)
|
||||
}
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestRunPipe_SkipWhenPublishFalse(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Publish: false,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, pipeline.IsSkip(Pipe{}.Run(ctx)))
|
||||
}
|
||||
|
||||
func TestRunPipe_DirUpload(t *testing.T) {
|
||||
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")
|
||||
|
||||
var ctx = &context.Context{
|
||||
Version: "1.0.0",
|
||||
Publish: true,
|
||||
Parallelism: 4,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
ProjectName: "mybin",
|
||||
Dist: dist,
|
||||
Builds: []config.Build{
|
||||
{
|
||||
Env: []string{"CGO_ENABLED=0"},
|
||||
Goos: []string{"darwin"},
|
||||
Goarch: []string{"amd64"},
|
||||
},
|
||||
},
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "binary",
|
||||
Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, plat := range []string{"darwinamd64"} {
|
||||
ctx.AddBinary(plat, "mybin", "mybin", binPath)
|
||||
}
|
||||
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestDescription(t *testing.T) {
|
||||
assert.NotEmpty(t, Pipe{}.String())
|
||||
}
|
||||
|
||||
func TestNoArtifactories(t *testing.T) {
|
||||
assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{}))))
|
||||
}
|
||||
|
||||
func TestArtifactoriesWithoutTarget(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, pipeline.IsSkip(Pipe{}.Run(ctx)))
|
||||
}
|
||||
|
||||
func TestArtifactoriesWithoutUsername(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.True(t, pipeline.IsSkip(Pipe{}.Run(ctx)))
|
||||
}
|
||||
|
||||
func TestArtifactoriesWithoutName(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 TestArtifactoriesWithoutSecret(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",
|
||||
},
|
||||
},
|
||||
}))))
|
||||
}
|
||||
|
||||
func TestArtifactoriesWithInvalidMode(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Publish: true,
|
||||
Env: map[string]string{
|
||||
"ARTIFACTORY_PRODUCTION_SECRET": "deployuser-secret",
|
||||
},
|
||||
Config: config.Project{
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Name: "production",
|
||||
Mode: "does-not-exists",
|
||||
Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
|
||||
Username: "deployuser",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Error(t, Pipe{}.Run(ctx))
|
||||
}
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Config: 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, Pipe{}.Default(ctx))
|
||||
assert.Len(t, ctx.Config.Artifactories, 1)
|
||||
var artifactory = ctx.Config.Artifactories[0]
|
||||
assert.Equal(t, "archive", artifactory.Mode)
|
||||
}
|
||||
|
||||
func TestDefaultNoArtifactories(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Config: config.Project{
|
||||
Artifactories: []config.Artifactory{},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, Pipe{}.Default(ctx))
|
||||
assert.Empty(t, ctx.Config.Artifactories)
|
||||
}
|
||||
|
||||
func TestDefaultSet(t *testing.T) {
|
||||
var ctx = &context.Context{
|
||||
Config: config.Project{
|
||||
Artifactories: []config.Artifactory{
|
||||
{
|
||||
Mode: "custom",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.NoError(t, Pipe{}.Default(ctx))
|
||||
assert.Len(t, ctx.Config.Artifactories, 1)
|
||||
var artifactory = ctx.Config.Artifactories[0]
|
||||
assert.Equal(t, "custom", artifactory.Mode)
|
||||
}
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/goreleaser/goreleaser/context"
|
||||
"github.com/goreleaser/goreleaser/pipeline"
|
||||
"github.com/goreleaser/goreleaser/pipeline/archive"
|
||||
"github.com/goreleaser/goreleaser/pipeline/artifactory"
|
||||
"github.com/goreleaser/goreleaser/pipeline/brew"
|
||||
"github.com/goreleaser/goreleaser/pipeline/build"
|
||||
"github.com/goreleaser/goreleaser/pipeline/checksums"
|
||||
@@ -31,6 +32,7 @@ var defaulters = []pipeline.Defaulter{
|
||||
fpm.Pipe{},
|
||||
checksums.Pipe{},
|
||||
docker.Pipe{},
|
||||
artifactory.Pipe{},
|
||||
brew.Pipe{},
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user