1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-02-07 13:31:37 +02:00

feat: remove deprecated s3 pipe (#1291)

* feat: remove deprecated s3 pipe

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>

* fix: go mod tidy

Signed-off-by: Carlos Alexandro Becker <caarlos0@gmail.com>
This commit is contained in:
Carlos Alexandro Becker 2020-01-26 12:25:27 -03:00 committed by GitHub
parent f1fc2c03bd
commit 5f2cf501e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 18 additions and 775 deletions

2
go.mod
View File

@ -6,7 +6,7 @@ require (
code.gitea.io/sdk/gitea v0.0.0-20191013013401-e41e9ea72caa
github.com/Masterminds/semver/v3 v3.0.3
github.com/apex/log v1.1.1
github.com/aws/aws-sdk-go v1.25.11
github.com/aws/aws-sdk-go v1.25.11 // indirect
github.com/caarlos0/ctrlc v1.0.0
github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e
github.com/fatih/color v1.9.0

View File

@ -11,7 +11,6 @@ import (
"github.com/goreleaser/goreleaser/internal/pipe/brew"
"github.com/goreleaser/goreleaser/internal/pipe/docker"
"github.com/goreleaser/goreleaser/internal/pipe/release"
"github.com/goreleaser/goreleaser/internal/pipe/s3"
"github.com/goreleaser/goreleaser/internal/pipe/scoop"
"github.com/goreleaser/goreleaser/internal/pipe/snapcraft"
"github.com/goreleaser/goreleaser/internal/pipe/upload"
@ -36,7 +35,6 @@ type Publisher interface {
// nolint: gochecknoglobals
var publishers = []Publisher{
s3.Pipe{},
blob.Pipe{},
upload.Pipe{},
artifactory.Pipe{},

View File

@ -1,86 +0,0 @@
package s3
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
)
type SessionBuilder interface {
Config(*aws.Config) SessionBuilder
Profile(string) SessionBuilder
Options(*session.Options) SessionBuilder
Endpoint(string) SessionBuilder
S3ForcePathStyle(bool) SessionBuilder
Build() *session.Session
}
type sessionBuilder struct {
awsConfig *aws.Config
profile string
options *session.Options
endpoint *string
forcePathStyle *bool
}
func (sb *sessionBuilder) Config(c *aws.Config) SessionBuilder {
sb.awsConfig = c
return sb
}
func (sb *sessionBuilder) Profile(p string) SessionBuilder {
sb.profile = p
return sb
}
func (sb *sessionBuilder) Endpoint(e string) SessionBuilder {
sb.endpoint = aws.String(e)
return sb
}
func (sb *sessionBuilder) S3ForcePathStyle(b bool) SessionBuilder {
sb.forcePathStyle = aws.Bool(b)
return sb
}
func (sb *sessionBuilder) Options(o *session.Options) SessionBuilder {
sb.options = o
return sb
}
func (sb *sessionBuilder) Build() *session.Session {
if sb.awsConfig == nil {
sb.awsConfig = aws.NewConfig()
}
if sb.endpoint != nil {
sb.awsConfig.Endpoint = sb.endpoint
sb.awsConfig.S3ForcePathStyle = sb.forcePathStyle
}
sb.awsConfig.Credentials = credentials.NewChainCredentials([]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{
Profile: sb.profile,
},
})
_, err := sb.awsConfig.Credentials.Get()
if err == nil {
return session.Must(session.NewSession(sb.awsConfig))
}
if sb.options == nil {
sb.options = &session.Options{
AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
SharedConfigState: session.SharedConfigEnable,
Profile: sb.profile,
}
}
return session.Must(session.NewSessionWithOptions(*sb.options))
}
func newSessionBuilder() SessionBuilder {
return &sessionBuilder{}
}

View File

@ -1,202 +0,0 @@
package s3
import (
"testing"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/stretchr/testify/assert"
)
func setEnv() {
os.Setenv("AWS_ACCESS_KEY_ID", "accessKey")
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
}
func clearnEnv() {
os.Unsetenv("AWS_ACCESS_KEY_ID")
os.Unsetenv("AWS_SECRET_ACCESS_KEY")
os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE")
os.Unsetenv("AWS_CONFIG_FILE")
}
func Test_awsSession(t *testing.T) {
type args struct {
profile string
}
tests := []struct {
name string
args args
want *session.Session
before func()
expectToken string
endpoint string
S3ForcePathStyle bool
}{
{
name: "test endpoint",
before: setEnv,
endpoint: "test",
},
{
name: "test S3ForcePathStyle",
before: setEnv,
S3ForcePathStyle: true,
},
{
name: "test env provider",
args: args{
profile: "test1",
},
before: setEnv,
},
{
name: "test default shared credentials provider",
before: func() {
clearnEnv()
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", filepath.Join("testdata", "credentials.ini"))
},
expectToken: "token",
},
{
name: "test default shared credentials provider",
before: func() {
clearnEnv()
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", filepath.Join("testdata", "credentials.ini"))
},
expectToken: "token",
},
{
name: "test profile with shared credentials provider",
before: func() {
clearnEnv()
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", filepath.Join("testdata", "credentials.ini"))
},
args: args{
profile: "no_token",
},
expectToken: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clearnEnv()
defer clearnEnv()
if tt.before != nil {
tt.before()
}
builder := newSessionBuilder()
builder.Profile(tt.args.profile)
builder.Endpoint(tt.endpoint)
builder.S3ForcePathStyle(tt.S3ForcePathStyle)
sess := builder.Build()
assert.NotNil(t, sess)
creds, err := sess.Config.Credentials.Get()
assert.Nil(t, err)
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, tt.expectToken, creds.SessionToken, "Expect token to match")
assert.Equal(t, aws.String(tt.endpoint), sess.Config.Endpoint, "Expect endpoint to match")
assert.Equal(t, aws.Bool(tt.S3ForcePathStyle), sess.Config.S3ForcePathStyle, "Expect S3ForcePathStyle to match")
})
}
}
const assumeRoleRespMsg = `
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleResult>
<AssumedRoleUser>
<Arn>arn:aws:sts::account_id:assumed-role/role/session_name</Arn>
<AssumedRoleId>AKID:session_name</AssumedRoleId>
</AssumedRoleUser>
<Credentials>
<AccessKeyId>AKID</AccessKeyId>
<SecretAccessKey>SECRET</SecretAccessKey>
<SessionToken>SESSION_TOKEN</SessionToken>
<Expiration>%s</Expiration>
</Credentials>
</AssumeRoleResult>
<ResponseMetadata>
<RequestId>request-id</RequestId>
</ResponseMetadata>
</AssumeRoleResponse>
`
func Test_awsSession_mfa(t *testing.T) {
clearnEnv()
defer clearnEnv()
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", filepath.Join("testdata", "credentials.ini"))
os.Setenv("AWS_CONFIG_FILE", filepath.Join("testdata", "config.ini"))
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, r.FormValue("SerialNumber"), "arn:aws:iam::1111111111:mfa/test")
assert.Equal(t, r.FormValue("TokenCode"), "tokencode")
_, err := w.Write([]byte(fmt.Sprintf(assumeRoleRespMsg, time.Now().Add(15*time.Minute).Format("2006-01-02T15:04:05Z"))))
assert.NoError(t, err)
}))
customProviderCalled := false
options := &session.Options{
Profile: "cloudformation@flowlab-dev",
Config: aws.Config{
Region: aws.String("eu-west-1"),
Endpoint: aws.String(server.URL),
DisableSSL: aws.Bool(true),
},
SharedConfigState: session.SharedConfigEnable,
AssumeRoleTokenProvider: func() (string, error) {
customProviderCalled = true
return "tokencode", nil
},
}
builder := newSessionBuilder()
builder.Profile("cloudformation@flowlab-dev")
builder.Options(options)
sess := builder.Build()
creds, err := sess.Config.Credentials.Get()
assert.NoError(t, err)
assert.True(t, customProviderCalled)
assert.Contains(t, creds.ProviderName, "AssumeRoleProvider")
}
func Test_awsSession_fail(t *testing.T) {
tests := []struct {
name string
}{
{
name: "should fail with no credentials",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clearnEnv()
defer clearnEnv()
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "/nope")
builder := newSessionBuilder()
sess := builder.Build()
assert.NotNil(t, sess)
_, err := sess.Config.Credentials.Get()
assert.NotNil(t, err)
})
}
}

