github.com/joselitofilho/goreleaser@v0.155.1-0.20210123221854-e4891856c593/internal/pipe/blob/upload.go (about) 1 package blob 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "net/url" 8 "path" 9 10 "github.com/apex/log" 11 "github.com/goreleaser/goreleaser/internal/artifact" 12 "github.com/goreleaser/goreleaser/internal/extrafiles" 13 "github.com/goreleaser/goreleaser/internal/semerrgroup" 14 "github.com/goreleaser/goreleaser/internal/tmpl" 15 "github.com/goreleaser/goreleaser/pkg/config" 16 "github.com/goreleaser/goreleaser/pkg/context" 17 "gocloud.dev/blob" 18 "gocloud.dev/secrets" 19 20 // Import the blob packages we want to be able to open. 21 _ "gocloud.dev/blob/azureblob" 22 _ "gocloud.dev/blob/gcsblob" 23 _ "gocloud.dev/blob/s3blob" 24 25 // import the secrets packages we want to be able to open:. 26 _ "gocloud.dev/secrets/awskms" 27 _ "gocloud.dev/secrets/azurekeyvault" 28 _ "gocloud.dev/secrets/gcpkms" 29 ) 30 31 func urlFor(ctx *context.Context, conf config.Blob) (string, error) { 32 bucket, err := tmpl.New(ctx).Apply(conf.Bucket) 33 if err != nil { 34 return "", err 35 } 36 37 bucketURL := fmt.Sprintf("%s://%s", conf.Provider, bucket) 38 39 if conf.Provider != "s3" { 40 return bucketURL, nil 41 } 42 43 var query = url.Values{} 44 if conf.Endpoint != "" { 45 query.Add("endpoint", conf.Endpoint) 46 query.Add("s3ForcePathStyle", "true") 47 } 48 if conf.Region != "" { 49 query.Add("region", conf.Region) 50 } 51 if conf.DisableSSL { 52 query.Add("disableSSL", "true") 53 } 54 55 if len(query) > 0 { 56 bucketURL = bucketURL + "?" + query.Encode() 57 } 58 59 return bucketURL, nil 60 } 61 62 // Takes goreleaser context(which includes artificats) and bucketURL for 63 // upload to destination (eg: gs://gorelease-bucket) using the given uploader 64 // implementation. 65 func doUpload(ctx *context.Context, conf config.Blob) error { 66 folder, err := tmpl.New(ctx).Apply(conf.Folder) 67 if err != nil { 68 return err 69 } 70 71 bucketURL, err := urlFor(ctx, conf) 72 if err != nil { 73 return err 74 } 75 76 var filter = artifact.Or( 77 artifact.ByType(artifact.UploadableArchive), 78 artifact.ByType(artifact.UploadableBinary), 79 artifact.ByType(artifact.UploadableSourceArchive), 80 artifact.ByType(artifact.Checksum), 81 artifact.ByType(artifact.Signature), 82 artifact.ByType(artifact.LinuxPackage), 83 ) 84 if len(conf.IDs) > 0 { 85 filter = artifact.And(filter, artifact.ByIDs(conf.IDs...)) 86 } 87 88 var up = newUploader(ctx) 89 if err := up.Open(ctx, bucketURL); err != nil { 90 return handleError(err, bucketURL) 91 } 92 defer up.Close() 93 94 var g = semerrgroup.New(ctx.Parallelism) 95 for _, artifact := range ctx.Artifacts.Filter(filter).List() { 96 artifact := artifact 97 g.Go(func() error { 98 // TODO: replace this with ?prefix=folder on the bucket url 99 var dataFile = artifact.Path 100 var uploadFile = path.Join(folder, artifact.Name) 101 102 err := uploadData(ctx, conf, up, dataFile, uploadFile, bucketURL) 103 104 return err 105 }) 106 } 107 108 files, err := extrafiles.Find(conf.ExtraFiles) 109 if err != nil { 110 return err 111 } 112 for name, fullpath := range files { 113 name := name 114 fullpath := fullpath 115 g.Go(func() error { 116 var uploadFile = path.Join(folder, name) 117 118 err := uploadData(ctx, conf, up, fullpath, uploadFile, bucketURL) 119 120 return err 121 }) 122 } 123 124 return g.Wait() 125 } 126 127 func uploadData(ctx *context.Context, conf config.Blob, up uploader, dataFile, uploadFile, bucketURL string) error { 128 data, err := getData(ctx, conf, dataFile) 129 if err != nil { 130 return err 131 } 132 133 err = up.Upload(ctx, uploadFile, data) 134 if err != nil { 135 return handleError(err, bucketURL) 136 } 137 return err 138 } 139 140 func handleError(err error, url string) error { 141 switch { 142 case errorContains(err, "NoSuchBucket", "ContainerNotFound", "notFound"): 143 return fmt.Errorf("provided bucket does not exist: %s: %w", url, err) 144 case errorContains(err, "NoCredentialProviders"): 145 return fmt.Errorf("check credentials and access to bucket: %s: %w", url, err) 146 case errorContains(err, "InvalidAccessKeyId"): 147 return fmt.Errorf("aws access key id you provided does not exist in our records: %w", err) 148 case errorContains(err, "AuthenticationFailed"): 149 return fmt.Errorf("azure storage key you provided is not valid: %w", err) 150 case errorContains(err, "invalid_grant"): 151 return fmt.Errorf("google app credentials you provided is not valid: %w", err) 152 case errorContains(err, "no such host"): 153 return fmt.Errorf("azure storage account you provided is not valid: %w", err) 154 case errorContains(err, "ServiceCode=ResourceNotFound"): 155 return fmt.Errorf("missing azure storage key for provided bucket %s: %w", url, err) 156 default: 157 return fmt.Errorf("failed to write to bucket: %w", err) 158 } 159 } 160 161 func newUploader(ctx *context.Context) uploader { 162 if ctx.SkipPublish { 163 return &skipUploader{} 164 } 165 return &productionUploader{} 166 } 167 168 func getData(ctx *context.Context, conf config.Blob, path string) ([]byte, error) { 169 data, err := ioutil.ReadFile(path) 170 if err != nil { 171 return data, fmt.Errorf("failed to open file %s: %w", path, err) 172 } 173 if conf.KMSKey == "" { 174 return data, nil 175 } 176 keeper, err := secrets.OpenKeeper(ctx, conf.KMSKey) 177 if err != nil { 178 return data, fmt.Errorf("failed to open kms %s: %w", conf.KMSKey, err) 179 } 180 defer keeper.Close() 181 data, err = keeper.Encrypt(ctx, data) 182 if err != nil { 183 return data, fmt.Errorf("failed to encrypt with kms: %w", err) 184 } 185 return data, err 186 } 187 188 // uploader implements upload. 189 type uploader interface { 190 io.Closer 191 Open(ctx *context.Context, url string) error 192 Upload(ctx *context.Context, path string, data []byte) error 193 } 194 195 // skipUploader is used when --skip-upload is set and will just log 196 // things without really doing anything. 197 type skipUploader struct{} 198 199 func (u *skipUploader) Close() error { return nil } 200 func (u *skipUploader) Open(_ *context.Context, _ string) error { return nil } 201 202 func (u *skipUploader) Upload(_ *context.Context, path string, _ []byte) error { 203 log.WithField("path", path).Warn("upload skipped because skip-publish is set") 204 return nil 205 } 206 207 // productionUploader actually do upload to. 208 type productionUploader struct { 209 bucket *blob.Bucket 210 } 211 212 func (u *productionUploader) Close() error { 213 if u.bucket == nil { 214 return nil 215 } 216 return u.bucket.Close() 217 } 218 func (u *productionUploader) Open(ctx *context.Context, bucket string) error { 219 log.WithFields(log.Fields{ 220 "bucket": bucket, 221 }).Debug("uploading") 222 223 conn, err := blob.OpenBucket(ctx, bucket) 224 if err != nil { 225 return err 226 } 227 u.bucket = conn 228 return nil 229 } 230 231 func (u *productionUploader) Upload(ctx *context.Context, filepath string, data []byte) (err error) { 232 log.WithField("path", filepath).Info("uploading") 233 234 opts := &blob.WriterOptions{ 235 ContentDisposition: "attachment; filename=" + path.Base(filepath), 236 } 237 w, err := u.bucket.NewWriter(ctx, filepath, opts) 238 if err != nil { 239 return err 240 } 241 defer func() { 242 if cerr := w.Close(); err == nil { 243 err = cerr 244 } 245 }() 246 _, err = w.Write(data) 247 return 248 }