github.com/fastly/cli@v1.7.2-0.20240304164155-9d0f1d77c3bf/pkg/commands/logging/s3/create.go (about) 1 package s3 2 3 import ( 4 "fmt" 5 "io" 6 7 "github.com/fastly/go-fastly/v9/fastly" 8 9 "github.com/fastly/cli/pkg/argparser" 10 "github.com/fastly/cli/pkg/commands/logging/common" 11 "github.com/fastly/cli/pkg/errors" 12 "github.com/fastly/cli/pkg/global" 13 "github.com/fastly/cli/pkg/manifest" 14 "github.com/fastly/cli/pkg/text" 15 ) 16 17 // CreateCommand calls the Fastly API to create an Amazon S3 logging endpoint. 18 type CreateCommand struct { 19 argparser.Base 20 Manifest manifest.Data 21 22 // Required. 23 ServiceName argparser.OptionalServiceNameID 24 ServiceVersion argparser.OptionalServiceVersion 25 26 // mutual exclusions 27 // AccessKey + SecretKey or IAMRole must be provided 28 AccessKey argparser.OptionalString 29 SecretKey argparser.OptionalString 30 IAMRole argparser.OptionalString 31 32 // Optional. 33 AutoClone argparser.OptionalAutoClone 34 BucketName argparser.OptionalString 35 CompressionCodec argparser.OptionalString 36 Domain argparser.OptionalString 37 EndpointName argparser.OptionalString // Can't shadow argparser.Base method Name(). 38 FileMaxBytes argparser.OptionalInt 39 Format argparser.OptionalString 40 FormatVersion argparser.OptionalInt 41 GzipLevel argparser.OptionalInt 42 MessageType argparser.OptionalString 43 Path argparser.OptionalString 44 Period argparser.OptionalInt 45 Placement argparser.OptionalString 46 PublicKey argparser.OptionalString 47 Redundancy argparser.OptionalString 48 ResponseCondition argparser.OptionalString 49 ServerSideEncryption argparser.OptionalString 50 ServerSideEncryptionKMSKeyID argparser.OptionalString 51 TimestampFormat argparser.OptionalString 52 } 53 54 // NewCreateCommand returns a usable command registered under the parent. 55 func NewCreateCommand(parent argparser.Registerer, g *global.Data) *CreateCommand { 56 c := CreateCommand{ 57 Base: argparser.Base{ 58 Globals: g, 59 }, 60 } 61 c.CmdClause = parent.Command("create", "Create an Amazon S3 logging endpoint on a Fastly service version").Alias("add") 62 63 // Required. 64 c.CmdClause.Flag("name", "The name of the S3 logging object. Used as a primary key for API access").Short('n').Action(c.EndpointName.Set).StringVar(&c.EndpointName.Value) 65 c.RegisterFlag(argparser.StringFlagOpts{ 66 Name: argparser.FlagVersionName, 67 Description: argparser.FlagVersionDesc, 68 Dst: &c.ServiceVersion.Value, 69 Required: true, 70 }) 71 72 // Optional. 73 c.RegisterAutoCloneFlag(argparser.AutoCloneFlagOpts{ 74 Action: c.AutoClone.Set, 75 Dst: &c.AutoClone.Value, 76 }) 77 c.CmdClause.Flag("access-key", "Your S3 account access key").Action(c.AccessKey.Set).StringVar(&c.AccessKey.Value) 78 c.CmdClause.Flag("bucket", "Your S3 bucket name").Action(c.BucketName.Set).StringVar(&c.BucketName.Value) 79 common.CompressionCodec(c.CmdClause, &c.CompressionCodec) 80 c.CmdClause.Flag("domain", "The domain of the S3 endpoint").Action(c.Domain.Set).StringVar(&c.Domain.Value) 81 c.CmdClause.Flag("file-max-bytes", "The maximum size of a log file in bytes").Action(c.FileMaxBytes.Set).IntVar(&c.FileMaxBytes.Value) 82 common.Format(c.CmdClause, &c.Format) 83 common.FormatVersion(c.CmdClause, &c.FormatVersion) 84 common.GzipLevel(c.CmdClause, &c.GzipLevel) 85 c.CmdClause.Flag("iam-role", "The IAM role ARN for logging").Action(c.IAMRole.Set).StringVar(&c.IAMRole.Value) 86 common.MessageType(c.CmdClause, &c.MessageType) 87 common.Path(c.CmdClause, &c.Path) 88 common.Period(c.CmdClause, &c.Period) 89 common.Placement(c.CmdClause, &c.Placement) 90 common.PublicKey(c.CmdClause, &c.PublicKey) 91 c.CmdClause.Flag("redundancy", "The S3 storage class. One of: standard, intelligent_tiering, standard_ia, onezone_ia, glacier, glacier_ir, deep_archive, or reduced_redundancy").Action(c.Redundancy.Set).EnumVar(&c.Redundancy.Value, string(fastly.S3RedundancyStandard), string(fastly.S3RedundancyIntelligentTiering), string(fastly.S3RedundancyStandardIA), string(fastly.S3RedundancyOneZoneIA), string(fastly.S3RedundancyGlacierFlexibleRetrieval), string(fastly.S3RedundancyGlacierInstantRetrieval), string(fastly.S3RedundancyGlacierDeepArchive), string(fastly.S3RedundancyReduced)) 92 common.ResponseCondition(c.CmdClause, &c.ResponseCondition) 93 c.CmdClause.Flag("secret-key", "Your S3 account secret key").Action(c.SecretKey.Set).StringVar(&c.SecretKey.Value) 94 c.CmdClause.Flag("server-side-encryption", "Set to enable S3 Server Side Encryption. Can be either AES256 or aws:kms").Action(c.ServerSideEncryption.Set).EnumVar(&c.ServerSideEncryption.Value, string(fastly.S3ServerSideEncryptionAES), string(fastly.S3ServerSideEncryptionKMS)) 95 c.CmdClause.Flag("server-side-encryption-kms-key-id", "Server-side KMS Key ID. Must be set if server-side-encryption is set to aws:kms").Action(c.ServerSideEncryptionKMSKeyID.Set).StringVar(&c.ServerSideEncryptionKMSKeyID.Value) 96 c.RegisterFlag(argparser.StringFlagOpts{ 97 Name: argparser.FlagServiceIDName, 98 Description: argparser.FlagServiceIDDesc, 99 Dst: &g.Manifest.Flag.ServiceID, 100 Short: 's', 101 }) 102 c.RegisterFlag(argparser.StringFlagOpts{ 103 Action: c.ServiceName.Set, 104 Name: argparser.FlagServiceName, 105 Description: argparser.FlagServiceDesc, 106 Dst: &c.ServiceName.Value, 107 }) 108 common.TimestampFormat(c.CmdClause, &c.TimestampFormat) 109 return &c 110 } 111 112 // ConstructInput transforms values parsed from CLI flags into an object to be used by the API client library. 113 func (c *CreateCommand) ConstructInput(serviceID string, serviceVersion int) (*fastly.CreateS3Input, error) { 114 var input fastly.CreateS3Input 115 116 input.ServiceID = serviceID 117 input.ServiceVersion = serviceVersion 118 if c.EndpointName.WasSet { 119 input.Name = &c.EndpointName.Value 120 } 121 if c.BucketName.WasSet { 122 input.BucketName = &c.BucketName.Value 123 } 124 125 // The following block checks for invalid permutations of the ways in 126 // which the AccessKey + SecretKey and IAMRole flags can be 127 // provided. This is necessary because either the AccessKey and 128 // SecretKey or the IAMRole is required, but they are mutually 129 // exclusive. The kingpin library lacks a way to express this constraint 130 // via the flag specification API so we enforce it manually here. 131 switch { 132 case !c.AccessKey.WasSet && !c.SecretKey.WasSet && !c.IAMRole.WasSet: 133 return nil, fmt.Errorf("error parsing arguments: the --access-key and --secret-key flags or the --iam-role flag must be provided") 134 case (c.AccessKey.WasSet || c.SecretKey.WasSet) && c.IAMRole.WasSet: 135 // Enforce mutual exclusion 136 return nil, fmt.Errorf("error parsing arguments: the --access-key and --secret-key flags are mutually exclusive with the --iam-role flag") 137 case c.AccessKey.WasSet && !c.SecretKey.WasSet: 138 return nil, fmt.Errorf("error parsing arguments: required flag --secret-key not provided") 139 case !c.AccessKey.WasSet && c.SecretKey.WasSet: 140 return nil, fmt.Errorf("error parsing arguments: required flag --access-key not provided") 141 } 142 143 // The following blocks enforces the mutual exclusivity of the 144 // CompressionCodec and GzipLevel flags. 145 if c.CompressionCodec.WasSet && c.GzipLevel.WasSet { 146 return nil, fmt.Errorf("error parsing arguments: the --compression-codec flag is mutually exclusive with the --gzip-level flag") 147 } 148 149 if c.AccessKey.WasSet { 150 input.AccessKey = &c.AccessKey.Value 151 } 152 153 if c.SecretKey.WasSet { 154 input.SecretKey = &c.SecretKey.Value 155 } 156 157 if c.IAMRole.WasSet { 158 input.IAMRole = &c.IAMRole.Value 159 } 160 161 if c.Domain.WasSet { 162 input.Domain = &c.Domain.Value 163 } 164 165 if c.FileMaxBytes.WasSet { 166 input.FileMaxBytes = &c.FileMaxBytes.Value 167 } 168 169 if c.Path.WasSet { 170 input.Path = &c.Path.Value 171 } 172 173 if c.Period.WasSet { 174 input.Period = &c.Period.Value 175 } 176 177 if c.GzipLevel.WasSet { 178 input.GzipLevel = &c.GzipLevel.Value 179 } 180 181 if c.Format.WasSet { 182 input.Format = &c.Format.Value 183 } 184 185 if c.FormatVersion.WasSet { 186 input.FormatVersion = &c.FormatVersion.Value 187 } 188 189 if c.MessageType.WasSet { 190 input.MessageType = &c.MessageType.Value 191 } 192 193 if c.ResponseCondition.WasSet { 194 input.ResponseCondition = &c.ResponseCondition.Value 195 } 196 197 if c.TimestampFormat.WasSet { 198 input.TimestampFormat = &c.TimestampFormat.Value 199 } 200 201 if c.Placement.WasSet { 202 input.Placement = &c.Placement.Value 203 } 204 205 if c.PublicKey.WasSet { 206 input.PublicKey = &c.PublicKey.Value 207 } 208 209 if c.ServerSideEncryptionKMSKeyID.WasSet { 210 input.ServerSideEncryptionKMSKeyID = &c.ServerSideEncryptionKMSKeyID.Value 211 } 212 213 if c.CompressionCodec.WasSet { 214 input.CompressionCodec = &c.CompressionCodec.Value 215 } 216 217 if c.Redundancy.WasSet { 218 redundancy, err := ValidateRedundancy(c.Redundancy.Value) 219 if err == nil { 220 input.Redundancy = &redundancy 221 } 222 } 223 224 if c.ServerSideEncryption.WasSet { 225 switch c.ServerSideEncryption.Value { 226 case string(fastly.S3ServerSideEncryptionAES): 227 sse := fastly.S3ServerSideEncryptionAES 228 input.ServerSideEncryption = &sse 229 case string(fastly.S3ServerSideEncryptionKMS): 230 sse := fastly.S3ServerSideEncryptionKMS 231 input.ServerSideEncryption = &sse 232 } 233 } 234 235 return &input, nil 236 } 237 238 // ValidateRedundancy identifies the given redundancy type. 239 func ValidateRedundancy(val string) (redundancy fastly.S3Redundancy, err error) { 240 switch val { 241 case string(fastly.S3RedundancyStandard): 242 redundancy = fastly.S3RedundancyStandard 243 case string(fastly.S3RedundancyIntelligentTiering): 244 redundancy = fastly.S3RedundancyIntelligentTiering 245 case string(fastly.S3RedundancyStandardIA): 246 redundancy = fastly.S3RedundancyStandardIA 247 case string(fastly.S3RedundancyOneZoneIA): 248 redundancy = fastly.S3RedundancyOneZoneIA 249 case string(fastly.S3RedundancyGlacierInstantRetrieval): 250 redundancy = fastly.S3RedundancyGlacierInstantRetrieval 251 case string(fastly.S3RedundancyGlacierFlexibleRetrieval): 252 redundancy = fastly.S3RedundancyGlacierFlexibleRetrieval 253 case string(fastly.S3RedundancyGlacierDeepArchive): 254 redundancy = fastly.S3RedundancyGlacierDeepArchive 255 case string(fastly.S3RedundancyReduced): 256 redundancy = fastly.S3RedundancyReduced 257 default: 258 err = fmt.Errorf("unknown redundancy: " + val) 259 } 260 return redundancy, err 261 } 262 263 // Exec invokes the application logic for the command. 264 func (c *CreateCommand) Exec(_ io.Reader, out io.Writer) error { 265 serviceID, serviceVersion, err := argparser.ServiceDetails(argparser.ServiceDetailsOpts{ 266 AutoCloneFlag: c.AutoClone, 267 APIClient: c.Globals.APIClient, 268 Manifest: *c.Globals.Manifest, 269 Out: out, 270 ServiceNameFlag: c.ServiceName, 271 ServiceVersionFlag: c.ServiceVersion, 272 VerboseMode: c.Globals.Flags.Verbose, 273 }) 274 if err != nil { 275 c.Globals.ErrLog.AddWithContext(err, map[string]any{ 276 "Service ID": serviceID, 277 "Service Version": errors.ServiceVersion(serviceVersion), 278 }) 279 return err 280 } 281 282 input, err := c.ConstructInput(serviceID, fastly.ToValue(serviceVersion.Number)) 283 if err != nil { 284 c.Globals.ErrLog.Add(err) 285 return err 286 } 287 288 d, err := c.Globals.APIClient.CreateS3(input) 289 if err != nil { 290 c.Globals.ErrLog.Add(err) 291 return err 292 } 293 294 text.Success(out, 295 "Created S3 logging endpoint %s (service %s version %d)", 296 fastly.ToValue(d.Name), 297 fastly.ToValue(d.ServiceID), 298 fastly.ToValue(d.ServiceVersion), 299 ) 300 return nil 301 }