github.com/aavshr/aws-sdk-go@v1.41.3/private/model/api/customization_passes.go (about) 1 //go:build codegen 2 // +build codegen 3 4 package api 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 ) 13 14 type service struct { 15 srcName string 16 dstName string 17 18 serviceVersion string 19 } 20 21 var mergeServices = map[string]service{ 22 "dynamodbstreams": { 23 dstName: "dynamodb", 24 srcName: "streams.dynamodb", 25 }, 26 "wafregional": { 27 dstName: "waf", 28 srcName: "waf-regional", 29 serviceVersion: "2015-08-24", 30 }, 31 } 32 33 var serviceAliaseNames = map[string]string{ 34 "costandusagereportservice": "CostandUsageReportService", 35 "elasticloadbalancing": "ELB", 36 "elasticloadbalancingv2": "ELBV2", 37 "config": "ConfigService", 38 } 39 40 func (a *API) setServiceAliaseName() { 41 if newName, ok := serviceAliaseNames[a.PackageName()]; ok { 42 a.name = newName 43 } 44 } 45 46 // customizationPasses Executes customization logic for the API by package name. 47 func (a *API) customizationPasses() error { 48 var svcCustomizations = map[string]func(*API) error{ 49 "s3": s3Customizations, 50 "s3control": s3ControlCustomizations, 51 "cloudfront": cloudfrontCustomizations, 52 "rds": rdsCustomizations, 53 "neptune": neptuneCustomizations, 54 "docdb": docdbCustomizations, 55 56 // Disable endpoint resolving for services that require customer 57 // to provide endpoint them selves. 58 "cloudsearchdomain": disableEndpointResolving, 59 "iotdataplane": disableEndpointResolving, 60 61 // MTurk smoke test is invalid. The service requires AWS account to be 62 // linked to Amazon Mechanical Turk Account. 63 "mturk": supressSmokeTest, 64 65 // Backfill the authentication type for cognito identity and sts. 66 // Removes the need for the customizations in these services. 67 "cognitoidentity": backfillAuthType(NoneAuthType, 68 "GetId", 69 "GetOpenIdToken", 70 "UnlinkIdentity", 71 "GetCredentialsForIdentity", 72 ), 73 "sts": backfillAuthType(NoneAuthType, 74 "AssumeRoleWithSAML", 75 "AssumeRoleWithWebIdentity", 76 ), 77 } 78 79 for k := range mergeServices { 80 svcCustomizations[k] = mergeServicesCustomizations 81 } 82 83 if fn := svcCustomizations[a.PackageName()]; fn != nil { 84 err := fn(a) 85 if err != nil { 86 return fmt.Errorf("service customization pass failure for %s: %v", a.PackageName(), err) 87 } 88 } 89 90 return nil 91 } 92 93 func supressSmokeTest(a *API) error { 94 a.SmokeTests.TestCases = []SmokeTestCase{} 95 return nil 96 } 97 98 // Customizes the API generation to replace values specific to S3. 99 func s3Customizations(a *API) error { 100 101 // back-fill signing name as 's3' 102 a.Metadata.SigningName = "s3" 103 104 var strExpires *Shape 105 106 var keepContentMD5Ref = map[string]struct{}{ 107 "PutObjectInput": {}, 108 "UploadPartInput": {}, 109 } 110 111 for name, s := range a.Shapes { 112 // Remove ContentMD5 members unless specified otherwise. 113 if _, keep := keepContentMD5Ref[name]; !keep { 114 if _, have := s.MemberRefs["ContentMD5"]; have { 115 delete(s.MemberRefs, "ContentMD5") 116 } 117 } 118 119 // Generate getter methods for API operation fields used by customizations. 120 for _, refName := range []string{"Bucket", "SSECustomerKey", "CopySourceSSECustomerKey"} { 121 if ref, ok := s.MemberRefs[refName]; ok { 122 ref.GenerateGetter = true 123 } 124 } 125 126 // Generate a endpointARN method for the BucketName shape if this is used as an operation input 127 if s.UsedAsInput { 128 if s.ShapeName == "CreateBucketInput" { 129 // For all operations but CreateBucket the BucketName shape 130 // needs to be decorated. 131 continue 132 } 133 var endpointARNShape *ShapeRef 134 for _, ref := range s.MemberRefs { 135 if ref.OrigShapeName != "BucketName" || ref.Shape.Type != "string" { 136 continue 137 } 138 if endpointARNShape != nil { 139 return fmt.Errorf("more then one BucketName shape present on shape") 140 } 141 ref.EndpointARN = true 142 endpointARNShape = ref 143 } 144 if endpointARNShape != nil { 145 s.HasEndpointARNMember = true 146 a.HasEndpointARN = true 147 } 148 } 149 150 // Decorate member references that are modeled with the wrong type. 151 // Specifically the case where a member was modeled as a string, but is 152 // expected to sent across the wire as a base64 value. 153 // 154 // e.g. S3's SSECustomerKey and CopySourceSSECustomerKey 155 for _, refName := range []string{ 156 "SSECustomerKey", 157 "CopySourceSSECustomerKey", 158 } { 159 if ref, ok := s.MemberRefs[refName]; ok { 160 ref.CustomTags = append(ref.CustomTags, ShapeTag{ 161 "marshal-as", "blob", 162 }) 163 } 164 } 165 166 // Expires should be a string not time.Time since the format is not 167 // enforced by S3, and any value can be set to this field outside of the SDK. 168 if strings.HasSuffix(name, "Output") { 169 if ref, ok := s.MemberRefs["Expires"]; ok { 170 if strExpires == nil { 171 newShape := *ref.Shape 172 strExpires = &newShape 173 strExpires.Type = "string" 174 strExpires.refs = []*ShapeRef{} 175 } 176 ref.Shape.removeRef(ref) 177 ref.Shape = strExpires 178 ref.Shape.refs = append(ref.Shape.refs, &s.MemberRef) 179 } 180 } 181 } 182 s3CustRemoveHeadObjectModeledErrors(a) 183 184 return nil 185 } 186 187 // S3 HeadObject API call incorrect models NoSuchKey as valid 188 // error code that can be returned. This operation does not 189 // return error codes, all error codes are derived from HTTP 190 // status codes. 191 // 192 // aws/aws-sdk-go#1208 193 func s3CustRemoveHeadObjectModeledErrors(a *API) { 194 op, ok := a.Operations["HeadObject"] 195 if !ok { 196 return 197 } 198 op.Documentation += ` 199 // 200 // See http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#RESTErrorResponses 201 // for more information on returned errors.` 202 op.ErrorRefs = []ShapeRef{} 203 } 204 205 // S3 service operations with an AccountId need accessors to be generated for 206 // them so the fields can be dynamically accessed without reflection. 207 func s3ControlCustomizations(a *API) error { 208 for _, s := range a.Shapes { 209 // Generate a endpointARN method for the BucketName shape if this is used as an operation input 210 if s.UsedAsInput { 211 if s.ShapeName == "CreateBucketInput" || s.ShapeName == "ListRegionalBucketsInput" { 212 // For operations CreateBucketInput and ListRegionalBuckets the OutpostID shape 213 // needs to be decorated 214 var outpostIDMemberShape *ShapeRef 215 for memberName, ref := range s.MemberRefs { 216 if memberName != "OutpostId" || ref.Shape.Type != "string" { 217 continue 218 } 219 if outpostIDMemberShape != nil { 220 return fmt.Errorf("more then one OutpostID shape present on shape") 221 } 222 ref.OutpostIDMember = true 223 outpostIDMemberShape = ref 224 } 225 if outpostIDMemberShape != nil { 226 s.HasOutpostIDMember = true 227 a.HasOutpostID = true 228 } 229 continue 230 } 231 232 // List of input shapes that use accesspoint names as arnable fields 233 accessPointNameArnables := map[string]struct{}{ 234 "GetAccessPointInput": {}, 235 "DeleteAccessPointInput": {}, 236 "PutAccessPointPolicyInput": {}, 237 "GetAccessPointPolicyInput": {}, 238 "DeleteAccessPointPolicyInput": {}, 239 } 240 241 var endpointARNShape *ShapeRef 242 for _, ref := range s.MemberRefs { 243 // Operations that have AccessPointName field that takes in an ARN as input 244 if _, ok := accessPointNameArnables[s.ShapeName]; ok { 245 if ref.OrigShapeName != "AccessPointName" || ref.Shape.Type != "string" { 246 continue 247 } 248 } else if ref.OrigShapeName != "BucketName" || ref.Shape.Type != "string" { 249 // All other operations currently allow BucketName field to take in ARN. 250 // Exceptions for these are CreateBucket and ListRegionalBucket which use 251 // Outpost id and are handled above separately. 252 continue 253 } 254 255 if endpointARNShape != nil { 256 return fmt.Errorf("more then one member present on shape takes arn as input") 257 } 258 ref.EndpointARN = true 259 endpointARNShape = ref 260 } 261 if endpointARNShape != nil { 262 s.HasEndpointARNMember = true 263 a.HasEndpointARN = true 264 265 for _, ref := range s.MemberRefs { 266 // check for account id customization 267 if ref.OrigShapeName == "AccountId" && ref.Shape.Type == "string" { 268 ref.AccountIDMemberWithARN = true 269 s.HasAccountIdMemberWithARN = true 270 a.HasAccountIdWithARN = true 271 } 272 } 273 } 274 } 275 } 276 277 return nil 278 } 279 280 // cloudfrontCustomizations customized the API generation to replace values 281 // specific to CloudFront. 282 func cloudfrontCustomizations(a *API) error { 283 // MaxItems members should always be integers 284 for _, s := range a.Shapes { 285 if ref, ok := s.MemberRefs["MaxItems"]; ok { 286 ref.ShapeName = "Integer" 287 ref.Shape = a.Shapes["Integer"] 288 } 289 } 290 return nil 291 } 292 293 // mergeServicesCustomizations references any duplicate shapes from DynamoDB 294 func mergeServicesCustomizations(a *API) error { 295 info := mergeServices[a.PackageName()] 296 297 p := strings.Replace(a.path, info.srcName, info.dstName, -1) 298 299 if info.serviceVersion != "" { 300 index := strings.LastIndex(p, string(filepath.Separator)) 301 files, _ := ioutil.ReadDir(p[:index]) 302 if len(files) > 1 { 303 panic("New version was introduced") 304 } 305 p = p[:index] + "/" + info.serviceVersion 306 } 307 308 file := filepath.Join(p, "api-2.json") 309 310 serviceAPI := API{} 311 serviceAPI.Attach(file) 312 serviceAPI.Setup() 313 314 for n := range a.Shapes { 315 if _, ok := serviceAPI.Shapes[n]; ok { 316 a.Shapes[n].resolvePkg = SDKImportRoot + "/service/" + info.dstName 317 } 318 } 319 320 return nil 321 } 322 323 // rdsCustomizations are customization for the service/rds. This adds 324 // non-modeled fields used for presigning. 325 func rdsCustomizations(a *API) error { 326 inputs := []string{ 327 "CopyDBSnapshotInput", 328 "CreateDBInstanceReadReplicaInput", 329 "CopyDBClusterSnapshotInput", 330 "CreateDBClusterInput", 331 "StartDBInstanceAutomatedBackupsReplicationInput", 332 } 333 generatePresignedURL(a, inputs) 334 return nil 335 } 336 337 // neptuneCustomizations are customization for the service/neptune. This adds 338 // non-modeled fields used for presigning. 339 func neptuneCustomizations(a *API) error { 340 inputs := []string{ 341 "CopyDBClusterSnapshotInput", 342 "CreateDBClusterInput", 343 } 344 generatePresignedURL(a, inputs) 345 return nil 346 } 347 348 // neptuneCustomizations are customization for the service/neptune. This adds 349 // non-modeled fields used for presigning. 350 func docdbCustomizations(a *API) error { 351 inputs := []string{ 352 "CopyDBClusterSnapshotInput", 353 "CreateDBClusterInput", 354 } 355 generatePresignedURL(a, inputs) 356 return nil 357 } 358 359 func generatePresignedURL(a *API, inputShapes []string) { 360 for _, input := range inputShapes { 361 if ref, ok := a.Shapes[input]; ok { 362 ref.MemberRefs["SourceRegion"] = &ShapeRef{ 363 Documentation: docstring(`SourceRegion is the source region where the resource exists. This is not sent over the wire and is only used for presigning. This value should always have the same region as the source ARN.`), 364 ShapeName: "String", 365 Shape: a.Shapes["String"], 366 Ignore: true, 367 } 368 ref.MemberRefs["DestinationRegion"] = &ShapeRef{ 369 Documentation: docstring(`DestinationRegion is used for presigning the request to a given region.`), 370 ShapeName: "String", 371 Shape: a.Shapes["String"], 372 } 373 } 374 } 375 } 376 377 func disableEndpointResolving(a *API) error { 378 a.Metadata.NoResolveEndpoint = true 379 return nil 380 } 381 382 func backfillAuthType(typ AuthType, opNames ...string) func(*API) error { 383 return func(a *API) error { 384 for _, opName := range opNames { 385 op, ok := a.Operations[opName] 386 if !ok { 387 panic("unable to backfill auth-type for unknown operation " + opName) 388 } 389 if v := op.AuthType; len(v) != 0 { 390 fmt.Fprintf(os.Stderr, "unable to backfill auth-type for %s, already set, %s\n", opName, v) 391 continue 392 } 393 394 op.AuthType = typ 395 } 396 397 return nil 398 } 399 }