View File

@ -1,126 +0,0 @@
// Package s3 provides a Pipe that push artifacts to s3/minio
package s3
import (
"os"
"path/filepath"
"github.com/apex/log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/deprecate"
"github.com/goreleaser/goreleaser/internal/pipe"
"github.com/goreleaser/goreleaser/internal/semerrgroup"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
)
// Pipe for Artifactory
type Pipe struct{}
// String returns the description of the pipe
func (Pipe) String() string {
return "S3"
}
// Default sets the pipe defaults
func (Pipe) Default(ctx *context.Context) error {
for i := range ctx.Config.S3 {
s3 := &ctx.Config.S3[i]
if s3.Bucket == "" {
continue
}
deprecate.Notice("s3")
if s3.Folder == "" {
s3.Folder = "{{ .ProjectName }}/{{ .Tag }}"
}
if s3.Region == "" {
s3.Region = "us-east-1"
}
if s3.ACL == "" {
s3.ACL = "private"
}
}
return nil
}
// Publish to S3
func (Pipe) Publish(ctx *context.Context) error {
if len(ctx.Config.S3) == 0 {
return pipe.Skip("s3 section is not configured")
}
var g = semerrgroup.New(ctx.Parallelism)
for _, conf := range ctx.Config.S3 {
conf := conf
g.Go(func() error {
return upload(ctx, conf)
})
}
return g.Wait()
}
func newS3Svc(conf config.S3) *s3.S3 {
builder := newSessionBuilder()
builder.Profile(conf.Profile)
if conf.Endpoint != "" {
builder.Endpoint(conf.Endpoint)
builder.S3ForcePathStyle(true)
}
sess := builder.Build()
return s3.New(sess, &aws.Config{
Region: aws.String(conf.Region),
})
}
func upload(ctx *context.Context, conf config.S3) error {
var svc = newS3Svc(conf)
template := tmpl.New(ctx)
bucket, err := template.Apply(conf.Bucket)
if err != nil {
return err
}
folder, err := template.Apply(conf.Folder)
if err != nil {
return err
}
var filter = artifact.Or(
artifact.ByType(artifact.UploadableArchive),
artifact.ByType(artifact.UploadableBinary),
artifact.ByType(artifact.Checksum),
artifact.ByType(artifact.Signature),
artifact.ByType(artifact.LinuxPackage),
)
if len(conf.IDs) > 0 {
filter = artifact.And(filter, artifact.ByIDs(conf.IDs...))
}
var g = semerrgroup.New(ctx.Parallelism)
for _, artifact := range ctx.Artifacts.Filter(filter).List() {
artifact := artifact
g.Go(func() error {
f, err := os.Open(artifact.Path)
if err != nil {
return err
}
log.WithFields(log.Fields{
"bucket": bucket,
"folder": folder,
"artifact": artifact.Name,
}).Info("uploading")
_, err = svc.PutObjectWithContext(ctx, &s3.PutObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(filepath.Join(folder, artifact.Name)),
Body: f,
ACL: aws.String(conf.ACL),
})
return err
})
}
return g.Wait()
}

