Skip to content

Commit

Permalink
feat(aws): allow importing existing S3 buckets (#636)
Browse files Browse the repository at this point in the history
  • Loading branch information
jyecusch authored Jul 4, 2024
1 parent e92356d commit 37a4af8
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 7 deletions.
91 changes: 88 additions & 3 deletions cloud/aws/deploy/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ package deploy

import (
"fmt"
"regexp"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
"github.com/nitrictech/nitric/cloud/common/deploy/resources"
common "github.com/nitrictech/nitric/cloud/common/deploy/tags"
deploymentspb "github.com/nitrictech/nitric/core/pkg/proto/deployments/v1"
Expand Down Expand Up @@ -103,13 +106,95 @@ func createNotification(ctx *pulumi.Context, name string, args *S3NotificationAr
return notification, nil
}

// extractBucketName - extracts the bucket name from an S3 ARN.
func extractBucketName(arn string) (string, error) {
s3ArnRegex := regexp.MustCompile(`(?i)^arn:aws:s3:::([^/]+)`)

matches := s3ArnRegex.FindStringSubmatch(arn)
if len(matches) < 2 {
return "", fmt.Errorf("invalid S3 bucket ARN: %s", arn)
}

bucketName := matches[1]

if bucketName == "" {
return "", fmt.Errorf("invalid S3 bucket ARN: bucket name could not be extracted from %s", arn)
}

return bucketName, nil
}

// importBucket - tags an existing bucket in AWS and adds it to the stack.
func importBucket(ctx *pulumi.Context, name string, importIdentifier string, opts []pulumi.ResourceOption, tags map[string]string, tagClient *resourcegroupstaggingapi.ResourceGroupsTaggingAPI) (*s3.Bucket, error) {
// Allow bucket names or ARNs as import identifiers
bucketName, err := extractBucketName(importIdentifier)
if err != nil {
bucketName = importIdentifier
}

bucketLookup, err := s3.LookupBucket(ctx, &s3.LookupBucketArgs{
Bucket: bucketName,
})
if err != nil {
return nil, fmt.Errorf("unable to lookup imported S3 bucket %s: %w", bucketName, err)
}

_, err = tagClient.TagResources(&resourcegroupstaggingapi.TagResourcesInput{
ResourceARNList: aws.StringSlice([]string{bucketLookup.Arn}),
Tags: aws.StringMap(tags),
})
if err != nil {
return nil, fmt.Errorf("unable to tag imported S3 bucket %s: %w", bucketName, err)
}

// nitric didn't create this resource, so it shouldn't delete it either.
allOpts := append(opts, pulumi.RetainOnDelete(true))

bucket, err := s3.GetBucket(
ctx,
name,
pulumi.ID(bucketLookup.Id),
nil,
allOpts...,
)
if err != nil {
return nil, fmt.Errorf("unable to import S3 bucket %s: %w", bucketName, err)
}

return bucket, nil
}

// createBucket - creates a new S3 bucket in AWS and tags it.
func createBucket(ctx *pulumi.Context, name string, opts []pulumi.ResourceOption, tags map[string]string) (*s3.Bucket, error) {
bucket, err := s3.NewBucket(ctx, name, &s3.BucketArgs{
Tags: pulumi.ToStringMap(tags),
}, opts...)
if err != nil {
return nil, err
}

return bucket, nil
}

// Bucket - Implements deployments of Nitric Buckets using AWS S3
func (a *NitricAwsPulumiProvider) Bucket(ctx *pulumi.Context, parent pulumi.Resource, name string, config *deploymentspb.Bucket) error {
opts := []pulumi.ResourceOption{pulumi.Parent(parent)}
tags := common.Tags(a.StackId, name, resources.Bucket)

var err error
var bucket *s3.Bucket

importArn := ""
if a.AwsConfig.Import.Buckets != nil {
importArn = a.AwsConfig.Import.Buckets[name]
}

if importArn != "" {
bucket, err = importBucket(ctx, name, importArn, opts, tags, a.ResourceTaggingClient)
} else {
bucket, err = createBucket(ctx, name, opts, tags)
}

bucket, err := s3.NewBucket(ctx, name, &s3.BucketArgs{
Tags: pulumi.ToStringMap(common.Tags(a.StackId, name, resources.Bucket)),
}, opts...)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions cloud/aws/deploy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ type ApiConfig struct {
Domains []string
}

// AwsImports - Import configuration for AWS, maps nitric names of resources to the ARNs of existing AWS resources.
type AwsImports struct {
// A map of nitric names to ARNs
Secrets map[string]string
Buckets map[string]string
}

type AwsConfig struct {
Expand Down
1 change: 1 addition & 0 deletions cloud/aws/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ func (a *NitricAwsPulumiProvider) Pre(ctx *pulumi.Context, resources []*pulumix.
a.StackId = <-stackIdChan

sess := session.Must(session.NewSessionWithOptions(session.Options{
Config: aws.Config{Region: aws.String(a.Region)},
SharedConfigState: session.SharedConfigEnable,
}))

Expand Down
8 changes: 4 additions & 4 deletions cloud/aws/deploy/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ import (
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// tagSecret - tags an existing secret in AWS and adds it to the stack.
func tagSecret(ctx *pulumi.Context, name string, importArn string, tags map[string]string, client *resourcegroupstaggingapi.ResourceGroupsTaggingAPI) (*secretsmanager.Secret, error) {
// importSecret - tags an existing secret in AWS and adds it to the stack.
func importSecret(ctx *pulumi.Context, name string, importArn string, tags map[string]string, tagClient *resourcegroupstaggingapi.ResourceGroupsTaggingAPI) (*secretsmanager.Secret, error) {
secretLookup, err := secretsmanager.LookupSecret(ctx, &secretsmanager.LookupSecretArgs{
Arn: aws.String(importArn),
})
if err != nil {
return nil, err
}

_, err = client.TagResources(&resourcegroupstaggingapi.TagResourcesInput{
_, err = tagClient.TagResources(&resourcegroupstaggingapi.TagResourcesInput{
ResourceARNList: aws.StringSlice([]string{secretLookup.Arn}),
Tags: aws.StringMap(tags),
})
Expand Down Expand Up @@ -82,7 +82,7 @@ func (a *NitricAwsPulumiProvider) Secret(ctx *pulumi.Context, parent pulumi.Reso
}

if importArn != "" {
secret, err = tagSecret(ctx, name, importArn, awsTags, a.ResourceTaggingClient)
secret, err = importSecret(ctx, name, importArn, awsTags, a.ResourceTaggingClient)
} else {
secret, err = createSecret(ctx, name, awsTags)
}
Expand Down

0 comments on commit 37a4af8

Please sign in to comment.