1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2024-12-31 01:53:50 +02:00

feat: Added basic support to push binaries into Artifactory

Artifactory is an universal Artifact Repository Manager by
JFrog. See https://www.jfrog.com/artifactory/

It is available in an OSS and Enterprise version.
Many companies using this internally to store, manage and
distribute binaries within their internal infrastructure.

It adds basic support to push all generated binaries into an
Artifactory. Basic means only the built artifacts. Without
checksums or archives.
As an authentication only Basic auth is supported by this Pipe.

See #344
This commit is contained in:
Andy Grunwald 2017-12-02 19:41:05 +01:00
parent 4b98d14f70
commit 492a018b7f
7 changed files with 635 additions and 26 deletions

View File

@ -62,3 +62,4 @@ snapcraft:
wrapped in your favorite CI.
grade: stable
confinement: classic

View File

@ -194,6 +194,15 @@ type Docker struct {
XXX map[string]interface{} `yaml:",inline"`
}
// Artifactory server configuration
type Artifactory struct {
Target string `yaml:",omitempty"`
Username 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"`
@ -213,18 +222,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"`
@ -289,6 +299,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()

View File

@ -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) {

View File

@ -29,6 +29,8 @@ snapshot:
invalid_snapshot: 1
dockers:
- invalid_docker: 1
artifactories:
- invalid_artifactory: 1
changelog:
invalid_changelog: 1
filters:

View File

@ -11,6 +11,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"
@ -27,19 +28,20 @@ import (
)
var pipes = []pipeline.Pipe{
defaults.Pipe{}, // load default configs
git.Pipe{}, // get and validate git repo state
changelog.Pipe{}, // builds the release changelog
env.Pipe{}, // load and validate environment variables
cleandist.Pipe{}, // ensure ./dist is clean
build.Pipe{}, // build
archive.Pipe{}, // archive (tar.gz, zip, etc)
fpm.Pipe{}, // archive via fpm (deb, rpm, etc)
snapcraft.Pipe{}, // archive via snapcraft (snap)
checksums.Pipe{}, // checksums of the files
docker.Pipe{}, // create and push docker images
release.Pipe{}, // release to github
brew.Pipe{}, // push to brew tap
defaults.Pipe{}, // load default configs
git.Pipe{}, // get and validate git repo state
changelog.Pipe{}, // builds the release changelog
env.Pipe{}, // load and validate environment variables
cleandist.Pipe{}, // ensure ./dist is clean
build.Pipe{}, // build
archive.Pipe{}, // archive (tar.gz, zip, etc)
fpm.Pipe{}, // archive via fpm (deb, rpm, etc)
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
}
// Flags interface represents an extractor of cli flags

View File

@ -0,0 +1,345 @@
// Package artifactory provides a Pipe that push to artifactory
package artifactory
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
"github.com/goreleaser/goreleaser/internal/buildtarget"
"github.com/goreleaser/goreleaser/pipeline"
"golang.org/x/sync/errgroup"
"github.com/apex/log"
)
// 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"`
}
// Pipe for Artifactory
type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Releasing to Artifactory"
}
// Run the pipe
//
// 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 {
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))
}
if ctx.Config.Artifactories[i].Username == "" {
return pipeline.Skip(fmt.Sprintf("artifactory section is not configured properly (missing username in artifactory %d)", i))
}
envName := fmt.Sprintf("ARTIFACTORY_%d_SECRET", i)
if os.Getenv(envName) == "" {
return pipeline.Skip(fmt.Sprintf("missing secret for artifactory %d: %s", i, ctx.Config.Artifactories[i].Target))
}
}
return doRun(ctx)
}
func doRun(ctx *context.Context) error {
if !ctx.Publish {
return pipeline.Skip("--skip-publish is set")
}
// Loop over all builds, because we want to publish
// every build to Artifactory
for _, build := range ctx.Config.Builds {
if err := runPipeOnBuild(ctx, build); err != nil {
return err
}
}
return nil
}
// runPipeOnBuild runs the pipe for every configured build
func runPipeOnBuild(ctx *context.Context, 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 doBuild(ctx, build, target)
})
}
if err := g.Wait(); err != nil {
return err
}
return nil
}
// doBuild runs the pipe action of the current build and the current target
// This is where the real action take place
func doBuild(ctx *context.Context, build config.Build, target buildtarget.Target) error {
binary, err := getBinaryForUploadPerBuild(ctx, target)
if err != nil {
return err
}
// 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))
// Generate name of target
uploadTarget, err := buildTargetName(ctx, artifactory, target)
if err != nil {
// We log the error, but continue the process
// The next target name could be generated successfully
log.WithError(err).Error("Artifactory: Error while building the target name")
continue
}
// The upload url to Artifactory needs the binary name
// Here we add the binary to the target url
if !strings.HasPrefix(uploadTarget, "/") {
uploadTarget += "/"
}
uploadTarget += binary.Name
// Upload the binary to Artifactory
file, err := os.Open(binary.Path)
if err != nil {
return err
}
defer func() { _ = file.Close() }()
artifact, resp, err := uploadBinaryToArtifactory(ctx, uploadTarget, artifactory.Username, secret, file)
if err != nil {
log.WithError(err).Errorf("Artifactory: Upload to target %s failed (HTTP Status: %s)", uploadTarget, resp.Status)
continue
}
log.WithField("uri", artifact.DownloadURI).WithField("target", target.PrettyString()).Info("uploaded successful")
}
return nil
}
// 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 {
Os string
Arch string
Arm string
Version string
Tag string
ProjectName string
}
// buildTargetName returns the name resolved target name with replaced variables
// Those variables can be replaced by the given context, goos, goarch, goarm and more
func buildTargetName(ctx *context.Context, artifactory config.Artifactory, target buildtarget.Target) (string, error) {
data := targetData{
Os: replace(ctx.Config.Archive.Replacements, target.OS),
Arch: replace(ctx.Config.Archive.Replacements, target.Arch),
Arm: replace(ctx.Config.Archive.Replacements, target.Arm),
Version: ctx.Version,
Tag: ctx.Git.CurrentTag,
ProjectName: ctx.Config.ProjectName,
}
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
}
// uploadBinaryToArtifactory uploads the binary file to target
func uploadBinaryToArtifactory(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()
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 {
json.Unmarshal(data, errorResponse)
}
return errorResponse
}

View File

@ -0,0 +1,246 @@
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(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"
}`)
})
// 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")
var ctx = &context.Context{
Version: "1.0.0",
Publish: true,
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{
{
Target: fmt.Sprintf("%s/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}", server.URL),
Username: "deployuser",
},
{
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)
}
ctx.Parallelism = 4
assert.NoError(t, Pipe{}.Run(ctx))
}
func TestDescription(t *testing.T) {
assert.NotEmpty(t, Pipe{}.Description())
}
func TestNoArtifactories(t *testing.T) {
assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{}))))
}
func TestNoArtifactoriesWithoutTarget(t *testing.T) {
assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{
Artifactories: []config.Artifactory{
{
Username: "deployuser",
},
},
}))))
}
func TestNoArtifactoriesWithoutUsername(t *testing.T) {
assert.True(t, pipeline.IsSkip(Pipe{}.Run(context.New(config.Project{
Artifactories: []config.Artifactory{
{
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{
{
Target: "http://artifacts.company.com/example-repo-local/{{ .ProjectName }}/{{ .Os }}/{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}",
Username: "deployuser",
},
},
}))))
}