View File

@ -1,236 +0,0 @@
package s3
import (
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/testlib"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDescription(t *testing.T) {
assert.NotEmpty(t, Pipe{}.String())
}
func TestNoS3(t *testing.T) {
testlib.AssertSkipped(t, Pipe{}.Publish(context.New(config.Project{})))
}
func TestDefaultsNoS3(t *testing.T) {
var assert = assert.New(t)
var ctx = context.New(config.Project{
S3: []config.S3{
{},
},
})
assert.NoError(Pipe{}.Default(ctx))
assert.Equal([]config.S3{{}}, ctx.Config.S3)
}
func TestDefaults(t *testing.T) {
var assert = assert.New(t)
var ctx = context.New(config.Project{
S3: []config.S3{
{
Bucket: "foo",
},
},
})
assert.NoError(Pipe{}.Default(ctx))
assert.Equal([]config.S3{{
Bucket: "foo",
Region: "us-east-1",
Folder: "{{ .ProjectName }}/{{ .Tag }}",
ACL: "private",
}}, ctx.Config.S3)
}
func TestUpload(t *testing.T) {
var listen = randomListen(t)
folder, err := ioutil.TempDir("", "goreleasertest")
assert.NoError(t, err)
tgzpath := filepath.Join(folder, "bin.tar.gz")
debpath := filepath.Join(folder, "bin.deb")
assert.NoError(t, ioutil.WriteFile(tgzpath, []byte("fake\ntargz"), 0744))
assert.NoError(t, ioutil.WriteFile(debpath, []byte("fake\ndeb"), 0744))
var ctx = context.New(config.Project{
Dist: folder,
ProjectName: "testupload",
S3: []config.S3{
{
Bucket: "test",
Endpoint: "http://" + listen,
IDs: []string{"foo", "bar"},
},
},
})
ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.UploadableArchive,
Name: "bin.tar.gz",
Path: tgzpath,
Extra: map[string]interface{}{
"ID": "foo",
},
})
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.LinuxPackage,
Name: "bin.deb",
Path: debpath,
Extra: map[string]interface{}{
"ID": "bar",
},
})
var name = "test_upload"
defer stop(t, name)
start(t, name, listen)
prepareEnv(t, listen)
assert.NoError(t, Pipe{}.Default(ctx))
assert.NoError(t, Pipe{}.Publish(ctx))
}
func TestUploadCustomBucketID(t *testing.T) {
var listen = randomListen(t)
folder, err := ioutil.TempDir("", "goreleasertest")
assert.NoError(t, err)
tgzpath := filepath.Join(folder, "bin.tar.gz")
debpath := filepath.Join(folder, "bin.deb")
assert.NoError(t, ioutil.WriteFile(tgzpath, []byte("fake\ntargz"), 0744))
assert.NoError(t, ioutil.WriteFile(debpath, []byte("fake\ndeb"), 0744))
// Set custom BUCKET_ID env variable.
err = os.Setenv("BUCKET_ID", "test")
assert.NoError(t, err)
var ctx = context.New(config.Project{
Dist: folder,
ProjectName: "testupload",
S3: []config.S3{
{
Bucket: "{{.Env.BUCKET_ID}}",
Endpoint: "http://" + listen,
},
},
})
ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.UploadableArchive,
Name: "bin.tar.gz",
Path: tgzpath,
})
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.LinuxPackage,
Name: "bin.deb",
Path: debpath,
})
var name = "custom_bucket_id"
defer stop(t, name)
start(t, name, listen)
prepareEnv(t, listen)
assert.NoError(t, Pipe{}.Default(ctx))
assert.NoError(t, Pipe{}.Publish(ctx))
}
func TestUploadInvalidCustomBucketID(t *testing.T) {
var listen = randomListen(t)
folder, err := ioutil.TempDir("", "goreleasertest")
assert.NoError(t, err)
tgzpath := filepath.Join(folder, "bin.tar.gz")
debpath := filepath.Join(folder, "bin.deb")
assert.NoError(t, ioutil.WriteFile(tgzpath, []byte("fake\ntargz"), 0744))
assert.NoError(t, ioutil.WriteFile(debpath, []byte("fake\ndeb"), 0744))
// Set custom BUCKET_ID env variable.
assert.NoError(t, err)
var ctx = context.New(config.Project{
Dist: folder,
ProjectName: "testupload",
S3: []config.S3{
{
Bucket: "{{.Bad}}",
Endpoint: "http://" + listen,
},
},
})
ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"}
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.UploadableArchive,
Name: "bin.tar.gz",
Path: tgzpath,
})
ctx.Artifacts.Add(&artifact.Artifact{
Type: artifact.LinuxPackage,
Name: "bin.deb",
Path: debpath,
})
var name = "invalid_bucket_id"
defer stop(t, name)
start(t, name, listen)
prepareEnv(t, listen)
assert.NoError(t, Pipe{}.Default(ctx))
assert.Error(t, Pipe{}.Publish(ctx))
}
func randomListen(t *testing.T) string {
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
listener.Close()
return listener.Addr().String()
}
func prepareEnv(t *testing.T, listen string) {
os.Setenv("AWS_ACCESS_KEY_ID", "minio")
os.Setenv("AWS_SECRET_ACCESS_KEY", "miniostorage")
os.Setenv("AWS_REGION", "us-east-1")
t.Log("creating test bucket")
_, err := newS3Svc(config.S3{
Endpoint: "http://" + listen,
Region: "us-east-1",
}).CreateBucket(&s3.CreateBucketInput{
Bucket: aws.String("test"),
})
require.NoError(t, err)
}
func start(t *testing.T, name, listen string) {
if out, err := exec.Command(
"docker", "run", "-d", "--rm",
"--name", name,
"-p", listen+":9000",
"-e", "MINIO_ACCESS_KEY=minio",
"-e", "MINIO_SECRET_KEY=miniostorage",
"--health-interval", "1s",
"minio/minio:RELEASE.2019-05-14T23-57-45Z",
"server", "/data",
).CombinedOutput(); err != nil {
t.Fatalf("failed to start minio: %s", string(out))
}
for range time.Tick(time.Second) {
out, err := exec.Command("docker", "inspect", "--format='{{json .State.Health}}'", name).CombinedOutput()
if err != nil {
t.Fatalf("failed to check minio status: %s", string(out))
}
if strings.Contains(string(out), `"Status":"healthy"`) {
t.Log("minio is healthy")
break
}
t.Log("waiting for minio to be healthy")
}
}
func stop(t *testing.T, name string) {
if out, err := exec.Command("docker", "stop", name).CombinedOutput(); err != nil {
t.Fatalf("failed to stop minio: %s", string(out))
}
}

