diff --git a/README.md b/README.md index b90b9a0..b94fde2 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,12 @@ your stack in a different region. ## Additional Flags +* `--stackit-s3-suffix "random"` +If the s3 bucket stackit creates requires a unique S3 bucket name. + +* `--stackit-tags "key=val,key2=val2"` +If the s3 bucket stackit creates requires tags, you can add them with this flag. + TODO: Document these properly * `--service-role VAL` diff --git a/cmd/package.go b/cmd/package.go index 66448d0..11492e5 100644 --- a/cmd/package.go +++ b/cmd/package.go @@ -18,6 +18,13 @@ import ( "bytes" "context" "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/sts" @@ -27,17 +34,11 @@ import ( "github.com/olekukonko/tablewriter" "github.com/pkg/errors" "github.com/spf13/cobra" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "text/template" ) -func packageTemplate(ctx context.Context, sess *session.Session, prefix string, templateReader packager.TemplateReader, writer io.Writer) (*string, error) { +func packageTemplate(ctx context.Context, sess *session.Session, prefix string, s3suffix string, s3Tags string, templateReader packager.TemplateReader, writer io.Writer) (*string, error) { s3api := s3.New(sess) - pkger := packager.New(s3api, sts.New(sess), *s3api.Config.Region) + pkger := packager.New(s3api, sts.New(sess), *s3api.Config.Region, s3suffix, s3Tags) packagedTemplate, err := pkger.Package(ctx, prefix, templateReader, writer) if err != nil { return nil, errors.Wrap(err, "packaging template") @@ -112,6 +113,8 @@ package will: profile, _ := cmd.PersistentFlags().GetString("profile") templatePath, _ := cmd.PersistentFlags().GetString("template") prefix, _ := cmd.PersistentFlags().GetString("prefix") + s3Suffix, _ := cmd.PersistentFlags().GetString("s3Suffix") + s3Tags, _ := cmd.PersistentFlags().GetString("s3Tags") template, err := pathToTemplate(templatePath) if err != nil { @@ -123,7 +126,7 @@ package will: defer end() sess := awsSession(profile, region) - packagedTemplate, err := packageTemplate(ctx, sess, prefix, template, cmd.OutOrStderr()) + packagedTemplate, err := packageTemplate(ctx, sess, prefix, s3Suffix, s3Tags, template, cmd.OutOrStderr()) if err != nil { fmt.Fprintf(cmd.OutOrStderr(), "%+v\n", err) return diff --git a/cmd/root.go b/cmd/root.go index dc059ee..ddc665f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -53,6 +53,8 @@ func init() { RootCmd.PersistentFlags().String("region", "", "") RootCmd.PersistentFlags().String("profile", "", "") RootCmd.PersistentFlags().String("stack-name", "", "") + RootCmd.PersistentFlags().String("stackit-s3-suffix", "", "") + RootCmd.PersistentFlags().String("stackit-tags", "", "") // Here you will define your flags and configuration settings. // Cobra supports Persistent Flags, which, if defined here, diff --git a/cmd/up.go b/cmd/up.go index 243c901..6316fe9 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -17,6 +17,10 @@ package cmd import ( "context" "fmt" + "io" + "os" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/sts" @@ -25,9 +29,6 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "io" - "os" - "strings" ) func printUntilDone(ctx context.Context, events <-chan stackit.TailStackEvent, w io.Writer) { @@ -65,6 +66,8 @@ func parseCLIInput(cmd *cobra.Command, args []string) stackit.StackitUpInput { template, _ := cmd.PersistentFlags().GetString("template") tags, _ := cmd.PersistentFlags().GetStringSlice("tag") notificationArns, _ := cmd.PersistentFlags().GetStringSlice("notification-arn") + s3Suffix, _ := RootCmd.PersistentFlags().GetString("stackit-s3-suffix") + s3Tags, _ := RootCmd.PersistentFlags().GetString("stackit-tags") input := stackit.StackitUpInput{ StackName: stackName, @@ -101,6 +104,14 @@ func parseCLIInput(cmd *cobra.Command, args []string) stackit.StackitUpInput { input.Tags = keyvalSliceToMap(tags) } + if s3Suffix != "" { + input.S3Suffix = fmt.Sprintf("-%s", s3Suffix) + } + + if s3Tags != "" { + input.S3Tags = s3Tags + } + return input } @@ -121,7 +132,7 @@ func up(cmd *cobra.Command, args []string) error { defer printerCancel() if templateFile, ok := input.Template.(*templateReader); ok && templateFile != nil { - template, err := packageTemplate(ctx, sess, input.StackName, templateFile, cmd.OutOrStderr()) + template, err := packageTemplate(ctx, sess, input.StackName, input.S3Suffix, input.S3Tags, templateFile, cmd.OutOrStderr()) if err != nil { return errors.Wrap(err, "packaging template") } diff --git a/pkg/stackit/packager/bucket.go b/pkg/stackit/packager/bucket.go index 7652779..b25d13b 100644 --- a/pkg/stackit/packager/bucket.go +++ b/pkg/stackit/packager/bucket.go @@ -2,6 +2,8 @@ package packager import ( "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/s3" @@ -20,7 +22,8 @@ func (p *Packager) s3BucketName() (string, error) { } accountId := *getAccountResp.Account - bucketName := fmt.Sprintf("stackit-%s-%s", p.region, accountId) + // s3Suffix comes with a "-" if its set in params + bucketName := fmt.Sprintf("stackit-%s-%s%s", p.region, accountId, p.s3Suffix) enableVersioning := func() error { _, err := p.s3.PutBucketVersioning(&s3.PutBucketVersioningInput{ @@ -47,6 +50,19 @@ func (p *Packager) s3BucketName() (string, error) { return "", err } + if p.s3Tags != "" { + tags := getStackitTags(p.s3Tags) + _, err := p.s3.PutBucketTagging(&s3.PutBucketTaggingInput{ + Bucket: &bucketName, + Tagging: &s3.Tagging{ + TagSet: tags, + }, + }) + if err != nil { + return "", errors.Wrap(err, "Adding tags on bucket ") + } + } + p.cachedBucketName = bucketName return bucketName, nil } @@ -64,3 +80,24 @@ func (p *Packager) s3BucketName() (string, error) { p.cachedBucketName = bucketName return bucketName, nil } + +func getStackitTags(tags string) []*s3.Tag { + + tagList := strings.Split(tags, ",") + tagMap := make(map[string]string) + for _, pair := range tagList { + dict := strings.Split(pair, "=") + tagMap[dict[0]] = dict[1] + } + + result := make([]*s3.Tag, 0, len(tagMap)) + for k, v := range tagMap { + var t = &s3.Tag{ + Key: aws.String(k), + Value: aws.String(v), + } + result = append(result, t) + } + + return result +} diff --git a/pkg/stackit/packager/packager.go b/pkg/stackit/packager/packager.go index 003f28b..1ac8b0e 100644 --- a/pkg/stackit/packager/packager.go +++ b/pkg/stackit/packager/packager.go @@ -5,30 +5,35 @@ import ( "crypto/md5" "encoding/hex" "fmt" + "io" + "os" + "path/filepath" + "strings" + "github.com/aws/aws-sdk-go/service/s3/s3iface" "github.com/aws/aws-sdk-go/service/sts/stsiface" "github.com/glassechidna/stackit/pkg/stackit/cfnyaml" "github.com/glassechidna/stackit/pkg/zipper" "github.com/pkg/errors" - "io" - "os" - "path/filepath" - "strings" ) type Packager struct { - s3 s3iface.S3API - sts stsiface.STSAPI - region string + s3 s3iface.S3API + sts stsiface.STSAPI + region string + s3Suffix string + s3Tags string cachedBucketName string } -func New(s3 s3iface.S3API, sts stsiface.STSAPI, region string) *Packager { +func New(s3 s3iface.S3API, sts stsiface.STSAPI, region string, s3Suffix string, s3Tags string) *Packager { return &Packager{ - s3: s3, - sts: sts, - region: region, + s3: s3, + sts: sts, + region: region, + s3Suffix: s3Suffix, + s3Tags: s3Tags, } } diff --git a/pkg/stackit/up.go b/pkg/stackit/up.go index 0bc3529..861c545 100644 --- a/pkg/stackit/up.go +++ b/pkg/stackit/up.go @@ -3,14 +3,15 @@ package stackit import ( "context" "fmt" + "strings" + "time" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/cloudformation" "github.com/aws/aws-sdk-go/service/sts" "github.com/davecgh/go-spew/spew" "github.com/glassechidna/stackit/pkg/stackit/changeset" "github.com/pkg/errors" - "strings" - "time" ) type Template interface { @@ -28,6 +29,8 @@ type StackitUpInput struct { Tags map[string]string NotificationARNs []string PopulateMissing bool + S3Suffix string + S3Tags string } func (s *Stackit) populateMissing(ctx context.Context, input *StackitUpInput) error {