diff --git a/packages/@aws-cdk/sdk-v2-to-v3-adapter/lib/parameter-types.ts b/packages/@aws-cdk/sdk-v2-to-v3-adapter/lib/parameter-types.ts new file mode 100644 index 0000000000000..caf992274d837 --- /dev/null +++ b/packages/@aws-cdk/sdk-v2-to-v3-adapter/lib/parameter-types.ts @@ -0,0 +1,562 @@ +// This file was generated from the aws-sdk-js-v3 at Thu Sep 07 2023 15:56:12 GMT+0100 (British Summer Time) +/* eslint-disable quote-props,comma-dangle */ +export interface TypeCoercionMap { + [service: string]: { + [action: string]: string[] + } +}; +export const UINT8ARRAY_PARAMETERS: TypeCoercionMap = { + 'acm': { + 'exportcertificate': [ + 'Passphrase' + ], + 'importcertificate': [ + 'Certificate', + 'CertificateChain', + 'PrivateKey' + ] + }, + 'acm-pca': { + 'importcertificateauthoritycertificate': [ + 'Certificate', + 'CertificateChain' + ], + 'issuecertificate': [ + 'Csr' + ] + }, + 'apigateway': { + 'importapikeys': [ + 'body' + ], + 'importdocumentationparts': [ + 'body' + ], + 'importrestapi': [ + 'body' + ], + 'putrestapi': [ + 'body' + ], + 'posttoconnection': [ + 'Data' + ] + }, + 'appconfig': { + 'createhostedconfigurationversion': [ + 'Content' + ] + }, + 'appsync': { + 'startschemacreation': [ + 'definition' + ] + }, + 'awsmobilehubservice': { + 'createproject': [ + 'contents' + ], + 'updateproject': [ + 'contents' + ] + }, + 'backup-storage': { + 'notifyobjectcomplete': [ + 'MetadataBlob' + ], + 'putchunk': [ + 'Data' + ], + 'putobject': [ + 'InlineChunk' + ] + }, + 'cloudfront': { + 'createfunction': [ + 'FunctionCode' + ], + 'testfunction': [ + 'EventObject' + ], + 'updatefunction': [ + 'FunctionCode' + ] + }, + 'cloudsearch': { + 'uploaddocuments': [ + 'documents' + ] + }, + 'codeartifact': { + 'publishpackageversion': [ + 'assetContent' + ] + }, + 'codecommit': { + 'createcommit': [ + 'putFiles.*.fileContent' + ], + 'createunreferencedmergecommit': [ + 'conflictResolution.replaceContents.*.content' + ], + 'mergebranchesbysquash': [ + 'conflictResolution.replaceContents.*.content' + ], + 'mergebranchesbythreeway': [ + 'conflictResolution.replaceContents.*.content' + ], + 'mergepullrequestbysquash': [ + 'conflictResolution.replaceContents.*.content' + ], + 'mergepullrequestbythreeway': [ + 'conflictResolution.replaceContents.*.content' + ], + 'putfile': [ + 'fileContent' + ] + }, + 'cognito-idp': { + 'setuicustomization': [ + 'ImageFile' + ] + }, + 'comprehend': { + 'classifydocument': [ + 'Bytes' + ], + 'detectentities': [ + 'Bytes' + ] + }, + 'datasync': { + 'createlocationhdfs': [ + 'KerberosKeytab', + 'KerberosKrb5Conf' + ], + 'createlocationobjectstorage': [ + 'ServerCertificate' + ], + 'updatelocationhdfs': [ + 'KerberosKeytab', + 'KerberosKrb5Conf' + ], + 'updatelocationobjectstorage': [ + 'ServerCertificate' + ] + }, + 'dms': { + 'importcertificate': [ + 'CertificateWallet' + ] + }, + 'ebs': { + 'putsnapshotblock': [ + 'BlockData' + ] + }, + 'ec2': { + 'bundleinstance': [ + 'Storage.S3.UploadPolicy' + ], + 'importkeypair': [ + 'PublicKeyMaterial' + ], + 'modifyinstanceattribute': [ + 'UserData.Value' + ] + }, + 'ecr': { + 'uploadlayerpart': [ + 'layerPartBlob' + ] + }, + 'ecr-public': { + 'createrepository': [ + 'catalogData.logoImageBlob' + ], + 'putrepositorycatalogdata': [ + 'catalogData.logoImageBlob' + ], + 'uploadlayerpart': [ + 'layerPartBlob' + ] + }, + 'firehose': { + 'putrecord': [ + 'Record.Data' + ], + 'putrecordbatch': [ + 'Records.*.Data' + ] + }, + 'gamelift': { + 'createscript': [ + 'ZipFile' + ], + 'updatescript': [ + 'ZipFile' + ] + }, + 'gamesparks': { + 'importgameconfiguration': [ + 'ImportSource.File' + ] + }, + 'glacier': { + 'uploadarchive': [ + 'body' + ], + 'uploadmultipartpart': [ + 'body' + ] + }, + 'glue': { + 'updatecolumnstatisticsforpartition': [ + 'ColumnStatisticsList.*.StatisticsData.DecimalColumnStatisticsData.MaximumValue.UnscaledValue' + ], + 'updatecolumnstatisticsfortable': [ + 'ColumnStatisticsList.*.StatisticsData.DecimalColumnStatisticsData.MaximumValue.UnscaledValue' + ] + }, + 'greengrass': { + 'createcomponentversion': [ + 'inlineRecipe' + ] + }, + 'iot': { + 'createotaupdate': [ + 'files.*.codeSigning.customCodeSigning.signature.inlineDocument' + ], + 'testinvokeauthorizer': [ + 'mqttContext.password' + ] + }, + 'iotanalytics': { + 'batchputmessage': [ + 'messages.*.payload' + ], + 'runpipelineactivity': [ + 'payloads.*' + ] + }, + 'iotdata': { + 'publish': [ + 'payload' + ], + 'updatethingshadow': [ + 'payload' + ] + }, + 'ioteventsdata': { + 'batchputmessage': [ + 'messages.*.payload' + ] + }, + 'iotsitewise': { + 'createportal': [ + 'portalLogoImageFile.data' + ], + 'updateportal': [ + 'portalLogoImage.file.data' + ] + }, + 'iotwireless': { + 'updateresourceposition': [ + 'GeoJsonPayload' + ] + }, + 'kafka': { + 'createconfiguration': [ + 'ServerProperties' + ], + 'updateconfiguration': [ + 'ServerProperties' + ] + }, + 'kendra': { + 'batchputdocument': [ + 'Documents.*.Blob' + ] + }, + 'kinesis': { + 'putrecord': [ + 'Data' + ], + 'putrecords': [ + 'Records.*.Data' + ] + }, + 'kinesisanalytics': { + 'createapplication': [ + 'ApplicationConfiguration.ApplicationCodeConfiguration.CodeContent.ZipFileContent' + ], + 'updateapplication': [ + 'ApplicationConfigurationUpdate.ApplicationCodeConfigurationUpdate.CodeContentUpdate.ZipFileContentUpdate' + ] + }, + 'kms': { + 'decrypt': [ + 'CiphertextBlob', + 'Recipient.AttestationDocument' + ], + 'encrypt': [ + 'Plaintext' + ], + 'generatedatakey': [ + 'Recipient.AttestationDocument' + ], + 'generatedatakeypair': [ + 'Recipient.AttestationDocument' + ], + 'generatemac': [ + 'Message' + ], + 'generaterandom': [ + 'Recipient.AttestationDocument' + ], + 'importkeymaterial': [ + 'EncryptedKeyMaterial' + ], + 'reencrypt': [ + 'CiphertextBlob' + ], + 'sign': [ + 'Message' + ], + 'verify': [ + 'Message', + 'Signature' + ], + 'verifymac': [ + 'Mac', + 'Message' + ] + }, + 'lambda': { + 'createfunction': [ + 'Code.ZipFile' + ], + 'invoke': [ + 'Payload' + ], + 'invokeasync': [ + 'InvokeArgs' + ], + 'invokewithresponsestream': [ + 'Payload' + ], + 'publishlayerversion': [ + 'Content.ZipFile' + ], + 'updatefunctioncode': [ + 'ZipFile' + ] + }, + 'lex': { + 'startimport': [ + 'payload' + ], + 'postcontent': [ + 'inputStream' + ], + 'recognizeutterance': [ + 'inputStream' + ] + }, + 'lookoutvision': { + 'detectanomalies': [ + 'Body' + ], + 'updatedatasetentries': [ + 'Changes' + ] + }, + 'mediastore': { + 'putobject': [ + 'Body' + ] + }, + 'mobiletargeting': { + 'sendmessages': [ + 'MessageRequest.MessageConfiguration.EmailMessage.RawEmail.Data' + ], + 'sendusersmessages': [ + 'SendUsersMessageRequest.MessageConfiguration.EmailMessage.RawEmail.Data' + ] + }, + 'qldb': { + 'sendcommand': [ + 'CommitTransaction.CommitDigest', + 'ExecuteStatement.Parameters.*.IonBinary' + ] + }, + 'quicksight': { + 'startassetbundleimportjob': [ + 'AssetBundleImportSource.Body' + ] + }, + 'rekognition': { + 'comparefaces': [ + 'SourceImage.Bytes' + ], + 'detectcustomlabels': [ + 'Image.Bytes' + ], + 'detectfaces': [ + 'Image.Bytes' + ], + 'detectlabels': [ + 'Image.Bytes' + ], + 'detectmoderationlabels': [ + 'Image.Bytes' + ], + 'detectprotectiveequipment': [ + 'Image.Bytes' + ], + 'detecttext': [ + 'Image.Bytes' + ], + 'indexfaces': [ + 'Image.Bytes' + ], + 'recognizecelebrities': [ + 'Image.Bytes' + ], + 'searchfacesbyimage': [ + 'Image.Bytes' + ], + 'searchusersbyimage': [ + 'Image.Bytes' + ], + 'updatedatasetentries': [ + 'Changes.GroundTruth' + ] + }, + 's3': { + 'putobject': [ + 'Body' + ], + 'uploadpart': [ + 'Body' + ], + 'writegetobjectresponse': [ + 'Body' + ] + }, + 'sagemaker': { + 'invokeendpoint': [ + 'Body' + ], + 'invokeendpointwithresponsestream': [ + 'Body' + ] + }, + 'secretsmanager': { + 'createsecret': [ + 'SecretBinary' + ], + 'putsecretvalue': [ + 'SecretBinary' + ], + 'updatesecret': [ + 'SecretBinary' + ] + }, + 'ses': { + 'createdeliverabilitytestreport': [ + 'Content.Raw.Data', + 'Content.Raw.Data' + ], + 'sendemail': [ + 'Content.Raw.Data', + 'Content.Raw.Data' + ], + 'sendrawemail': [ + 'RawMessage.Data' + ] + }, + 'signer': { + 'signpayload': [ + 'payload' + ] + }, + 'ssm': { + 'registertaskwithmaintenancewindow': [ + 'TaskInvocationParameters.Lambda.Payload' + ], + 'updatemaintenancewindowtask': [ + 'TaskInvocationParameters.Lambda.Payload' + ] + }, + 'support': { + 'addattachmentstoset': [ + 'attachments.*.data' + ] + }, + 'synthetics': { + 'createcanary': [ + 'Code.ZipFile' + ], + 'updatecanary': [ + 'Code.ZipFile' + ] + }, + 'textract': { + 'analyzedocument': [ + 'Document.Bytes' + ], + 'analyzeexpense': [ + 'Document.Bytes' + ], + 'analyzeid': [ + 'DocumentPages.*.Bytes' + ], + 'detectdocumenttext': [ + 'Document.Bytes' + ] + }, + 'translate': { + 'importterminology': [ + 'TerminologyData.File' + ], + 'translatedocument': [ + 'Document.Content' + ] + }, + 'waf': { + 'updatebytematchset': [ + 'Updates.*.ByteMatchTuple.TargetString' + ] + }, + 'waf-regional': { + 'updatebytematchset': [ + 'Updates.*.ByteMatchTuple.TargetString' + ] + }, + 'wafv2': { + 'checkcapacity': [ + 'Rules.*.Statement.ByteMatchStatement.SearchString' + ], + 'createrulegroup': [ + 'Rules.*.Statement.ByteMatchStatement.SearchString' + ], + 'createwebacl': [ + 'Rules.*.Statement.ByteMatchStatement.SearchString' + ], + 'updaterulegroup': [ + 'Rules.*.Statement.ByteMatchStatement.SearchString' + ], + 'updatewebacl': [ + 'Rules.*.Statement.ByteMatchStatement.SearchString' + ] + }, + 'workspaces': { + 'importclientbranding': [ + 'DeviceTypeAndroid.Logo', + 'DeviceTypeIos.Logo', + 'DeviceTypeIos.Logo2x', + 'DeviceTypeIos.Logo3x' + ] + } +}; diff --git a/scripts/update-sdkv3-parameters-model.sh b/scripts/update-sdkv3-parameters-model.sh new file mode 100755 index 0000000000000..47d2d60769cfc --- /dev/null +++ b/scripts/update-sdkv3-parameters-model.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -eu +scriptdir=$(cd $(dirname $0) && pwd) +tmpdir=$(mktemp -d) +zip=https://github.com/aws/aws-sdk-js-v3/archive/refs/heads/main.zip +ziprootentry=aws-sdk-js-v3-main +target_file=packages/@aws-cdk/sdk-v2-to-v3-adapter/lib/parameter-types.ts + +# Use the GitHub feature to download a zip archive of the current state of a branch +# (Cloning --depth 1 takes about twice as long, cloning with full depth many minutes) +echo "⬇️ Downloading..." +curl -SfL -o $tmpdir/main.zip "$zip" +(cd $tmpdir && unzip -q main.zip) + +echo "🖨️ Generating..." +npx ts-node $scriptdir/update-sdkv3-parameters-model.ts $tmpdir/$ziprootentry > "$target_file" + +echo "🚮 Cleaning up..." +rm -rf $tmpdir + +echo "✅ Done." diff --git a/scripts/update-sdkv3-parameters-model.ts b/scripts/update-sdkv3-parameters-model.ts new file mode 100644 index 0000000000000..9ac7cc85f2f34 --- /dev/null +++ b/scripts/update-sdkv3-parameters-model.ts @@ -0,0 +1,180 @@ +/** + * From the SDKv3 repository, build an index of all types that are are "Blob"s, + * so we can properly convert input strings into `Uint8Arrays` in Custom Resources + * that call the SDKv3 in an untyped way. + */ +import * as path from 'path'; +import { promises as fs } from 'fs'; + +/** + * For every Smithy .json file in a directory, extract relevant types into a mapping and render them to a TypeScript file + */ +async function main(argv: string[]) { + if (!argv[0]) { + throw new Error('Usage: update-sdkv3-parameters-model '); + } + const root = argv[0]; + + const blobMapping: TypeCoercionMap = {}; + + const dir = path.join(root, 'codegen', 'sdk-codegen', 'aws-models'); + for (const entry of await fs.readdir(dir, { withFileTypes: true, encoding: 'utf-8' })) { + if (entry.isFile() && entry.name.endsWith('.json')) { + const contents = JSON.parse(await fs.readFile(path.join(dir, entry.name), { encoding: 'utf-8' })); + if (contents.smithy !== '2.0') { + console.error(`Skipping ${entry.name}`); + continue; + } + try { + await doFile(blobMapping, contents); + } catch (e) { + throw new Error(`Error handling ${entry.name}: ${e}`); + } + } + } + + if (Object.entries(blobMapping).length === 0) { + throw new Error('No mappings found!'); + } + + // Sort the map so we're independent of the order the OS gave us the files in, or what the filenames are + const sortedMapping = Object.fromEntries(Object.entries(blobMapping).sort(sortByKey)); + + renderMappingToTypeScript(sortedMapping); +} + +/** + * Recurse through all the types of a singly Smithy model, and record the blobs + */ +async function doFile(blobMap: TypeCoercionMap, model: SmithyFile) { + const shapes = model.shapes; + + const service = Object.values(shapes).find(isShape('service')); + if (!service) { + throw new Error('Did not find service'); + } + const _shortName = (service.traits?.['aws.api#service']?.arnNamespace + ?? service.traits?.['aws.api#service']?.endpointPrefix + ?? service.traits?.['aws.auth#sigv4']?.name); + if (!_shortName) { + throw new Error('Service does not have shortname'); + } + const shortName = _shortName; + + // Sort operations so we have a stable order to minimize future diffs + const operations = service.operations ?? []; + operations.sort((a, b) => a.target.localeCompare(b.target)); + + for (const operationTarget of operations) { + const operation = shapes[operationTarget.target]; + if (!isShape('operation')(operation)) { + throw new Error(`Not an operation: ${operationTarget.target}`); + } + if (operation.input) { + const [, opName] = operationTarget.target.split('#'); + recurse(operation.input.target, opName.toLowerCase(), [], []); + } + } + + /** + * Recurse through type shapes, finding the blobs + */ + function recurse(id: string, opName: string, memberPath: string[], seen: string[]) { + if (id.startsWith('smithy.api#') || seen.includes(id)) { + return; + } + seen.push(id); + const shape = shapes[id]; + + if (isShape('blob')(shape)) { + addToBlobs(opName, memberPath); + return; + } + if (isShape('structure')(shape)) { + for (const [field, member] of Object.entries(shape.members ?? {}).sort(sortByKey)) { + recurse(member.target, opName, [...memberPath, field], seen); + } + return; + } + if (isShape('list')(shape)) { + recurse(shape.member.target, opName, [...memberPath, '*'], seen); + return; + } + } + + function addToBlobs(opName: string, memberPath: string[]) { + if (!blobMap[shortName]) { + blobMap[shortName] = {}; + } + if (!blobMap[shortName][opName]) { + blobMap[shortName][opName] = []; + } + blobMap[shortName][opName].push(memberPath.join('.')); + } +} + +interface TypeCoercionMap { + [service: string]: { + [action: string]: string[] + } +}; + +interface SmithyFile { + shapes: Record; +} + +type SmithyShape = + | { type: 'service', operations?: SmithyTarget[], traits?: SmithyTraits } + | { type: 'operation', input?: SmithyTarget, output: SmithyTarget, traits?: SmithyTraits } + | { type: 'structure', members?: Record, traits?: SmithyTraits } + | { type: 'list', member: SmithyTarget } + | { type: string } + ; + +interface SmithyTarget { target: string }; + +interface SmithyTraits { + 'aws.api#service'?: { + sdkId?: string; + arnNamespace?: string; + cloudFormationName?: string; + endpointPrefix?: string; + }; + 'aws.auth#sigv4'?: { + name: string; + }; +} + +function isShape(key: A) { + return (x: SmithyShape): x is Extract => x.type === key; +} + +function sortByKey(e1: [string, A], e2: [string, A]) { + return e1[0].localeCompare(e2[0]); +} + +/** + * Render the given mapping to a TypeScript source file + */ +function renderMappingToTypeScript(blobMap: TypeCoercionMap) { + const lines = new Array(); + + lines.push( + `// This file was generated from the aws-sdk-js-v3 at ${new Date()}`, + '/* eslint-disable quote-props,comma-dangle */', + 'export interface TypeCoercionMap {', + ' [service: string]: {', + ' [action: string]: string[]', + ' }', + '};' + ); + + lines.push('export const UINT8ARRAY_PARAMETERS: TypeCoercionMap = ' + JSON.stringify(blobMap, undefined, 2).replace(/"/g, '\'') + ';'); + + console.log(lines.join('\n')); +} + +main(process.argv.slice(2)).catch((e) => { + console.error(e); + process.exitCode = 1; +});