View File

@ -1,4 +0,0 @@
[profile cloudformation@flowlab-dev]
role_arn = arn:aws:iam::1111111111:role/CloudFormation
source_profile = user
mfa_serial = arn:aws:iam::1111111111:mfa/test

View File

@ -1,12 +0,0 @@
[default]
aws_access_key_id = accessKey
aws_secret_access_key = secret
aws_session_token = token
[no_token]
aws_access_key_id = accessKey
aws_secret_access_key = secret
[user]
aws_access_key_id = accesKey
aws_secret_access_key = secret

View File

@ -313,17 +313,6 @@ type Before struct {
Hooks []string `yaml:",omitempty"`
}
// S3 contains s3 config
type S3 struct {
Region string `yaml:",omitempty"`
Bucket string `yaml:",omitempty"`
Folder string `yaml:",omitempty"`
Profile string `yaml:",omitempty"`
Endpoint string `yaml:",omitempty"` // used for minio for example
ACL string `yaml:",omitempty"`
IDs []string `yaml:"ids,omitempty"`
}
// Blob contains config for GO CDK blob
type Blob struct {
Bucket string `yaml:",omitempty"`
@ -368,7 +357,6 @@ type Project struct {
Artifactories []Upload `yaml:",omitempty"`
Uploads []Upload `yaml:",omitempty"`
Puts []Upload `yaml:",omitempty"` // TODO: remove this
S3 []S3 `yaml:"s3,omitempty"`
Blob []Blob `yaml:"blob,omitempty"` // TODO: remove this
Blobs []Blob `yaml:"blobs,omitempty"`
Changelog Changelog `yaml:",omitempty"`

View File

@ -16,7 +16,6 @@ import (
"github.com/goreleaser/goreleaser/internal/pipe/nfpm"
"github.com/goreleaser/goreleaser/internal/pipe/project"
"github.com/goreleaser/goreleaser/internal/pipe/release"
"github.com/goreleaser/goreleaser/internal/pipe/s3"
"github.com/goreleaser/goreleaser/internal/pipe/scoop"
"github.com/goreleaser/goreleaser/internal/pipe/sign"
"github.com/goreleaser/goreleaser/internal/pipe/snapcraft"
@ -48,7 +47,6 @@ var Defaulters = []Defaulter{
sign.Pipe{},
docker.Pipe{},
artifactory.Pipe{},
s3.Pipe{},
blob.Pipe{},
brew.Pipe{},
scoop.Pipe{},

View File

@ -84,3 +84,13 @@ GCS provider uses [Application Default Credentials](https://cloud.google.com/doc
- Environment Variable (GOOGLE_APPLICATION_CREDENTIALS)
- Default Service Account from the compute instance(Compute Engine, Kubernetes Engine, Cloud function etc).
### ACLs
There is no common way to set ACLs across all bucket providers, so, [go-cloud][]
[does not support it yet][issue1108].
You are expected to set the ACLs on the bucket/folder/etc, depending on your
provider.
[go-cloud]: https://gocloud.dev/howto/blob/
[issue1108]: https://github.com/google/go-cloud/issues/1108

View File

@ -151,9 +151,13 @@ brews:
# etc
```
## Expired deprecation notices
The following options were deprecated for ~6 months and are now fully removed.
### s3
> since 2019-06-09
> since 2019-06-09, removed 2020-01-07
S3 was deprecated in favor of the new `blob`, which supports S3, Azure Blob and
GCS.
@ -175,9 +179,7 @@ blobs:
# etc
```
## Expired deprecation notices
The following options were deprecated for ~6 months and are now fully removed.
ACLs should be set on the bucket, the `acl` option does not exist anymore.
### archive

View File

@ -1,87 +0,0 @@
---
title: S3
series: customization
hideFromIndex: true
weight: 115
---
Since [v0.74.0](https://github.com/goreleaser/goreleaser/releases/tag/v0.74.0),
GoReleaser supports pushing artifacts to Amazon S3 and other API-compatible
object storages ([minio][] for example).
[minio]: https://www.minio.io
Right now, the implementation is quite simple and probably won't cover all
use cases. If you need one of such use cases, please open an issue/pull request.
Here is what you can customize:
## Customization
```yaml
# .goreleaser.yml
s3:
# You can have multiple s3 configs
-
# Template for the bucket name(without the s3:// prefix)
# Default is empty.
bucket: my-bucket
# IDs of the artifacts you want to upload.
ids:
- foo
- bar
# AWS Region to use.
# Defaults is us-east-1
region: us-east-1
# Template for the path/name inside the bucket.
# Default is `{{ .ProjectName }}/{{ .Tag }}`
folder: "foo/bar/{{.Version}}"
# Set a custom profile to use for this s3 config. If you have multiple
# profiles setup in you ~/.aws config, this shall help defining which
# profile to use in which s3 bucket.
# Default is empty.
profile: my-profile
# Endpoint allows you to set a custom endpoint, which is useful if you
# want to push your artifacts to a minio server for example.
# Default is AWS S3 URL.
endpoint: "http://minio.foo.com"
# Sets the ACL of the object using the specified canned ACL.
# Default is private.
acl: public-read
```
> Learn more about the [name template engine](/templates).
> Learn more about the [acl](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUTacl.html).
## Authentication
GoReleaser will authenticate using the [same methods defined by aws-cli][auth].
You can read the [docs][auth] to find out more about it.
Currently it supports authentication with:
- An [EnvProvider][EnvProvider] which retrieves credentials from the environment
variables of the running process. Environment credentials never expire.
Environment variables used:
- Access Key ID: `AWS_ACCESS_KEY_ID` or `AWS_ACCESS_KEY`
- Secret Access Key: `AWS_SECRET_ACCESS_KEY` or `AWS_SECRET_KEY`
- A [SharedCredentialsProvider][SharedCredentialsProvider] which retrieves
credentials from the current user's home directory, and keeps track if those
credentials are expired. Profile ini file example: `$HOME/.aws/credentials`
- A AssumeRoleTokenProvider with enabled SharedConfigState which uses MFA
prompting for token code on stdin. Go to [session doc][session] for more
details.
You can also set different profile names for each S3 config, so you may be able
to push to buckets in different accounts, for example.
[auth]: https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html
[envProvider]: https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#EnvProvider
[sharedCredentialsProvider]: https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#SharedCredentialsProvider
[session]: https://docs.aws.amazon.com/sdk-for-go/api/aws/session/