1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-16 03:52:12 +02:00

Merge branch 'master' into organizing

This commit is contained in:
Carlos Alexandro Becker 2018-09-13 21:38:09 -03:00
commit ac3dabbe4c
No known key found for this signature in database
GPG Key ID: E61E2F7DC14AB940
5 changed files with 292 additions and 66 deletions

View File

@ -3,12 +3,14 @@ package http
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"html/template"
"io"
h "net/http"
"net/url"
"os"
"runtime"
"strings"
"github.com/apex/log"
@ -97,6 +99,10 @@ func CheckConfig(ctx *context.Context, put *config.Put, kind string) error {
return misconfigured(kind, put, fmt.Sprintf("missing %s environment variable", envName))
}
if put.TrustedCerts != "" && !x509.NewCertPool().AppendCertsFromPEM([]byte(put.TrustedCerts)) {
return misconfigured(kind, put, "no certificate could be added from the specified trusted_certificates configuration")
}
return nil
}
@ -143,7 +149,7 @@ func Upload(ctx *context.Context, puts []config.Put, kind string, check Response
}).Error(err.Error())
return err
}
if err := uploadWithFilter(ctx, put, artifact.Or(filters...), kind, check); err != nil {
if err := uploadWithFilter(ctx, &put, artifact.Or(filters...), kind, check); err != nil {
return err
}
}
@ -151,7 +157,7 @@ func Upload(ctx *context.Context, puts []config.Put, kind string, check Response
return nil
}
func uploadWithFilter(ctx *context.Context, put config.Put, filter artifact.Filter, kind string, check ResponseChecker) error {
func uploadWithFilter(ctx *context.Context, put *config.Put, filter artifact.Filter, kind string, check ResponseChecker) error {
var g = semerrgroup.New(ctx.Parallelism)
for _, artifact := range ctx.Artifacts.Filter(filter).List() {
artifact := artifact
@ -163,7 +169,7 @@ func uploadWithFilter(ctx *context.Context, put config.Put, filter artifact.Filt
}
// uploadAsset uploads file to target and logs all actions
func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifact, kind string, check ResponseChecker) error {
func uploadAsset(ctx *context.Context, put *config.Put, artifact artifact.Artifact, kind string, check ResponseChecker) error {
envBase := fmt.Sprintf("%s_%s_", strings.ToUpper(kind), strings.ToUpper(put.Name))
username := put.Username
if username == "" {
@ -193,7 +199,7 @@ func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifac
}
targetURL += artifact.Name
_, err = uploadAssetToServer(ctx, targetURL, username, secret, asset, check)
_, err = uploadAssetToServer(ctx, put, targetURL, username, secret, asset, check)
if err != nil {
msg := fmt.Sprintf("%s: upload failed", kind)
log.WithError(err).WithFields(log.Fields{
@ -212,22 +218,18 @@ func uploadAsset(ctx *context.Context, put config.Put, artifact artifact.Artifac
}
// uploadAssetToServer uploads the asset file to target
func uploadAssetToServer(ctx *context.Context, target, username, secret string, a *asset, check ResponseChecker) (*h.Response, error) {
func uploadAssetToServer(ctx *context.Context, put *config.Put, target, username, secret string, a *asset, check ResponseChecker) (*h.Response, error) {
req, err := newUploadRequest(target, username, secret, a)
if err != nil {
return nil, err
}
return executeHTTPRequest(ctx, req, check)
return executeHTTPRequest(ctx, put, req, check)
}
// newUploadRequest creates a new h.Request for uploading
func newUploadRequest(target, username, secret string, a *asset) (*h.Request, error) {
u, err := url.Parse(target)
if err != nil {
return nil, err
}
req, err := h.NewRequest("PUT", u.String(), a.ReadCloser)
req, err := h.NewRequest("PUT", target, a.ReadCloser)
if err != nil {
return nil, err
}
@ -238,9 +240,36 @@ func newUploadRequest(target, username, secret string, a *asset) (*h.Request, er
return req, err
}
func getHTTPClient(put *config.Put) (*h.Client, error) {
if put.TrustedCerts == "" {
return h.DefaultClient, nil
}
pool, err := x509.SystemCertPool()
if err != nil {
if runtime.GOOS == "windows" {
// on windows ignore errors until golang issues #16736 & #18609 get fixed
pool = x509.NewCertPool()
} else {
return nil, err
}
}
pool.AppendCertsFromPEM([]byte(put.TrustedCerts)) // already validated certs checked by CheckConfig
return &h.Client{
Transport: &h.Transport{
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
},
}, nil
}
// executeHTTPRequest processes the http call with respect of context ctx
func executeHTTPRequest(ctx *context.Context, req *h.Request, check ResponseChecker) (*h.Response, error) {
resp, err := h.DefaultClient.Do(req)
func executeHTTPRequest(ctx *context.Context, put *config.Put, req *h.Request, check ResponseChecker) (*h.Response, error) {
client, err := getHTTPClient(put)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
@ -249,7 +278,6 @@ func executeHTTPRequest(ctx *context.Context, req *h.Request, check ResponseChec
return nil, ctx.Err()
default:
}
return nil, err
}
@ -280,21 +308,21 @@ type targetData struct {
// 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.Put, artifact artifact.Artifact) (string, error) {
func resolveTargetTemplate(ctx *context.Context, put *config.Put, artifact artifact.Artifact) (string, error) {
data := targetData{
Version: ctx.Version,
Tag: ctx.Git.CurrentTag,
ProjectName: ctx.Config.ProjectName,
}
if artifactory.Mode == ModeBinary {
if put.Mode == ModeBinary {
data.Os = replace(ctx.Config.Archive.Replacements, artifact.Goos)
data.Arch = replace(ctx.Config.Archive.Replacements, artifact.Goarch)
data.Arm = replace(ctx.Config.Archive.Replacements, artifact.Goarm)
}
var out bytes.Buffer
t, err := template.New(ctx.Config.ProjectName).Parse(artifactory.Target)
t, err := template.New(ctx.Config.ProjectName).Parse(put.Target)
if err != nil {
return "", err
}

View File

@ -2,12 +2,14 @@ package http
import (
"bytes"
"encoding/pem"
"fmt"
"io"
"io/ioutil"
h "net/http"
"net/http/httptest"
"os"
"strings"
"sync"
"testing"
@ -18,20 +20,6 @@ import (
"github.com/goreleaser/goreleaser/pkg/context"
)
var (
mux *h.ServeMux
srv *httptest.Server
)
func setup() {
mux = h.NewServeMux()
srv = httptest.NewServer(mux)
}
func teardown() {
srv.Close()
}
func TestAssetOpenDefault(t *testing.T) {
tf, err := ioutil.TempFile("", "")
if err != nil {
@ -112,6 +100,7 @@ func TestCheckConfig(t *testing.T) {
{"name missing", args{ctx, &config.Put{Target: "http://blabla", Username: "pepe", Mode: ModeArchive}, "test"}, true},
{"mode missing", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe"}, "test"}, true},
{"mode invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: "blabla"}, "test"}, true},
{"cert invalid", args{ctx, &config.Put{Name: "a", Target: "http://blabla", Username: "pepe", Mode: ModeBinary, TrustedCerts: "bad cert!"}, "test"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -196,11 +185,10 @@ func doCheck(c check, r *h.Request) error {
}
func TestUpload(t *testing.T) {
setup()
defer teardown()
content := []byte("blah!")
requests := []*h.Request{}
var m sync.Mutex
mux := h.NewServeMux()
mux.Handle("/", h.HandlerFunc(func(w h.ResponseWriter, r *h.Request) {
bs, err := ioutil.ReadAll(r.Body)
if err != nil {
@ -247,37 +235,93 @@ func TestUpload(t *testing.T) {
} {
ctx.Artifacts.Add(artifact.Artifact{Name: "a." + a.ext, Path: "/a/a." + a.ext, Type: a.typ})
}
tests := []struct {
name string
ctx *context.Context
wantErr bool
put config.Put
check func(r []*h.Request) error
name string
tryPlain bool
tryTLS bool
wantErrPlain bool
wantErrTLS bool
setup func(*httptest.Server) (*context.Context, config.Put)
check func(r []*h.Request) error
}{
{"wrong-mode", ctx, true,
config.Put{Mode: "wrong-mode", Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u1"},
{"wrong-mode", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: "wrong-mode",
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u1",
TrustedCerts: cert(s),
}
},
checks(),
},
{"username-from-env", ctx, false,
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/"},
{"username-from-env", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u2", "x", content},
check{"/blah/2.1.0/a.tar", "u2", "x", content},
),
},
{"archive", ctx, false,
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u1"},
{"archive", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u1",
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u1", "x", content},
check{"/blah/2.1.0/a.tar", "u1", "x", content},
),
},
{"binary", ctx, false,
config.Put{Mode: ModeBinary, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u2"},
{"binary", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u2",
TrustedCerts: cert(s),
}
},
checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content}),
},
{"archive-with-checksum-and-signature", ctx, false,
config.Put{Mode: ModeArchive, Name: "a", Target: srv.URL + "/{{.ProjectName}}/{{.Version}}/", Username: "u3", Checksum: true, Signature: true},
{"binary-add-ending-bar", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}",
Username: "u2",
TrustedCerts: cert(s),
}
},
checks(check{"/blah/2.1.0/a.ubi", "u2", "x", content}),
},
{"archive-with-checksum-and-signature", true, true, false, false,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeArchive,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Checksum: true,
Signature: true,
TrustedCerts: cert(s),
}
},
checks(
check{"/blah/2.1.0/a.deb", "u3", "x", content},
check{"/blah/2.1.0/a.tar", "u3", "x", content},
@ -285,16 +329,100 @@ func TestUpload(t *testing.T) {
check{"/blah/2.1.0/a.sig", "u3", "x", content},
),
},
{"bad-template", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectNameXXX}}/{{.VersionXXX}}/",
Username: "u3",
Checksum: true,
Signature: true,
TrustedCerts: cert(s),
}
},
checks(),
},
{"failed-request", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL[0:strings.LastIndex(s.URL, ":")] + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Checksum: true,
Signature: true,
TrustedCerts: cert(s),
}
},
checks(),
},
{"broken-cert", false, true, false, true,
func(s *httptest.Server) (*context.Context, config.Put) {
return ctx, config.Put{
Mode: ModeBinary,
Name: "a",
Target: s.URL + "/{{.ProjectName}}/{{.Version}}/",
Username: "u3",
Checksum: false,
Signature: false,
TrustedCerts: "bad certs!",
}
},
checks(),
},
{"skip-publishing", true, true, true, true,
func(s *httptest.Server) (*context.Context, config.Put) {
c := *ctx
c.SkipPublish = true
return &c, config.Put{}
},
checks(),
},
}
uploadAndCheck := func(setup func(*httptest.Server) (*context.Context, config.Put), wantErrPlain, wantErrTLS bool, check func(r []*h.Request) error, srv *httptest.Server) {
requests = nil
ctx, put := setup(srv)
wantErr := wantErrPlain
if srv.Certificate() != nil {
wantErr = wantErrTLS
}
if err := Upload(ctx, []config.Put{put}, "test", is2xx); (err != nil) != wantErr {
t.Errorf("Upload() error = %v, wantErr %v", err, wantErr)
}
if err := check(requests); err != nil {
t.Errorf("Upload() request invalid. Error: %v", err)
}
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
requests = nil
if err := Upload(tt.ctx, []config.Put{tt.put}, "test", is2xx); (err != nil) != tt.wantErr {
t.Errorf("Upload() error = %v, wantErr %v", err, tt.wantErr)
}
if err := tt.check(requests); err != nil {
t.Errorf("Upload() request invalid. Error: %v", err)
}
})
if tt.tryPlain {
t.Run(tt.name, func(t *testing.T) {
srv := httptest.NewServer(mux)
defer srv.Close()
uploadAndCheck(tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
})
}
if tt.tryTLS {
t.Run(tt.name+"-tls", func(t *testing.T) {
srv := httptest.NewUnstartedServer(mux)
srv.StartTLS()
defer srv.Close()
uploadAndCheck(tt.setup, tt.wantErrPlain, tt.wantErrTLS, tt.check, srv)
})
}
}
}
func cert(srv *httptest.Server) string {
if srv == nil || srv.Certificate() == nil {
return ""
}
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: srv.Certificate().Raw,
}
return string(pem.EncodeToMemory(block))
}

View File

@ -287,12 +287,13 @@ type S3 struct {
// Put HTTP upload configuration
type Put struct {
Name string `yaml:",omitempty"`
Target string `yaml:",omitempty"`
Username string `yaml:",omitempty"`
Mode string `yaml:",omitempty"`
Checksum bool `yaml:",omitempty"`
Signature bool `yaml:",omitempty"`
Name string `yaml:",omitempty"`
Target string `yaml:",omitempty"`
Username string `yaml:",omitempty"`
Mode string `yaml:",omitempty"`
Checksum bool `yaml:",omitempty"`
Signature bool `yaml:",omitempty"`
TrustedCerts string `yaml:"trusted_certificates,omitempty"`
}
// Project includes all project configuration

View File

@ -88,6 +88,34 @@ 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.
### Server authentication
You can authenticate your Artifactory TLS server adding a trusted X.509
certificate chain in your configuration.
The trusted certificate chain will be used to validate the server certificates.
You can set the trusted certificate chain using the `trusted_certificates`
setting the artifactory section with PEM encoded certificates on a YAML literal
block like this:
```yaml
puts:
- name: "some artifactory server with a private TLS certificate"
#...(other settings)...
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
## Customization
Of course, you can customize a lot of things:
@ -112,6 +140,13 @@ artifactories:
checksum: true
# Upload signatures (defaults to false)
signature: true
# Certificate chain used to validate server certificates
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
These settings should allow you to push your artifacts into multiple Artifactories.

View File

@ -5,7 +5,6 @@ hideFromIndex: true
weight: 120
---
Since [vX.Y.Z](https://github.com/goreleaser/goreleaser/releases/tag/vX.Y.Z),
GoReleaser supports building and pushing artifacts to HTTP servers using simple HTTP PUT requests.
## How it works
@ -85,6 +84,34 @@ If your instance is named `production`, you need to store the secret in the
environment variable `PUT_PRODUCTION_SECRET`.
The name will be transformed to uppercase.
### Server authentication
You can authenticate your TLS server adding a trusted X.509 certificate chain
in your put configuration.
The trusted certificate chain will be used to validate the server certificates.
You can set the trusted certificate chain using the `trusted_certificates`
setting the put section with PEM encoded certificates on a YAML literal block
like this:
```yaml
puts:
- name: "some HTTP/TLS server"
#...(other settings)...
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
## Customization
Of course, you can customize a lot of things:
@ -102,13 +129,20 @@ puts:
# Default is `archive`.
mode: archive
# URL to be used as target of the HTTP PUT request
target: http://some.server/some/path/example-repo-local/{{ .ProjectName }}/{{ .Version }}/
target: https://some.server/some/path/example-repo-local/{{ .ProjectName }}/{{ .Version }}/
# User that will be used for the deployment
username: deployuser
# Upload checksums (defaults to false)
checksum: true
# Upload signatures (defaults to false)
signature: true
# Certificate chain used to validate server certificates
trusted_certificates: |
-----BEGIN CERTIFICATE-----
MIIDrjCCApagAwIBAgIIShr2zchZo+8wDQYJKoZIhvcNAQENBQAwNTEXMBUGA1UE
...(edited content)...
TyzMJasj5BPZrmKjJb6O/tOtEIJ66xPSBTxPShkEYHnB7A==
-----END CERTIFICATE-----
```
These settings should allow you to push your artifacts into multiple HTTP servers.