1package publisher 2 3import ( 4 "context" 5 "mime" 6 "net/http" 7 "os" 8 "path" 9 "strings" 10 11 "github.com/alecthomas/kingpin" 12 "github.com/aws/aws-sdk-go/aws" 13 "github.com/aws/aws-sdk-go/aws/credentials" 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/s3manager" 17 "github.com/develar/app-builder/pkg/util" 18 "github.com/develar/errors" 19) 20 21type ObjectOptions struct { 22 file *string 23 24 endpoint *string 25 region *string 26 bucket *string 27 key *string 28 29 acl *string 30 storageClass *string 31 encryption *string 32 33 accessKey *string 34 secretKey *string 35} 36 37func ConfigurePublishToS3Command(app *kingpin.Application) { 38 command := app.Command("publish-s3", "Publish to S3") 39 options := ObjectOptions{ 40 file: command.Flag("file", "").Required().String(), 41 42 region: command.Flag("region", "").String(), 43 bucket: command.Flag("bucket", "").Required().String(), 44 key: command.Flag("key", "").Required().String(), 45 endpoint: command.Flag("endpoint", "").String(), 46 47 acl: command.Flag("acl", "").String(), 48 storageClass: command.Flag("storageClass", "").String(), 49 encryption: command.Flag("encryption", "").String(), 50 51 accessKey: command.Flag("accessKey", "").String(), 52 secretKey: command.Flag("secretKey", "").String(), 53 } 54 55 command.Action(func(context *kingpin.ParseContext) error { 56 err := upload(&options) 57 if err != nil { 58 return err 59 } 60 return nil 61 }) 62 63 configureResolveBucketLocationCommand(app) 64} 65 66func configureResolveBucketLocationCommand(app *kingpin.Application) { 67 command := app.Command("get-bucket-location", "") 68 bucket := command.Flag("bucket", "").Required().String() 69 command.Action(func(context *kingpin.ParseContext) error { 70 requestContext, _ := util.CreateContext() 71 result, err := getBucketRegion(aws.NewConfig(), bucket, requestContext, createHttpClient()) 72 if err != nil { 73 return err 74 } 75 76 _, err = os.Stdout.WriteString(result) 77 if err != nil { 78 return errors.WithStack(err) 79 } 80 return nil 81 }) 82} 83 84func getBucketRegion(awsConfig *aws.Config, bucket *string, context context.Context, httpClient *http.Client) (string, error) { 85 awsSession, err := session.NewSession(awsConfig, &aws.Config{ 86 // any region required 87 Region: aws.String("us-east-1"), 88 HTTPClient: httpClient, 89 }) 90 if err != nil { 91 return "", errors.WithStack(err) 92 } 93 94 client := s3.New(awsSession) 95 result, err := client.GetBucketLocationWithContext(context, &s3.GetBucketLocationInput{ 96 Bucket: bucket, 97 }) 98 if err != nil { 99 return "", errors.WithStack(err) 100 } 101 if result == nil || result.LocationConstraint == nil || len(*result.LocationConstraint) == 0 { 102 return "us-east-1", nil 103 } 104 return *result.LocationConstraint, nil 105} 106 107func upload(options *ObjectOptions) error { 108 publishContext, _ := util.CreateContext() 109 110 httpClient := createHttpClient() 111 112 awsConfig := &aws.Config{ 113 HTTPClient: httpClient, 114 } 115 if *options.endpoint != "" { 116 awsConfig.Endpoint = options.endpoint 117 awsConfig.S3ForcePathStyle = aws.Bool(true) 118 } 119 120 //awsConfig.WithLogLevel(aws.LogDebugWithHTTPBody) 121 122 if *options.accessKey != "" { 123 awsConfig.Credentials = credentials.NewStaticCredentials(*options.accessKey, *options.secretKey, "") 124 } 125 126 switch { 127 case *options.region != "": 128 awsConfig.Region = options.region 129 case *options.endpoint != "": 130 awsConfig.Region = aws.String("us-east-1") 131 default: 132 // AWS SDK for Go requires region 133 region, err := getBucketRegion(awsConfig, options.bucket, publishContext, httpClient) 134 if err != nil { 135 return errors.WithStack(err) 136 } 137 awsConfig.Region = ®ion 138 } 139 140 awsSession, err := session.NewSession(awsConfig) 141 if err != nil { 142 return errors.WithStack(err) 143 } 144 145 uploader := s3manager.NewUploader(awsSession) 146 147 file, err := os.Open(*options.file) 148 defer util.Close(file) 149 if err != nil { 150 return errors.WithStack(err) 151 } 152 153 uploadInput := s3manager.UploadInput{ 154 Bucket: options.bucket, 155 Key: options.key, 156 ContentType: aws.String(getMimeType(*options.key)), 157 Body: file, 158 } 159 if *options.acl != "" { 160 uploadInput.ACL = options.acl 161 } 162 if *options.storageClass != "" { 163 uploadInput.StorageClass = options.storageClass 164 } 165 if *options.encryption != "" { 166 uploadInput.ServerSideEncryption = options.encryption 167 } 168 169 _, err = uploader.UploadWithContext(publishContext, &uploadInput) 170 if err != nil { 171 return errors.WithStack(err) 172 } 173 174 return nil 175} 176 177func createHttpClient() *http.Client { 178 return &http.Client{ 179 Transport: &http.Transport{ 180 Proxy: util.ProxyFromEnvironmentAndNpm, 181 }, 182 } 183} 184 185func getMimeType(key string) string { 186 if strings.HasSuffix(key, ".AppImage") { 187 return "application/vnd.appimage" 188 } 189 if strings.HasSuffix(key, ".exe") { 190 return "application/octet-stream" 191 } 192 if strings.HasSuffix(key, ".zip") { 193 return "application/zip" 194 } 195 if strings.HasSuffix(key, ".blockmap") { 196 return "application/gzip" 197 } 198 if strings.HasSuffix(key, ".snap") { 199 return "application/vnd.snap" 200 } 201 if strings.HasSuffix(key, ".dmg") { 202 //noinspection SpellCheckingInspection 203 return "application/x-apple-diskimage" 204 } 205 206 ext := path.Ext(key) 207 if ext != "" { 208 mimeType := mime.TypeByExtension(ext) 209 if mimeType != "" { 210 return mimeType 211 } 212 } 213 return "application/octet-stream" 214} 215