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:
parent
4b98d14f70
commit
492a018b7f
@ -62,3 +62,4 @@ snapcraft:
|
||||
wrapped in your favorite CI.
|
||||
grade: stable
|
||||
confinement: classic
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
345
pipeline/artifactory/artifactory.go
Normal file
345
pipeline/artifactory/artifactory.go
Normal 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
|
||||
}
|
246
pipeline/artifactory/artifactory_test.go
Normal file
246
pipeline/artifactory/artifactory_test.go
Normal 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",
|
||||
},
|
||||
},
|
||||
}))))
|
||||
}
|
Loading…
Reference in New Issue
Block a user