github.com/jenkins-x/jx/v2@v2.1.155/pkg/cloud/amazon/storage/bucket_provider.go (about) 1 package storage 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net/url" 8 "os" 9 "strings" 10 11 session2 "github.com/jenkins-x/jx/v2/pkg/cloud/amazon/session" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/session" 15 "github.com/aws/aws-sdk-go/service/s3" 16 "github.com/aws/aws-sdk-go/service/s3/s3iface" 17 "github.com/aws/aws-sdk-go/service/s3/s3manager" 18 "github.com/aws/aws-sdk-go/service/s3/s3manager/s3manageriface" 19 "github.com/google/uuid" 20 "github.com/jenkins-x/jx-logging/pkg/log" 21 "github.com/jenkins-x/jx/v2/pkg/cloud/buckets" 22 "github.com/jenkins-x/jx/v2/pkg/config" 23 "github.com/jenkins-x/jx/v2/pkg/util" 24 "github.com/pkg/errors" 25 ) 26 27 // AmazonBucketProvider the bucket provider for AWS 28 type AmazonBucketProvider struct { 29 Requirements *config.RequirementsConfig 30 api s3iface.S3API 31 uploader s3manageriface.UploaderAPI 32 downloader s3manageriface.DownloaderAPI 33 } 34 35 func (b AmazonBucketProvider) createAWSSession() (*session.Session, error) { 36 region := b.Requirements.Cluster.Region 37 if region == "" { 38 return nil, errors.New("requirements do not specify a cluster region") 39 } 40 sess, err := session2.NewAwsSession("", region) 41 if err != nil { 42 return nil, errors.Wrap(err, "failed to create AWS session") 43 } 44 return sess, nil 45 } 46 47 func (b *AmazonBucketProvider) s3() (s3iface.S3API, error) { 48 if b.api != nil { 49 return b.api, nil 50 } 51 sess, err := b.createAWSSession() 52 if err != nil { 53 return nil, errors.Wrap(err, "there was a problem creating the s3 API interface") 54 } 55 b.api = s3.New(sess) 56 57 return b.api, nil 58 } 59 60 func (b *AmazonBucketProvider) s3ManagerDownloader() (s3manageriface.DownloaderAPI, error) { 61 if b.downloader != nil { 62 return b.downloader, nil 63 } 64 sess, err := b.createAWSSession() 65 if err != nil { 66 return nil, errors.Wrap(err, "there was a problem creating the s3ManagerDownloader") 67 } 68 b.downloader = s3manager.NewDownloader(sess) 69 return b.downloader, nil 70 } 71 72 func (b *AmazonBucketProvider) s3ManagerUploader() (s3manageriface.UploaderAPI, error) { 73 if b.uploader != nil { 74 return b.uploader, nil 75 } 76 sess, err := b.createAWSSession() 77 if err != nil { 78 return nil, errors.Wrap(err, "there was a problem creating the s3ManagerUploader") 79 } 80 b.uploader = s3manager.NewUploader(sess) 81 return b.uploader, nil 82 } 83 84 // CreateNewBucketForCluster creates a new dynamic bucket 85 func (b *AmazonBucketProvider) CreateNewBucketForCluster(clusterName string, bucketKind string) (string, error) { 86 uuid4 := uuid.New() 87 bucketName := fmt.Sprintf("%s-%s-%s", clusterName, bucketKind, uuid4.String()) 88 89 // Max length is 63, https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html 90 if len(bucketName) > 63 { 91 bucketName = bucketName[:63] 92 } 93 bucketName = strings.TrimRight(bucketName, "-") 94 bucketURL := "s3://" + bucketName 95 err := b.EnsureBucketIsCreated(bucketURL) 96 if err != nil { 97 return bucketURL, errors.Wrapf(err, "failed to create bucket %s", bucketURL) 98 } 99 100 return bucketURL, nil 101 } 102 103 // EnsureBucketIsCreated ensures the bucket URL is created 104 func (b *AmazonBucketProvider) EnsureBucketIsCreated(bucketURL string) error { 105 svc, err := b.s3() 106 if err != nil { 107 return err 108 } 109 110 u, err := url.Parse(bucketURL) 111 if err != nil { 112 return errors.Wrapf(err, "failed to parse bucket name from %s", bucketURL) 113 } 114 bucketName := u.Host 115 116 // Check if bucket exists already 117 _, err = svc.HeadBucket(&s3.HeadBucketInput{Bucket: aws.String(bucketName)}) 118 if err == nil { 119 return nil // bucket already exists 120 } 121 reqFailure, ok := err.(s3.RequestFailure) 122 if !ok || reqFailure.StatusCode() != 404 { 123 return errors.Wrapf(err, "failed to check if %s bucket exists already", bucketName) 124 } 125 126 infoBucketURL := util.ColorInfo(bucketURL) 127 log.Logger().Infof("The bucket %s does not exist so lets create it", infoBucketURL) 128 129 cbInput := &s3.CreateBucketInput{ 130 Bucket: aws.String(bucketName), 131 } 132 // There's a known problem with the S3 API that will make the request fail if you provide a CreateBucketConfiguration 133 // with a LocationConstraint pointing to the S3 default us-east-1 region. If not provided, it will be created in that region. 134 if b.Requirements.Cluster.Region != "us-east-1" { 135 cbInput.CreateBucketConfiguration = &s3.CreateBucketConfiguration{ 136 LocationConstraint: aws.String(b.Requirements.Cluster.Region), 137 } 138 } 139 _, err = svc.CreateBucket(cbInput) 140 if err != nil { 141 return errors.Wrapf(err, "there was a problem creating the bucket %s in the AWS", bucketName) 142 } 143 return nil 144 } 145 146 // UploadFileToBucket uploads a file to an S3 bucket to the provided bucket with the provided outputName 147 func (b *AmazonBucketProvider) UploadFileToBucket(reader io.Reader, outputName string, bucketURL string) (string, error) { 148 uploader, err := b.s3ManagerUploader() 149 if err != nil { 150 return "", nil 151 } 152 bucketURL = strings.TrimPrefix(bucketURL, "s3://") 153 output, err := uploader.Upload(&s3manager.UploadInput{ 154 Bucket: aws.String(bucketURL), 155 Key: aws.String("/" + outputName), 156 ContentType: aws.String(util.ContentTypeForFileName(outputName)), 157 Body: reader, 158 }) 159 if err != nil { 160 return "", err 161 } 162 log.Logger().Debugf("The file was uploaded successfully, location: %s", output.Location) 163 return fmt.Sprintf("%s://%s/%s", "s3", bucketURL, outputName), nil 164 } 165 166 // DownloadFileFromBucket downloads a file from an S3 bucket and converts the contents to a bufio.Scanner 167 func (b *AmazonBucketProvider) DownloadFileFromBucket(bucketURL string) (io.ReadCloser, error) { 168 downloader, err := b.s3ManagerDownloader() 169 if err != nil { 170 return nil, errors.Wrap(err, "there was a problem downloading from the bucket") 171 } 172 173 u, err := url.Parse(bucketURL) 174 if err != nil { 175 return nil, errors.Wrapf(err, "the provided bucket location is not a valid URL: %s", bucketURL) 176 } 177 requestInput := s3.GetObjectInput{ 178 Bucket: aws.String(u.Host), 179 Key: aws.String(u.Path), 180 } 181 182 f, err := ioutil.TempFile("", ".tmp-s3-download-") 183 if err != nil { 184 return nil, err 185 } 186 defer func() { 187 if err != nil { 188 _ = f.Close() 189 _ = os.Remove(f.Name()) 190 } 191 }() 192 _, err = downloader.Download(f, &requestInput) 193 if err != nil { 194 return nil, err 195 } 196 197 return &tempDownloadDestination{f}, nil 198 } 199 200 type tempDownloadDestination struct { 201 *os.File 202 } 203 204 func (f *tempDownloadDestination) Close() error { 205 err1 := f.File.Close() 206 err2 := os.Remove(f.File.Name()) 207 if err2 != nil && err1 == nil { 208 err1 = err2 209 } 210 return err1 211 } 212 213 // NewAmazonBucketProvider create a new provider for AWS 214 func NewAmazonBucketProvider(requirements *config.RequirementsConfig) buckets.Provider { 215 return &AmazonBucketProvider{ 216 Requirements: requirements, 217 } 218 }