diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts index 864afcf88bd7c..5b055190508b4 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts @@ -17,7 +17,6 @@ import { CacheBehavior } from './private/cache-behavior'; import { formatDistributionArn, grant } from './private/utils'; import * as acm from '../../aws-certificatemanager'; import * as cloudwatch from '../../aws-cloudwatch'; -import * as elasticloadbalancingv2 from '../../aws-elasticloadbalancingv2'; import * as iam from '../../aws-iam'; import * as lambda from '../../aws-lambda'; import * as s3 from '../../aws-s3'; @@ -717,9 +716,7 @@ export class Distribution extends Resource implements IDistribution { const generatedId = Names.uniqueId(scope).slice(-ORIGIN_ID_MAX_LENGTH); const distributionId = this.distributionId; const originBindConfig = origin.bind(scope, { originId: generatedId, distributionId: Lazy.string({ produce: () => this.distributionId }) }); - const originId = (originBindConfig.originProperty?.id as elasticloadbalancingv2.ILoadBalancerRef)?.loadBalancerRef?.loadBalancerArn - ?? originBindConfig.originProperty?.id - ?? generatedId; + const originId = originBindConfig.originProperty?.id ?? generatedId; const duplicateId = this.boundOrigins.find(boundOrigin => boundOrigin.originProperty?.id === originBindConfig.originProperty?.id); if (duplicateId) { throw new ValidationError(`Origin with id ${duplicateId.originProperty?.id} already exists. OriginIds must be unique within a distribution`, this); @@ -744,8 +741,7 @@ export class Distribution extends Resource implements IDistribution { ); return originGroupId; } - return (originBindConfig.originProperty?.id as elasticloadbalancingv2.ILoadBalancerRef)?.loadBalancerRef?.loadBalancerArn - ?? originBindConfig.originProperty?.id ?? originId; + return originBindConfig.originProperty?.id ?? originId; } } diff --git a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts index fecba8bc1f306..7bda54b36cbc4 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts @@ -5046,27 +5046,19 @@ describe('Lambda Function log group behavior', () => { }); describe('telemetry metadata', () => { - let getPrototypeOfSpy: jest.SpyInstance; + it('redaction happens when feature flag is enabled', () => { + const app = new cdk.App(); + app.node.setContext(cxapi.ENABLE_ADDITIONAL_METADATA_COLLECTION, true); + const stack = new cdk.Stack(app); - beforeEach(() => { const mockConstructor = { [JSII_RUNTIME_SYMBOL]: { fqn: 'aws-cdk-lib.aws-lambda.Function', }, }; - getPrototypeOfSpy = jest.spyOn(Object, 'getPrototypeOf').mockReturnValue({ + jest.spyOn(Object, 'getPrototypeOf').mockReturnValue({ constructor: mockConstructor, }); - }); - - afterEach(() => { - getPrototypeOfSpy.mockRestore(); - }); - - it('redaction happens when feature flag is enabled', () => { - const app = new cdk.App(); - app.node.setContext(cxapi.ENABLE_ADDITIONAL_METADATA_COLLECTION, true); - const stack = new cdk.Stack(app); const fn = new lambda.Function(stack, 'Lambda', { code: lambda.Code.fromInline('foo'), @@ -5097,6 +5089,15 @@ describe('telemetry metadata', () => { app.node.setContext(cxapi.ENABLE_ADDITIONAL_METADATA_COLLECTION, false); const stack = new cdk.Stack(app); + const mockConstructor = { + [JSII_RUNTIME_SYMBOL]: { + fqn: 'aws-cdk-lib.aws-lambda.Function', + }, + }; + jest.spyOn(Object, 'getPrototypeOf').mockReturnValue({ + constructor: mockConstructor, + }); + const fn = new lambda.Function(stack, 'Lambda', { code: lambda.Code.fromInline('foo'), handler: 'index.handler', @@ -5106,148 +5107,6 @@ describe('telemetry metadata', () => { expect(fn.node.metadata).toStrictEqual([]); }); }); -describe('L1 Relationships', () => { - it('simple union', () => { - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); - new lambda.CfnFunction(stack, 'MyLambda', { - code: { zipFile: 'foo' }, - role: role, // Simple Union - }); - Template.fromStack(stack).hasResource('AWS::Lambda::Function', { - Properties: { - Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] }, - }, - }); - }); - - it('array of unions', () => { - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); - const layer1 = new lambda.LayerVersion(stack, 'LayerVersion1', { - code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), - compatibleRuntimes: [lambda.Runtime.PYTHON_3_13], - }); - const layer2 = new lambda.LayerVersion(stack, 'LayerVersion2', { - code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')), - compatibleRuntimes: [lambda.Runtime.PYTHON_3_13], - }); - new lambda.CfnFunction(stack, 'MyLambda', { - code: { zipFile: 'foo' }, - role: role, - layers: [layer1, layer2], // Array of Unions - }); - Template.fromStack(stack).hasResource('AWS::Lambda::Function', { - Properties: { - Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] }, - Layers: [{ Ref: 'LayerVersion139D4D7A8' }, { Ref: 'LayerVersion23E5F3CEA' }], - }, - }); - }); - - it('nested union', () => { - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); - const bucket = new s3.Bucket(stack, 'MyBucket'); - - new lambda.CfnFunction(stack, 'MyLambda', { - code: { - s3Bucket: bucket, // Nested union - }, - role: role, - }); - Template.fromStack(stack).hasResource('AWS::Lambda::Function', { - Properties: { - Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] }, - Code: { S3Bucket: { Ref: 'MyBucketF68F3FF0' } }, - }, - }); - }); - - it('deeply nested union', () => { - const stack = new cdk.Stack(); - const topic = new sns.CfnTopic(stack, 'Topic'); - - new lambda.CfnEventInvokeConfig(stack, 'EventConfig', { - functionName: 'myFunction', - qualifier: '$LATEST', - destinationConfig: { - onFailure: { - destination: topic, // Deeply nested: destinationConfig -> onFailure -> destination (union) - }, - }, - }); - Template.fromStack(stack).hasResource('AWS::Lambda::EventInvokeConfig', { - Properties: { - DestinationConfig: { - OnFailure: { - Destination: { Ref: 'Topic' }, - }, - }, - }, - }); - }); - - it('nested array of unions', () => { - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); - const securityGroup = new ec2.SecurityGroup(stack, 'SG', { - vpc: new ec2.Vpc(stack, 'VPC'), - }); - new lambda.CfnFunction(stack, 'MyLambda', { - code: { zipFile: 'foo' }, - role: role, - vpcConfig: { - securityGroupIds: [securityGroup], // Nested array of union - }, - }); - Template.fromStack(stack).hasResource('AWS::Lambda::Function', { - Properties: { - Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] }, - VpcConfig: { - SecurityGroupIds: [{ 'Fn::GetAtt': ['SGADB53937', 'GroupId'] }], - }, - }, - }); - }); - - it('tokens should be passed as is', () => { - const stack = new cdk.Stack(); - const role = new iam.Role(stack, 'SomeRole', { - assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), - }); - const bucket = new s3.Bucket(stack, 'MyBucket'); - - const codeToken = cdk.Token.asAny({ - resolve: () => ({ s3Bucket: bucket.bucketName }), - }); - - const fsConfigToken = cdk.Token.asAny({ - resolve: () => ([{ arn: 'TestArn', localMountPath: '/mnt' }]), - }); - - new lambda.CfnFunction(stack, 'MyLambda', { - code: codeToken, - role: role, - fileSystemConfigs: fsConfigToken, - }); - Template.fromStack(stack).hasResource('AWS::Lambda::Function', { - Properties: { - Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] }, - Code: { S3Bucket: { Ref: 'MyBucketF68F3FF0' } }, - FileSystemConfigs: [{ Arn: 'TestArn', LocalMountPath: '/mnt' }], - }, - }); - }); -}); function newTestLambda(scope: constructs.Construct) { return new lambda.Function(scope, 'MyLambda', { diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index 34d09dcfeb303..76482f0343498 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -2354,7 +2354,7 @@ export class Bucket extends BucketBase { const objectLockConfiguration = this.parseObjectLockConfig(props); const replicationConfiguration = this.renderReplicationConfiguration(props); - this.replicationRoleArn = (replicationConfiguration?.role as iam.IRoleRef)?.roleRef?.roleArn ?? replicationConfiguration?.role; + this.replicationRoleArn = replicationConfiguration?.role; this.objectOwnership = props.objectOwnership; this.transitionDefaultMinimumObjectSize = props.transitionDefaultMinimumObjectSize; const resource = new CfnBucket(this, 'Resource', { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/ast.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/ast.ts index 035b8548c2688..c68dd7adc36ef 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/ast.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/ast.ts @@ -3,7 +3,6 @@ import { Module } from '@cdklabs/typewriter'; import { AugmentationsModule } from './augmentation-generator'; import { CannedMetricsModule } from './canned-metrics'; import { CDK_CORE, CONSTRUCTS, ModuleImportLocations } from './cdk'; -import { SelectiveImport } from './relationship-decider'; import { ResourceClass } from './resource-class'; /** @@ -60,7 +59,7 @@ export class AstBuilder { for (const link of resources) { ast.addResource(link.entity); } - ast.renderImports(); + return ast; } @@ -75,7 +74,6 @@ export class AstBuilder { const ast = new AstBuilder(scope, props, aug, metrics); ast.addResource(resource); - ast.renderImports(); return ast; } @@ -87,8 +85,6 @@ export class AstBuilder { public readonly resources: Record = {}; private nameSuffix?: string; private deprecated?: string; - public readonly selectiveImports = new Array(); - private readonly modulesRootLocation: string; protected constructor( public readonly module: T, @@ -99,7 +95,6 @@ export class AstBuilder { this.db = props.db; this.nameSuffix = props.nameSuffix; this.deprecated = props.deprecated; - this.modulesRootLocation = props.importLocations?.modulesRoot ?? '../..'; CDK_CORE.import(this.module, 'cdk', { fromLocation: props.importLocations?.core }); CONSTRUCTS.import(this.module, 'constructs'); @@ -116,35 +111,6 @@ export class AstBuilder { resourceClass.build(); - this.addImports(resourceClass); this.augmentations?.augmentResource(resource, resourceClass); } - - private addImports(resourceClass: ResourceClass) { - for (const selectiveImport of resourceClass.imports) { - const existingModuleImport = this.selectiveImports.find( - (imp) => imp.moduleName === selectiveImport.moduleName, - ); - if (!existingModuleImport) { - this.selectiveImports.push(selectiveImport); - } else { - // We need to avoid importing the same reference multiple times - for (const type of selectiveImport.types) { - if (!existingModuleImport.types.find((t) => - t.originalType === type.originalType && t.aliasedType === type.aliasedType, - )) { - existingModuleImport.types.push(type); - } - } - } - } - } - - public renderImports() { - const sortedImports = this.selectiveImports.sort((a, b) => a.moduleName.localeCompare(b.moduleName)); - for (const selectiveImport of sortedImports) { - const sourceModule = new Module(selectiveImport.moduleName); - sourceModule.importSelective(this.module, selectiveImport.types.map((t) => `${t.originalType} as ${t.aliasedType}`), { fromLocation: `${this.modulesRootLocation}/${sourceModule.name}` }); - } - } } diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts index cb8a05557bc97..60074230f0cdc 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts @@ -22,11 +22,6 @@ export interface ModuleImportLocations { * @default 'aws-cdk-lib/aws-cloudwatch' */ readonly cloudwatch?: string; - /** - * The root location of all the modules - * @default '../..' - */ - readonly modulesRoot?: string; } export class CdkCore extends ExternalModule { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/reference-props.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/reference-props.ts deleted file mode 100644 index 3d7c7125a0dd4..0000000000000 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/reference-props.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Resource } from '@aws-cdk/service-spec-types'; -import { $E, expr, Expression, PropertySpec, Type } from '@cdklabs/typewriter'; -import { attributePropertyName, referencePropertyName } from '../naming'; -import { CDK_CORE } from './cdk'; - -export interface ReferenceProp { - readonly declaration: PropertySpec; - readonly cfnValue: Expression; -} - -// Convenience typewriter builder -const $this = $E(expr.this_()); - -export function getReferenceProps(resource: Resource): ReferenceProp[] { - const referenceProps = []; - // Primary identifier. We assume all parts are strings. - const primaryIdentifier = resource.primaryIdentifier ?? []; - if (primaryIdentifier.length === 1) { - referenceProps.push({ - declaration: { - name: referencePropertyName(primaryIdentifier[0], resource.name), - type: Type.STRING, - immutable: true, - docs: { - summary: `The ${primaryIdentifier[0]} of the ${resource.name} resource.`, - }, - }, - cfnValue: $this.ref, - }); - } else if (primaryIdentifier.length > 1) { - for (const [i, cfnName] of enumerate(primaryIdentifier)) { - referenceProps.push({ - declaration: { - name: referencePropertyName(cfnName, resource.name), - type: Type.STRING, - immutable: true, - docs: { - summary: `The ${cfnName} of the ${resource.name} resource.`, - }, - }, - cfnValue: splitSelect('|', i, $this.ref), - }); - } - } - - const arnProp = findArnProperty(resource); - if (arnProp) { - referenceProps.push({ - declaration: { - name: referencePropertyName(arnProp, resource.name), - type: Type.STRING, - immutable: true, - docs: { - summary: `The ARN of the ${resource.name} resource.`, - }, - }, - cfnValue: $this[attributePropertyName(arnProp)], - }); - } - return referenceProps; -} - -/** - * Find an ARN property for a given resource - * - * Returns `undefined` if no ARN property is found, or if the ARN property is already - * included in the primary identifier. - */ -export function findArnProperty(resource: Resource) { - const possibleArnNames = ['Arn', `${resource.name}Arn`]; - for (const name of possibleArnNames) { - const prop = resource.attributes[name]; - if (prop && !resource.primaryIdentifier?.includes(name)) { - return name; - } - } - return undefined; -} - -function splitSelect(sep: string, n: number, base: Expression) { - return CDK_CORE.Fn.select(expr.lit(n), CDK_CORE.Fn.split(expr.lit(sep), base)); -} - -function enumerate(xs: A[]): Array<[number, A]> { - return xs.map((x, i) => [i, x]); -} diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts deleted file mode 100644 index 51f9f66194d6f..0000000000000 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/relationship-decider.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Property, RelationshipRef, Resource, RichProperty, SpecDatabase } from '@aws-cdk/service-spec-types'; -import { namespaceFromResource, referenceInterfaceName, referenceInterfaceAttributeName, referencePropertyName, typeAliasPrefixFromResource } from '../naming'; -import { getReferenceProps } from './reference-props'; -import { createModuleDefinitionFromCfnNamespace } from '../cfn2ts/pkglint'; -import { log } from '../util'; - -// For now we want relationships to be applied only for these services -const RELATIONSHIP_SERVICES = [ - 'iam', - 'apigateway', - 'ec2', - 'cloudfront', - 'kms', - 's3', - 'lambda', -]; - -/** - * Represents a cross-service property relationship that enables references - * between resources from different AWS services. - */ -export interface Relationship { - /** The TypeScript interface type that provides the reference (e.g. "IRoleRef") */ - readonly referenceType: string; - /** The property name on the reference interface that holds the reference object (e.g. "roleRef") */ - readonly referenceName: string; - /** The property to extract from the reference object (e.g. "roleArn") */ - readonly propName: string; -} - -/** - * Represents a selective import statement for cross-module type references. - * Used to import specific types from other CDK modules when relationships - * are between different modules. - */ -export interface SelectiveImport { - /** The module name to import from */ - readonly moduleName: string; - /** Array of types that need to be imported */ - readonly types: { - /** The original type name in the source module */ - originalType: string; - /** The aliased name to avoid naming conflicts */ - aliasedType: string; - }[]; -} - -/** - * Extracts resource relationship information from the database for cross-service property references. - */ -export class RelationshipDecider { - private readonly namespace: string; - public readonly imports = new Array(); - - constructor(readonly resource: Resource, private readonly db: SpecDatabase) { - this.namespace = namespaceFromResource(resource); - } - - private registerRequiredImport({ namespace, originalType, aliasedType }: { - namespace: string; - originalType: string; - aliasedType: string; - }) { - const moduleName = createModuleDefinitionFromCfnNamespace(namespace).moduleName; - const moduleImport = this.imports.find(i => i.moduleName === moduleName); - if (!moduleImport) { - this.imports.push({ - moduleName, - types: [{ originalType, aliasedType }], - }); - } else { - if (!moduleImport.types.find(t => - t.originalType === originalType && t.aliasedType === aliasedType, - )) { - moduleImport.types.push({ originalType, aliasedType }); - } - } - } - - /** - * Retrieves the target resource for a relationship. - * Returns undefined if the target property cannot be found in the reference - * properties as a relationship can only target a primary identifier or arn - */ - private findTargetResource(sourcePropName: string, relationship: RelationshipRef) { - if (!RELATIONSHIP_SERVICES.some(s => this.resource.cloudFormationType.toLowerCase().startsWith(`aws::${s}::`))) { - return undefined; - } - const targetResource = this.db.lookup('resource', 'cloudFormationType', 'equals', relationship.cloudFormationType).only(); - const refProps = getReferenceProps(targetResource); - const expectedPropName = referencePropertyName(relationship.propertyName, targetResource.name); - const prop = refProps.find(p => p.declaration.name === expectedPropName); - if (!prop) { - log.debug( - 'Could not find target prop for relationship:', - `${this.resource.cloudFormationType} ${sourcePropName}`, - `=> ${targetResource.cloudFormationType} ${relationship.propertyName}`, - ); - return undefined; - } - return targetResource; - } - - public parseRelationship(sourcePropName: string, relationships?: RelationshipRef[]) { - const parsedRelationships: Relationship[] = []; - if (!relationships) { - return parsedRelationships; - } - for (const relationship of relationships) { - const targetResource = this.findTargetResource(sourcePropName, relationship); - if (!targetResource) { - continue; - } - // Ignore the suffix part because it's an edge case that happens only for one module - const interfaceName = referenceInterfaceName(targetResource.name); - const refPropStructName = referenceInterfaceAttributeName(targetResource.name); - - const targetNamespace = namespaceFromResource(targetResource); - let aliasedTypeName = undefined; - if (this.namespace !== targetNamespace) { - // If this is not in our namespace we need to alias the import type - aliasedTypeName = `${typeAliasPrefixFromResource(targetResource)}${interfaceName}`; - this.registerRequiredImport({ namespace: targetNamespace, originalType: interfaceName, aliasedType: aliasedTypeName }); - } - parsedRelationships.push({ - referenceType: aliasedTypeName ?? interfaceName, - referenceName: refPropStructName, - propName: referencePropertyName(relationship.propertyName, targetResource.name), - }); - } - return parsedRelationships; - } - - /** - * Extracts the referenced type from a property's type, for direct refs and array element refs. - */ - private getReferencedType(prop: Property) { - // Use the oldest type for backwards compatibility - const type = new RichProperty(prop).types()[0]; - if (type.type === 'ref') { - return this.db.get('typeDefinition', type.reference.$ref); - } else if (type.type === 'array' && type.element.type === 'ref') { - return this.db.get('typeDefinition', type.element.reference.$ref); - } - return undefined; - } - - private hasValidRelationships(sourcePropName: string, relationships?: RelationshipRef[]): boolean { - if (!relationships) { - return false; - } - return relationships.some(rel => this.findTargetResource(sourcePropName, rel) !== undefined); - } - - /** - * Checks if a given property needs a flattening function or not - */ - public needsFlatteningFunction(propName: string, prop: Property, visited = new Set()): boolean { - if (this.hasValidRelationships(propName, prop.relationshipRefs)) { - return true; - } - - const referencedTypeDef = this.getReferencedType(prop); - if (!referencedTypeDef) { - return false; - } - - if (visited.has(referencedTypeDef.$id)) { - return false; - } - visited.add(referencedTypeDef.$id); - - for (const [nestedPropName, nestedProp] of Object.entries(referencedTypeDef.properties)) { - if (this.needsFlatteningFunction(nestedPropName, nestedProp, visited)) { - return true; - } - } - return false; - } -} diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts deleted file mode 100644 index 1dca3489dc9e1..0000000000000 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resolver-builder.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { DefinitionReference, Property } from '@aws-cdk/service-spec-types'; -import { expr, Expression, Module, Type } from '@cdklabs/typewriter'; -import { CDK_CORE } from './cdk'; -import { RelationshipDecider, Relationship } from './relationship-decider'; -import { NON_RESOLVABLE_PROPERTY_NAMES } from './tagging'; -import { TypeConverter } from './type-converter'; -import { flattenFunctionNameFromType, propertyNameFromCloudFormation } from '../naming'; - -export interface ResolverResult { - /** Property name */ - name: string; - /** Property type augmented with the relationships type information */ - propType: Type; - /** Property type without relationship type information */ - resolvableType: Type; - /** Same as propType without IResolvable */ - baseType: Type; - resolver: (props: Expression) => Expression; -} - -/** - * Builds property resolvers that handle relationships and nested property flattening - */ -export class ResolverBuilder { - constructor( - private readonly converter: TypeConverter, - private readonly relationshipDecider: RelationshipDecider, - private readonly module: Module, - ) {} - - public buildResolver(prop: Property, cfnName: string): ResolverResult { - const name = propertyNameFromCloudFormation(cfnName); - const baseType = this.converter.typeFromProperty(prop); - - // Whether or not a property is made `IResolvable` originally depended on - // the name of the property. These conditions were probably expected to coincide, - // but didn't. - const resolvableType = cfnName in NON_RESOLVABLE_PROPERTY_NAMES ? baseType : this.converter.makeTypeResolvable(baseType); - - const relationships = this.relationshipDecider.parseRelationship(name, prop.relationshipRefs); - if (relationships.length > 0) { - return this.buildRelationshipResolver({ relationships, baseType, name, resolvableType }); - } - - const originalType = this.converter.originalType(baseType); - if (this.relationshipDecider.needsFlatteningFunction(name, prop)) { - const optional = !prop.required; - const typeRef = originalType.type === 'array' ? originalType.element : originalType; - if (typeRef.type === 'ref') { - return this.buildNestedResolver({ name, baseType, typeRef: typeRef, resolvableType, optional }); - } - } - - return { - name, - propType: resolvableType, - resolvableType, - baseType, - resolver: (props: Expression) => expr.get(props, name), - }; - } - - private buildRelationshipResolver({ relationships, baseType, name, resolvableType }: { - relationships: Relationship[]; - baseType: Type; - name: string; - resolvableType: Type; - }): ResolverResult { - if (!(baseType === Type.STRING || baseType.arrayOfType === Type.STRING)) { - throw Error('Trying to map to a non string property'); - } - const newTypes = relationships.map(t => Type.fromName(this.module, t.referenceType)); - const propType = resolvableType.arrayOfType - ? Type.arrayOf(Type.distinctUnionOf(resolvableType.arrayOfType, ...newTypes)) - : Type.distinctUnionOf(resolvableType, ...newTypes); - - // Generates code like: - // For single value: (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? (props.roleArn as IUserRef)?.userRef?.userArn ?? props.roleArn - // For array: props.roleArns?.map((item: any) => (item as IRoleRef)?.roleRef?.roleArn ?? (item as IUserRef)?.userRef?.userArn ?? item) - // Ensures that arn properties always appear first in the chain as they are more general - const arnRels = relationships.filter(r => r.propName.toLowerCase().endsWith('arn')); - const otherRels = relationships.filter(r => !r.propName.toLowerCase().endsWith('arn')); - - const buildChain = (itemName: string) => [ - ...[...arnRels, ...otherRels] - .map(r => `(${itemName} as ${r.referenceType})?.${r.referenceName}?.${r.propName}`), - itemName, - ].join(' ?? '); - const resolver = (_: Expression) => { - if (resolvableType.arrayOfType) { - return expr.directCode(`props.${name}?.map((item: any) => ${ buildChain('item') })`); - } else { - return expr.directCode(buildChain(`props.${name}`)); - } - }; - - return { name, propType, resolvableType, baseType, resolver }; - } - - private buildNestedResolver({ name, baseType, typeRef, resolvableType, optional }: { - name: string; - baseType: Type; - typeRef: DefinitionReference; - resolvableType: Type; - optional: boolean; - }): ResolverResult { - const referencedTypeDef = this.converter.db.get('typeDefinition', typeRef.reference.$ref); - const referencedStruct = this.converter.convertTypeDefinitionType(referencedTypeDef); - const functionName = flattenFunctionNameFromType(referencedStruct); - - const resolver = (props: Expression) => { - const propValue = expr.get(props, name); - const isArray = baseType.arrayOfType !== undefined; - - const flattenCall = isArray - ? propValue.callMethod('map', expr.ident(functionName)) - : expr.ident(functionName).call(propValue); - - const condition = optional - ? expr.cond(propValue).then(flattenCall).else(expr.UNDEFINED) - : flattenCall; - - return isArray - ? expr.cond(CDK_CORE.isResolvableObject(propValue)).then(propValue).else(condition) - : condition; - }; - return { name, propType: resolvableType, resolvableType, baseType, resolver }; - } -} diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts index c46e8b89bb3e7..3e08ed49323df 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts @@ -28,20 +28,18 @@ import { } from '@cdklabs/typewriter'; import { CDK_CORE, CONSTRUCTS } from './cdk'; import { CloudFormationMapping } from './cloudformation-mapping'; -import { ResourceDecider } from './resource-decider'; +import { ResourceDecider, shouldBuildReferenceInterface } from './resource-decider'; import { TypeConverter } from './type-converter'; import { cfnParserNameFromType, cfnProducerNameFromType, classNameFromResource, cloudFormationDocLink, propertyNameFromCloudFormation, - propStructNameFromResource, referenceInterfaceName, referenceInterfaceAttributeName, referencePropertyName, + propStructNameFromResource, referencePropertyName, staticRequiredTransform, staticResourceTypeName, } from '../naming'; import { splitDocumentation } from '../util'; -import { findArnProperty } from './reference-props'; -import { SelectiveImport, RelationshipDecider } from './relationship-decider'; export interface ITypeHost { typeFromSpecType(type: PropertyType): Type; @@ -57,13 +55,11 @@ export interface ResourceClassProps { export class ResourceClass extends ClassType { private readonly propsType: StructType; - private readonly refInterface: InterfaceType; + private readonly refInterface?: InterfaceType; private readonly decider: ResourceDecider; - private readonly relationshipDecider: RelationshipDecider; private readonly converter: TypeConverter; private readonly module: Module; private referenceStruct?: StructType; - public readonly imports = new Array(); constructor( scope: IScope, @@ -71,17 +67,20 @@ export class ResourceClass extends ClassType { private readonly resource: Resource, private readonly props: ResourceClassProps = {}, ) { - // IBucketRef { bucketRef: BucketRef } - const refInterface = new InterfaceType(scope, { - export: true, - name: referenceInterfaceName(resource.name, props.suffix), - extends: [CONSTRUCTS.IConstruct], - docs: { - summary: `Indicates that this resource can be referenced as a ${resource.name}.`, - stability: Stability.Experimental, - ...maybeDeprecated(props.deprecated), - }, - }); + let refInterface: InterfaceType | undefined; + if (shouldBuildReferenceInterface(resource)) { + // IBucketRef { bucketRef: BucketRef } + refInterface = new InterfaceType(scope, { + export: true, + name: `I${resource.name}${props.suffix ?? ''}Ref`, + extends: [CONSTRUCTS.IConstruct], + docs: { + summary: `Indicates that this resource can be referenced as a ${resource.name}.`, + stability: Stability.Experimental, + ...maybeDeprecated(props.deprecated), + }, + }); + } super(scope, { export: true, @@ -96,7 +95,7 @@ export class ResourceClass extends ClassType { ...maybeDeprecated(props.deprecated), }, extends: CDK_CORE.CfnResource, - implements: [CDK_CORE.IInspectable, refInterface.type, ...ResourceDecider.taggabilityInterfaces(resource)].filter(isDefined), + implements: [CDK_CORE.IInspectable, refInterface?.type, ...ResourceDecider.taggabilityInterfaces(resource)].filter(isDefined), }); this.refInterface = refInterface; @@ -115,16 +114,13 @@ export class ResourceClass extends ClassType { }, }); - this.relationshipDecider = new RelationshipDecider(this.resource, db); this.converter = TypeConverter.forResource({ db: db, resource: this.resource, resourceClass: this, - relationshipDecider: this.relationshipDecider, }); - this.imports = this.relationshipDecider.imports; - this.decider = new ResourceDecider(this.resource, this.converter, this.relationshipDecider); + this.decider = new ResourceDecider(this.resource, this.converter); } /** @@ -195,6 +191,10 @@ export class ResourceClass extends ClassType { * Build the reference interface for this resource */ private buildReferenceInterface() { + if (!shouldBuildReferenceInterface(this.resource)) { + return; + } + // BucketRef { bucketName, bucketArn } this.referenceStruct = new StructType(this.scope, { export: true, @@ -211,8 +211,8 @@ export class ResourceClass extends ClassType { this.referenceStruct.addProperty(declaration); } - const refProperty = this.refInterface.addProperty({ - name: referenceInterfaceAttributeName(this.decider.camelResourceName), + const refProperty = this.refInterface!.addProperty({ + name: `${this.decider.camelResourceName}Ref`, type: this.referenceStruct.type, immutable: true, docs: { @@ -232,12 +232,12 @@ export class ResourceClass extends ClassType { private makeFromArnFactory() { const arnTemplate = this.resource.arnTemplate; - if (!(arnTemplate && this.referenceStruct)) { + if (!(arnTemplate && this.refInterface && this.referenceStruct)) { // We don't have enough information to build this factory return; } - const cfnArnProperty = findArnProperty(this.resource); + const cfnArnProperty = this.decider.findArnProperty(); if (cfnArnProperty == null) { return; } @@ -260,7 +260,7 @@ export class ResourceClass extends ClassType { } const innerClass = mkImportClass(this.scope); - const refAttributeName = referenceInterfaceAttributeName(this.decider.camelResourceName); + const refAttributeName = `${this.decider.camelResourceName}Ref`; innerClass.addProperty({ name: refAttributeName, @@ -287,9 +287,9 @@ export class ResourceClass extends ClassType { const factory = this.addMethod({ name: `from${this.resource.name}Arn`, static: true, - returnType: this.refInterface.type, + returnType: this.refInterface?.type, docs: { - summary: `Creates a new ${this.refInterface.name} from an ARN`, + summary: `Creates a new ${this.refInterface?.name} from an ARN`, }, }); factory.addParameter({ name: 'scope', type: CONSTRUCTS.Construct }); @@ -320,7 +320,7 @@ export class ResourceClass extends ClassType { private makeFromNameFactory() { const arnTemplate = this.resource.arnTemplate; - if (!(arnTemplate && this.referenceStruct)) { + if (!(arnTemplate && this.refInterface && this.referenceStruct)) { // We don't have enough information to build this factory return; } @@ -344,7 +344,7 @@ export class ResourceClass extends ClassType { const innerClass = mkImportClass(this.scope); - const refAttributeName = referenceInterfaceAttributeName(this.decider.camelResourceName); + const refAttributeName = `${this.decider.camelResourceName}Ref`; innerClass.addProperty({ name: refAttributeName, type: this.referenceStruct!.type, @@ -396,9 +396,9 @@ export class ResourceClass extends ClassType { const factory = this.addMethod({ name: `from${variableName}`, static: true, - returnType: this.refInterface.type, + returnType: this.refInterface!.type, docs: { - summary: `Creates a new ${this.refInterface.name} from a ${propName}`, + summary: `Creates a new ${this.refInterface!.name} from a ${propName}`, }, }); factory.addParameter({ name: 'scope', type: CONSTRUCTS.Construct }); diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts index f67567b577889..6a8a4a627ca2a 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts @@ -2,17 +2,30 @@ import { Deprecation, Property, Resource, RichProperty, TagVariant } from '@aws- import { $E, $T, Expression, PropertySpec, Type, expr } from '@cdklabs/typewriter'; import { CDK_CORE } from './cdk'; import { PropertyMapping } from './cloudformation-mapping'; -import { RelationshipDecider } from './relationship-decider'; -import { ResolverBuilder } from './resolver-builder'; import { NON_RESOLVABLE_PROPERTY_NAMES, TaggabilityStyle, resourceTaggabilityStyle } from './tagging'; import { TypeConverter } from './type-converter'; -import { attributePropertyName, camelcasedResourceName, cloudFormationDocLink, propertyNameFromCloudFormation } from '../naming'; +import { attributePropertyName, camelcasedResourceName, cloudFormationDocLink, propertyNameFromCloudFormation, referencePropertyName } from '../naming'; import { splitDocumentation } from '../util'; -import { getReferenceProps, ReferenceProp } from './reference-props'; // This convenience typewriter builder is used all over the place const $this = $E(expr.this_()); +interface ReferenceProp { + readonly declaration: PropertySpec; + readonly cfnValue: Expression; +} + +// We temporarily only do this for a few services, to experiment +const REFERENCE_PROP_SERVICES = [ + 'iam', + 'apigateway', + 'ec2', + 'cloudfront', + 'kms', + 's3', + 'lambda', +]; + /** * Decide how properties get mapped between model types, Typescript types, and CloudFormation */ @@ -27,7 +40,6 @@ export class ResourceDecider { } private readonly taggability?: TaggabilityStyle; - private readonly resolverBuilder: ResolverBuilder; public readonly referenceProps = new Array(); public readonly propsProperties = new Array(); @@ -35,19 +47,14 @@ export class ResourceDecider { public readonly classAttributeProperties = new Array(); public readonly camelResourceName: string; - constructor( - private readonly resource: Resource, - private readonly converter: TypeConverter, - private readonly relationshipDecider: RelationshipDecider, - ) { + constructor(private readonly resource: Resource, private readonly converter: TypeConverter) { this.camelResourceName = camelcasedResourceName(resource); this.taggability = resourceTaggabilityStyle(this.resource); - this.resolverBuilder = new ResolverBuilder(this.converter, this.relationshipDecider, this.converter.module); this.convertProperties(); this.convertAttributes(); - this.referenceProps.push(...getReferenceProps(resource)); + this.convertReferenceProps(); this.propsProperties.sort((p1, p2) => p1.propertySpec.name.localeCompare(p2.propertySpec.name)); this.classProperties.sort((p1, p2) => p1.propertySpec.name.localeCompare(p2.propertySpec.name)); @@ -73,39 +80,104 @@ export class ResourceDecider { } } + /** + * Find an ARN property for this resource + * + * Returns `undefined` if no ARN property is found, or if the ARN property is already + * included in the primary identifier. + */ + public findArnProperty() { + const possibleArnNames = ['Arn', `${this.resource.name}Arn`]; + for (const name of possibleArnNames) { + const prop = this.resource.attributes[name]; + if (prop && !this.resource.primaryIdentifier?.includes(name)) { + return name; + } + } + return undefined; + } + + private convertReferenceProps() { + // Primary identifier. We assume all parts are strings. + const primaryIdentifier = this.resource.primaryIdentifier ?? []; + if (primaryIdentifier.length === 1) { + this.referenceProps.push({ + declaration: { + name: referencePropertyName(primaryIdentifier[0], this.resource.name), + type: Type.STRING, + immutable: true, + docs: { + summary: `The ${primaryIdentifier[0]} of the ${this.resource.name} resource.`, + }, + }, + cfnValue: $this.ref, + }); + } else if (primaryIdentifier.length > 1) { + for (const [i, cfnName] of enumerate(primaryIdentifier)) { + this.referenceProps.push({ + declaration: { + name: referencePropertyName(cfnName, this.resource.name), + type: Type.STRING, + immutable: true, + docs: { + summary: `The ${cfnName} of the ${this.resource.name} resource.`, + }, + }, + cfnValue: splitSelect('|', i, $this.ref), + }); + } + } + + const arnProp = this.findArnProperty(); + if (arnProp) { + this.referenceProps.push({ + declaration: { + name: referencePropertyName(arnProp, this.resource.name), + type: Type.STRING, + immutable: true, + docs: { + summary: `The ARN of the ${this.resource.name} resource.`, + }, + }, + cfnValue: $this[attributePropertyName(arnProp)], + }); + } + } + /** * Default mapping for a property */ private handlePropertyDefault(cfnName: string, prop: Property) { - const optional = !prop.required; + const name = propertyNameFromCloudFormation(cfnName); - const resolverResult = this.resolverBuilder.buildResolver(prop, cfnName); + const { type, baseType } = this.legacyCompatiblePropType(cfnName, prop); + const optional = !prop.required; this.propsProperties.push({ propertySpec: { - name: resolverResult.name, - type: resolverResult.propType, + name, + type, optional, docs: this.defaultPropDocs(cfnName, prop), }, validateRequiredInConstructor: !!prop.required, cfnMapping: { cfnName, - propName: resolverResult.name, - baseType: resolverResult.baseType, + propName: name, + baseType, optional, }, }); this.classProperties.push({ propertySpec: { - name: resolverResult.name, - type: resolverResult.resolvableType, + name, + type, optional, immutable: false, docs: this.defaultClassPropDocs(cfnName, prop), }, - initializer: resolverResult.resolver, - cfnValueToRender: { [resolverResult.name]: $this[resolverResult.name] }, + initializer: (props: Expression) => expr.get(props, name), + cfnValueToRender: { [name]: $this[name] }, }); } @@ -394,3 +466,14 @@ export function deprecationMessage(property: Property): string | undefined { return undefined; } +function splitSelect(sep: string, n: number, base: Expression) { + return CDK_CORE.Fn.select(expr.lit(n), CDK_CORE.Fn.split(expr.lit(sep), base)); +} + +function enumerate(xs: A[]): Array<[number, A]> { + return xs.map((x, i) => [i, x]); +} + +export function shouldBuildReferenceInterface(resource: Resource) { + return true || REFERENCE_PROP_SERVICES.some(s => resource.cloudFormationType.toLowerCase().startsWith(`aws::${s}::`)); +} diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/type-converter.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/type-converter.ts index 155e62fb278a7..5accddfe15da2 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/type-converter.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/type-converter.ts @@ -8,7 +8,6 @@ import { } from '@aws-cdk/service-spec-types'; import { ClassType, Module, PrimitiveType, RichScope, StructType, Type, TypeDeclaration } from '@cdklabs/typewriter'; import { CDK_CORE } from './cdk'; -import { RelationshipDecider } from './relationship-decider'; import { TypeDefinitionStruct } from './typedefinition-struct'; import { structNameFromTypeDefinition } from '../naming/conventions'; @@ -32,7 +31,6 @@ export type TypeDefinitionConverter = ( export interface TypeConverterForResourceOptions extends Omit { readonly resource: Resource; readonly resourceClass: ClassType; - readonly relationshipDecider: RelationshipDecider; } /** @@ -66,7 +64,6 @@ export class TypeConverter { resourceClass: opts.resourceClass, converter, typeDefinition, - relationshipDecider: opts.relationshipDecider, }); return { diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-decider.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-decider.ts index 8980570ba84c9..cb8715183e992 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-decider.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-decider.ts @@ -1,11 +1,10 @@ import { Property, Resource, TypeDefinition } from '@aws-cdk/service-spec-types'; -import { Expression, PropertySpec, Type } from '@cdklabs/typewriter'; +import { PropertySpec, Type } from '@cdklabs/typewriter'; import { PropertyMapping } from './cloudformation-mapping'; -import { RelationshipDecider } from './relationship-decider'; -import { ResolverBuilder } from './resolver-builder'; import { deprecationMessage } from './resource-decider'; +import { NON_RESOLVABLE_PROPERTY_NAMES } from './tagging'; import { TypeConverter } from './type-converter'; -import { cloudFormationDocLink } from '../naming'; +import { cloudFormationDocLink, propertyNameFromCloudFormation } from '../naming'; import { splitDocumentation } from '../util'; /** @@ -13,15 +12,12 @@ import { splitDocumentation } from '../util'; */ export class TypeDefinitionDecider { public readonly properties = new Array(); - private readonly resolverBuilder: ResolverBuilder; constructor( private readonly resource: Resource, private readonly typeDefinition: TypeDefinition, private readonly converter: TypeConverter, - private readonly relationshipDecider: RelationshipDecider, ) { - this.resolverBuilder = new ResolverBuilder(this.converter, this.relationshipDecider, this.converter.module); this.convertProperties(); this.properties.sort((p1, p2) => p1.propertySpec.name.localeCompare(p2.propertySpec.name)); } @@ -36,14 +32,19 @@ export class TypeDefinitionDecider { * Default mapping for a property */ private handlePropertyDefault(cfnName: string, prop: Property) { - const optional = !prop.required; + const name = propertyNameFromCloudFormation(cfnName); + const baseType = this.converter.typeFromProperty(prop); - const resolverResult = this.resolverBuilder.buildResolver(prop, cfnName); + // Whether or not a property is made `IResolvable` originally depended on + // the name of the property. These conditions were probably expected to coincide, + // but didn't. + const type = cfnName in NON_RESOLVABLE_PROPERTY_NAMES ? baseType : this.converter.makeTypeResolvable(baseType); + const optional = !prop.required; this.properties.push({ propertySpec: { - name: resolverResult.name, - type: resolverResult.propType, + name, + type, optional, docs: { ...splitDocumentation(prop.documentation), @@ -56,14 +57,13 @@ export class TypeDefinitionDecider { deprecated: deprecationMessage(prop), }, }, - baseType: resolverResult.baseType, + baseType, cfnMapping: { cfnName, - propName: resolverResult.name, - baseType: resolverResult.baseType, + propName: name, + baseType, optional, }, - resolver: resolverResult.resolver, }); } } @@ -73,5 +73,4 @@ export interface TypeDefProperty { /** The type that was converted (does not have the IResolvable union) */ readonly baseType: Type; readonly cfnMapping: PropertyMapping; - readonly resolver: (_: Expression) => Expression; } diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-struct.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-struct.ts index 11eb590ad3859..ba0ad951da6db 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-struct.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/typedefinition-struct.ts @@ -1,19 +1,16 @@ import { Resource, TypeDefinition } from '@aws-cdk/service-spec-types'; -import { ClassType, expr, FreeFunction, Module, Stability, stmt, StructType, Type } from '@cdklabs/typewriter'; +import { ClassType, Module, Stability, StructType } from '@cdklabs/typewriter'; import { CloudFormationMapping } from './cloudformation-mapping'; -import { RelationshipDecider } from './relationship-decider'; import { TypeConverter } from './type-converter'; import { TypeDefinitionDecider } from './typedefinition-decider'; -import { cloudFormationDocLink, flattenFunctionNameFromType, structNameFromTypeDefinition } from '../naming'; +import { cloudFormationDocLink, structNameFromTypeDefinition } from '../naming'; import { splitDocumentation } from '../util'; -import { CDK_CORE } from './cdk'; export interface TypeDefinitionStructOptions { readonly typeDefinition: TypeDefinition; readonly converter: TypeConverter; readonly resource: Resource; readonly resourceClass: ClassType; - readonly relationshipDecider: RelationshipDecider; } /** @@ -26,7 +23,6 @@ export class TypeDefinitionStruct extends StructType { private readonly converter: TypeConverter; private readonly resource: Resource; private readonly module: Module; - private readonly relationshipDecider: RelationshipDecider; constructor(options: TypeDefinitionStructOptions) { super(options.resourceClass, { @@ -45,7 +41,6 @@ export class TypeDefinitionStruct extends StructType { this.typeDefinition = options.typeDefinition; this.converter = options.converter; this.resource = options.resource; - this.relationshipDecider = options.relationshipDecider; this.module = Module.of(this); } @@ -53,43 +48,13 @@ export class TypeDefinitionStruct extends StructType { public build() { const cfnMapping = new CloudFormationMapping(this.module, this.converter); - const decider = new TypeDefinitionDecider(this.resource, this.typeDefinition, this.converter, this.relationshipDecider); + const decider = new TypeDefinitionDecider(this.resource, this.typeDefinition, this.converter); for (const prop of decider.properties) { this.addProperty(prop.propertySpec); cfnMapping.add(prop.cfnMapping); } - let needsResolverFunction = false; - for (const [propName, prop] of Object.entries(this.typeDefinition.properties)) { - needsResolverFunction = needsResolverFunction - ? needsResolverFunction - : this.relationshipDecider.needsFlatteningFunction(propName, prop); - } - - if (needsResolverFunction) { - const resolverFunction = new FreeFunction(this.module, { - name: flattenFunctionNameFromType(this), - returnType: Type.unionOf(this.type, CDK_CORE.IResolvable), - parameters: [{ name: 'props', type: Type.unionOf(this.type, CDK_CORE.IResolvable) }], - }); - - const propsParam = resolverFunction.parameters[0]; - resolverFunction.addBody( - stmt.if_(CDK_CORE.isResolvableObject(propsParam)) - .then(stmt.ret(propsParam)), - - stmt.ret(expr.object( - Object.fromEntries( - decider.properties.map(prop => [ - prop.propertySpec.name, - prop.resolver(propsParam), - ]), - ), - )), - ); - } - cfnMapping.makeCfnProducer(this.module, this); cfnMapping.makeCfnParser(this.module, this); } diff --git a/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts b/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts index e0b69e5251bdb..e290eb1ae1cdf 100644 --- a/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts +++ b/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts @@ -63,17 +63,6 @@ export function interfaceNameFromResource(res: Resource, suffix?: string) { return `I${classNameFromResource(res, suffix)}`; } -export function namespaceFromResource(res: Resource) { - return res.cloudFormationType.split('::').slice(0, 2).join('::'); -} - -/** - * Get the AWS namespace prefix from a resource in PascalCase for use as a type alias prefix. - */ -export function typeAliasPrefixFromResource(res: Resource) { - return camelcase(res.cloudFormationType.split('::')[1], { pascalCase: true }); -} - export function cfnProducerNameFromType(struct: TypeDeclaration) { return `convert${qualifiedName(struct)}ToCloudFormation`; } @@ -86,10 +75,6 @@ export function cfnPropsValidatorNameFromType(struct: TypeDeclaration) { return `${qualifiedName(struct)}Validator`; } -export function flattenFunctionNameFromType(struct: TypeDeclaration) { - return `flatten${qualifiedName(struct)}`; -} - export function metricsClassNameFromService(namespace: string) { return `${namespace.replace(/^AWS\//, '').replace('/', '')}Metrics`; } @@ -125,14 +110,6 @@ export function referencePropertyName(propName: string, resourceName: string) { return camelcase(propName); } -export function referenceInterfaceName(resourceName: string, suffix?: string) { - return `I${resourceName}${suffix ?? ''}Ref`; -} - -export function referenceInterfaceAttributeName(resourceName: string) { - return `${camelcase(resourceName)}Ref`; -} - /** * Generate a name for the given declaration so that we can generate helper symbols for it that won't class * diff --git a/tools/@aws-cdk/spec2cdk/test/__snapshots__/relationships.test.ts.snap b/tools/@aws-cdk/spec2cdk/test/__snapshots__/relationships.test.ts.snap deleted file mode 100644 index a1f1b2c0dd21b..0000000000000 --- a/tools/@aws-cdk/spec2cdk/test/__snapshots__/relationships.test.ts.snap +++ /dev/null @@ -1,1992 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`resource with array of nested properties with relationship 1`] = ` -"/* eslint-disable prettier/prettier, @stylistic/max-len */ -import * as cdk from "aws-cdk-lib"; -import * as constructs from "constructs"; -import * as cfn_parse from "aws-cdk-lib/core/lib/helpers-internal"; -import * as cdk_errors from "aws-cdk-lib/core/lib/errors"; - -/** - * Indicates that this resource can be referenced as a Role. - * - * @stability experimental - */ -export interface IRoleRef extends constructs.IConstruct { - /** - * A reference to a Role resource. - */ - readonly roleRef: RoleReference; -} - -/** - * @cloudformationResource AWS::IAM::Role - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export class CfnRole extends cdk.CfnResource implements cdk.IInspectable, IRoleRef, cdk.ITaggable { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Role"; - - /** - * Build a CfnRole from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnRole { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnRolePropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnRole(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - /** - * @cloudformationAttribute RoleArn - */ - public readonly attrRoleArn: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnRoleProps = {}) { - super(scope, id, { - "type": CfnRole.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.attrRoleArn = cdk.Token.asString(this.getAtt("RoleArn", cdk.ResolutionTypeHint.STRING)); - } - - public get roleRef(): RoleReference { - return { - "roleArn": this.attrRoleArn - }; - } - - protected get cfnProperties(): Record { - return {}; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnRole.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnRolePropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnRole\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export interface CfnRoleProps { - -} - -/** - * A reference to a Role resource. - * - * @struct - * @stability external - */ -export interface RoleReference { - /** - * The ARN of the Role resource. - */ - readonly roleArn: string; -} - -/** - * Determine whether the given properties match those of a \`CfnRoleProps\` - * - * @param properties - the TypeScript properties of a \`CfnRoleProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnRolePropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - return errors.wrap("supplied properties not correct for \\"CfnRoleProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnRolePropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnRolePropsValidator(properties).assertSuccess(); - return {}; -} - -// @ts-ignore TS6133 -function CfnRolePropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * Indicates that this resource can be referenced as a Resource. - * - * @stability experimental - */ -export interface IResourceRef extends constructs.IConstruct { - /** - * A reference to a Resource resource. - */ - readonly resourceRef: ResourceReference; -} - -/** - * @cloudformationResource AWS::IAM::Resource - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-resource.html - */ -export class CfnResource extends cdk.CfnResource implements cdk.IInspectable, IResourceRef { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Resource"; - - /** - * Build a CfnResource from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnResource { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnResourcePropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnResource(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - public permissions?: Array | cdk.IResolvable; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnResourceProps = {}) { - super(scope, id, { - "type": CfnResource.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.permissions = (cdk.isResolvableObject(props.permissions) ? props.permissions : (props.permissions ? props.permissions.map(flattenCfnResourcePermissionProperty) : undefined)); - } - - public get resourceRef(): ResourceReference { - return {}; - } - - protected get cfnProperties(): Record { - return { - "permissions": this.permissions - }; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnResource.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnResourcePropsToCloudFormation(props); - } -} - -export namespace CfnResource { - /** - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-resource-permission.html - */ - export interface PermissionProperty { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-resource-permission.html#cfn-iam-resource-permission-rolearn - */ - readonly roleArn?: IRoleRef | string; - } -} - -/** - * Properties for defining a \`CfnResource\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-resource.html - */ -export interface CfnResourceProps { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-resource.html#cfn-iam-resource-permissions - */ - readonly permissions?: Array | cdk.IResolvable; -} - -// @ts-ignore TS6133 -function flattenCfnResourcePermissionProperty(props: cdk.IResolvable | CfnResource.PermissionProperty): cdk.IResolvable | CfnResource.PermissionProperty { - if (cdk.isResolvableObject(props)) return props; - return { - "roleArn": (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? props.roleArn - }; -} - -/** - * Determine whether the given properties match those of a \`PermissionProperty\` - * - * @param properties - the TypeScript properties of a \`PermissionProperty\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnResourcePermissionPropertyValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("roleArn", cdk.validateString)(properties.roleArn)); - return errors.wrap("supplied properties not correct for \\"PermissionProperty\\""); -} - -// @ts-ignore TS6133 -function convertCfnResourcePermissionPropertyToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnResourcePermissionPropertyValidator(properties).assertSuccess(); - return { - "RoleArn": cdk.stringToCloudFormation(properties.roleArn) - }; -} - -// @ts-ignore TS6133 -function CfnResourcePermissionPropertyFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("roleArn", "RoleArn", (properties.RoleArn != null ? cfn_parse.FromCloudFormation.getString(properties.RoleArn) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * A reference to a Resource resource. - * - * @struct - * @stability external - */ -export interface ResourceReference { - -} - -/** - * Determine whether the given properties match those of a \`CfnResourceProps\` - * - * @param properties - the TypeScript properties of a \`CfnResourceProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnResourcePropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("permissions", cdk.listValidator(CfnResourcePermissionPropertyValidator))(properties.permissions)); - return errors.wrap("supplied properties not correct for \\"CfnResourceProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnResourcePropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnResourcePropsValidator(properties).assertSuccess(); - return { - "Permissions": cdk.listMapper(convertCfnResourcePermissionPropertyToCloudFormation)(properties.permissions) - }; -} - -// @ts-ignore TS6133 -function CfnResourcePropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("permissions", "Permissions", (properties.Permissions != null ? cfn_parse.FromCloudFormation.getArray(CfnResourcePermissionPropertyFromCloudFormation)(properties.Permissions) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -}" -`; - -exports[`resource with multiple relationship references 1`] = ` -"/* eslint-disable prettier/prettier, @stylistic/max-len */ -import * as cdk from "aws-cdk-lib"; -import * as constructs from "constructs"; -import * as cfn_parse from "aws-cdk-lib/core/lib/helpers-internal"; -import * as cdk_errors from "aws-cdk-lib/core/lib/errors"; - -/** - * Indicates that this resource can be referenced as a Role. - * - * @stability experimental - */ -export interface IRoleRef extends constructs.IConstruct { - /** - * A reference to a Role resource. - */ - readonly roleRef: RoleReference; -} - -/** - * @cloudformationResource AWS::IAM::Role - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export class CfnRole extends cdk.CfnResource implements cdk.IInspectable, IRoleRef, cdk.ITaggable { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Role"; - - /** - * Build a CfnRole from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnRole { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnRolePropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnRole(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - /** - * @cloudformationAttribute RoleArn - */ - public readonly attrRoleArn: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnRoleProps = {}) { - super(scope, id, { - "type": CfnRole.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.attrRoleArn = cdk.Token.asString(this.getAtt("RoleArn", cdk.ResolutionTypeHint.STRING)); - } - - public get roleRef(): RoleReference { - return { - "roleArn": this.attrRoleArn - }; - } - - protected get cfnProperties(): Record { - return {}; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnRole.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnRolePropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnRole\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export interface CfnRoleProps { - -} - -/** - * A reference to a Role resource. - * - * @struct - * @stability external - */ -export interface RoleReference { - /** - * The ARN of the Role resource. - */ - readonly roleArn: string; -} - -/** - * Determine whether the given properties match those of a \`CfnRoleProps\` - * - * @param properties - the TypeScript properties of a \`CfnRoleProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnRolePropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - return errors.wrap("supplied properties not correct for \\"CfnRoleProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnRolePropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnRolePropsValidator(properties).assertSuccess(); - return {}; -} - -// @ts-ignore TS6133 -function CfnRolePropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * Indicates that this resource can be referenced as a User. - * - * @stability experimental - */ -export interface IUserRef extends constructs.IConstruct { - /** - * A reference to a User resource. - */ - readonly userRef: UserReference; -} - -/** - * @cloudformationResource AWS::IAM::User - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-user.html - */ -export class CfnUser extends cdk.CfnResource implements cdk.IInspectable, IUserRef, cdk.ITaggable { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::User"; - - /** - * Build a CfnUser from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnUser { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnUserPropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnUser(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - /** - * @cloudformationAttribute UserArn - */ - public readonly attrUserArn: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnUserProps = {}) { - super(scope, id, { - "type": CfnUser.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.attrUserArn = cdk.Token.asString(this.getAtt("UserArn", cdk.ResolutionTypeHint.STRING)); - } - - public get userRef(): UserReference { - return { - "userArn": this.attrUserArn - }; - } - - protected get cfnProperties(): Record { - return {}; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnUser.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnUserPropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnUser\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-user.html - */ -export interface CfnUserProps { - -} - -/** - * A reference to a User resource. - * - * @struct - * @stability external - */ -export interface UserReference { - /** - * The ARN of the User resource. - */ - readonly userArn: string; -} - -/** - * Determine whether the given properties match those of a \`CfnUserProps\` - * - * @param properties - the TypeScript properties of a \`CfnUserProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnUserPropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - return errors.wrap("supplied properties not correct for \\"CfnUserProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnUserPropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnUserPropsValidator(properties).assertSuccess(); - return {}; -} - -// @ts-ignore TS6133 -function CfnUserPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * Indicates that this resource can be referenced as a Policy. - * - * @stability experimental - */ -export interface IPolicyRef extends constructs.IConstruct { - /** - * A reference to a Policy resource. - */ - readonly policyRef: PolicyReference; -} - -/** - * @cloudformationResource AWS::IAM::Policy - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html - */ -export class CfnPolicy extends cdk.CfnResource implements cdk.IInspectable, IPolicyRef { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Policy"; - - /** - * Build a CfnPolicy from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnPolicy { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnPolicyPropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnPolicy(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - public principalArn?: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnPolicyProps = {}) { - super(scope, id, { - "type": CfnPolicy.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.principalArn = (props.principalArn as IRoleRef)?.roleRef?.roleArn ?? (props.principalArn as IUserRef)?.userRef?.userArn ?? props.principalArn; - } - - public get policyRef(): PolicyReference { - return {}; - } - - protected get cfnProperties(): Record { - return { - "principalArn": this.principalArn - }; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnPolicy.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnPolicyPropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnPolicy\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html - */ -export interface CfnPolicyProps { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html#cfn-iam-policy-principalarn - */ - readonly principalArn?: IRoleRef | IUserRef | string; -} - -/** - * A reference to a Policy resource. - * - * @struct - * @stability external - */ -export interface PolicyReference { - -} - -/** - * Determine whether the given properties match those of a \`CfnPolicyProps\` - * - * @param properties - the TypeScript properties of a \`CfnPolicyProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnPolicyPropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("principalArn", cdk.validateString)(properties.principalArn)); - return errors.wrap("supplied properties not correct for \\"CfnPolicyProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnPolicyPropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnPolicyPropsValidator(properties).assertSuccess(); - return { - "PrincipalArn": cdk.stringToCloudFormation(properties.principalArn) - }; -} - -// @ts-ignore TS6133 -function CfnPolicyPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("principalArn", "PrincipalArn", (properties.PrincipalArn != null ? cfn_parse.FromCloudFormation.getString(properties.PrincipalArn) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -}" -`; - -exports[`resource with nested relationship requiring flattening 1`] = ` -"/* eslint-disable prettier/prettier, @stylistic/max-len */ -import * as cdk from "aws-cdk-lib"; -import * as constructs from "constructs"; -import * as cfn_parse from "aws-cdk-lib/core/lib/helpers-internal"; -import * as cdk_errors from "aws-cdk-lib/core/lib/errors"; - -/** - * Indicates that this resource can be referenced as a Role. - * - * @stability experimental - */ -export interface IRoleRef extends constructs.IConstruct { - /** - * A reference to a Role resource. - */ - readonly roleRef: RoleReference; -} - -/** - * @cloudformationResource AWS::IAM::Role - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export class CfnRole extends cdk.CfnResource implements cdk.IInspectable, IRoleRef, cdk.ITaggable { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Role"; - - /** - * Build a CfnRole from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnRole { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnRolePropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnRole(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - /** - * @cloudformationAttribute RoleArn - */ - public readonly attrRoleArn: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnRoleProps = {}) { - super(scope, id, { - "type": CfnRole.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.attrRoleArn = cdk.Token.asString(this.getAtt("RoleArn", cdk.ResolutionTypeHint.STRING)); - } - - public get roleRef(): RoleReference { - return { - "roleArn": this.attrRoleArn - }; - } - - protected get cfnProperties(): Record { - return {}; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnRole.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnRolePropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnRole\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export interface CfnRoleProps { - -} - -/** - * A reference to a Role resource. - * - * @struct - * @stability external - */ -export interface RoleReference { - /** - * The ARN of the Role resource. - */ - readonly roleArn: string; -} - -/** - * Determine whether the given properties match those of a \`CfnRoleProps\` - * - * @param properties - the TypeScript properties of a \`CfnRoleProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnRolePropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - return errors.wrap("supplied properties not correct for \\"CfnRoleProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnRolePropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnRolePropsValidator(properties).assertSuccess(); - return {}; -} - -// @ts-ignore TS6133 -function CfnRolePropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * Indicates that this resource can be referenced as a Task. - * - * @stability experimental - */ -export interface ITaskRef extends constructs.IConstruct { - /** - * A reference to a Task resource. - */ - readonly taskRef: TaskReference; -} - -/** - * @cloudformationResource AWS::IAM::Task - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-task.html - */ -export class CfnTask extends cdk.CfnResource implements cdk.IInspectable, ITaskRef { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Task"; - - /** - * Build a CfnTask from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnTask { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnTaskPropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnTask(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - public executionConfig?: CfnTask.ExecutionConfigProperty | cdk.IResolvable; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnTaskProps = {}) { - super(scope, id, { - "type": CfnTask.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.executionConfig = (props.executionConfig ? flattenCfnTaskExecutionConfigProperty(props.executionConfig) : undefined); - } - - public get taskRef(): TaskReference { - return {}; - } - - protected get cfnProperties(): Record { - return { - "executionConfig": this.executionConfig - }; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnTask.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnTaskPropsToCloudFormation(props); - } -} - -export namespace CfnTask { - /** - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-task-executionconfig.html - */ - export interface ExecutionConfigProperty { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-task-executionconfig.html#cfn-iam-task-executionconfig-rolearn - */ - readonly roleArn?: IRoleRef | string; - } -} - -/** - * Properties for defining a \`CfnTask\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-task.html - */ -export interface CfnTaskProps { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-task.html#cfn-iam-task-executionconfig - */ - readonly executionConfig?: CfnTask.ExecutionConfigProperty | cdk.IResolvable; -} - -// @ts-ignore TS6133 -function flattenCfnTaskExecutionConfigProperty(props: CfnTask.ExecutionConfigProperty | cdk.IResolvable): CfnTask.ExecutionConfigProperty | cdk.IResolvable { - if (cdk.isResolvableObject(props)) return props; - return { - "roleArn": (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? props.roleArn - }; -} - -/** - * Determine whether the given properties match those of a \`ExecutionConfigProperty\` - * - * @param properties - the TypeScript properties of a \`ExecutionConfigProperty\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnTaskExecutionConfigPropertyValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("roleArn", cdk.validateString)(properties.roleArn)); - return errors.wrap("supplied properties not correct for \\"ExecutionConfigProperty\\""); -} - -// @ts-ignore TS6133 -function convertCfnTaskExecutionConfigPropertyToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnTaskExecutionConfigPropertyValidator(properties).assertSuccess(); - return { - "RoleArn": cdk.stringToCloudFormation(properties.roleArn) - }; -} - -// @ts-ignore TS6133 -function CfnTaskExecutionConfigPropertyFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("roleArn", "RoleArn", (properties.RoleArn != null ? cfn_parse.FromCloudFormation.getString(properties.RoleArn) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * A reference to a Task resource. - * - * @struct - * @stability external - */ -export interface TaskReference { - -} - -/** - * Determine whether the given properties match those of a \`CfnTaskProps\` - * - * @param properties - the TypeScript properties of a \`CfnTaskProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnTaskPropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("executionConfig", CfnTaskExecutionConfigPropertyValidator)(properties.executionConfig)); - return errors.wrap("supplied properties not correct for \\"CfnTaskProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnTaskPropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnTaskPropsValidator(properties).assertSuccess(); - return { - "ExecutionConfig": convertCfnTaskExecutionConfigPropertyToCloudFormation(properties.executionConfig) - }; -} - -// @ts-ignore TS6133 -function CfnTaskPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("executionConfig", "ExecutionConfig", (properties.ExecutionConfig != null ? CfnTaskExecutionConfigPropertyFromCloudFormation(properties.ExecutionConfig) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -}" -`; - -exports[`resource with nested relationship with type history 1`] = ` -"/* eslint-disable prettier/prettier, @stylistic/max-len */ -import * as cdk from "aws-cdk-lib"; -import * as constructs from "constructs"; -import * as cfn_parse from "aws-cdk-lib/core/lib/helpers-internal"; -import * as cdk_errors from "aws-cdk-lib/core/lib/errors"; - -/** - * Indicates that this resource can be referenced as a Role. - * - * @stability experimental - */ -export interface IRoleRef extends constructs.IConstruct { - /** - * A reference to a Role resource. - */ - readonly roleRef: RoleReference; -} - -/** - * @cloudformationResource AWS::IAM::Role - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export class CfnRole extends cdk.CfnResource implements cdk.IInspectable, IRoleRef, cdk.ITaggable { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Role"; - - /** - * Build a CfnRole from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnRole { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnRolePropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnRole(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - /** - * @cloudformationAttribute RoleArn - */ - public readonly attrRoleArn: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnRoleProps = {}) { - super(scope, id, { - "type": CfnRole.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.attrRoleArn = cdk.Token.asString(this.getAtt("RoleArn", cdk.ResolutionTypeHint.STRING)); - } - - public get roleRef(): RoleReference { - return { - "roleArn": this.attrRoleArn - }; - } - - protected get cfnProperties(): Record { - return {}; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnRole.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnRolePropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnRole\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export interface CfnRoleProps { - -} - -/** - * A reference to a Role resource. - * - * @struct - * @stability external - */ -export interface RoleReference { - /** - * The ARN of the Role resource. - */ - readonly roleArn: string; -} - -/** - * Determine whether the given properties match those of a \`CfnRoleProps\` - * - * @param properties - the TypeScript properties of a \`CfnRoleProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnRolePropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - return errors.wrap("supplied properties not correct for \\"CfnRoleProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnRolePropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnRolePropsValidator(properties).assertSuccess(); - return {}; -} - -// @ts-ignore TS6133 -function CfnRolePropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * Indicates that this resource can be referenced as a Job. - * - * @stability experimental - */ -export interface IJobRef extends constructs.IConstruct { - /** - * A reference to a Job resource. - */ - readonly jobRef: JobReference; -} - -/** - * @cloudformationResource AWS::IAM::Job - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-job.html - */ -export class CfnJob extends cdk.CfnResource implements cdk.IInspectable, IJobRef { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Job"; - - /** - * Build a CfnJob from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnJob { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnJobPropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnJob(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - public config?: cdk.IResolvable | CfnJob.OldConfigProperty; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnJobProps = {}) { - super(scope, id, { - "type": CfnJob.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.config = (props.config ? flattenCfnJobOldConfigProperty(props.config) : undefined); - } - - public get jobRef(): JobReference { - return {}; - } - - protected get cfnProperties(): Record { - return { - "config": this.config - }; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnJob.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnJobPropsToCloudFormation(props); - } -} - -export namespace CfnJob { - /** - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-job-config.html - */ - export interface ConfigProperty { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-job-config.html#cfn-iam-job-config-rolearn - */ - readonly roleArn?: IRoleRef | string; - - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-job-config.html#cfn-iam-job-config-timeout - */ - readonly timeout?: number; - } - - /** - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-job-oldconfig.html - */ - export interface OldConfigProperty { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-job-oldconfig.html#cfn-iam-job-oldconfig-rolearn - */ - readonly roleArn?: IRoleRef | string; - } -} - -/** - * Properties for defining a \`CfnJob\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-job.html - */ -export interface CfnJobProps { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-job.html#cfn-iam-job-config - */ - readonly config?: cdk.IResolvable | CfnJob.OldConfigProperty; -} - -// @ts-ignore TS6133 -function flattenCfnJobConfigProperty(props: CfnJob.ConfigProperty | cdk.IResolvable): CfnJob.ConfigProperty | cdk.IResolvable { - if (cdk.isResolvableObject(props)) return props; - return { - "roleArn": (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? props.roleArn, - "timeout": props.timeout - }; -} - -/** - * Determine whether the given properties match those of a \`ConfigProperty\` - * - * @param properties - the TypeScript properties of a \`ConfigProperty\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnJobConfigPropertyValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("roleArn", cdk.validateString)(properties.roleArn)); - errors.collect(cdk.propertyValidator("timeout", cdk.validateNumber)(properties.timeout)); - return errors.wrap("supplied properties not correct for \\"ConfigProperty\\""); -} - -// @ts-ignore TS6133 -function convertCfnJobConfigPropertyToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnJobConfigPropertyValidator(properties).assertSuccess(); - return { - "RoleArn": cdk.stringToCloudFormation(properties.roleArn), - "Timeout": cdk.numberToCloudFormation(properties.timeout) - }; -} - -// @ts-ignore TS6133 -function CfnJobConfigPropertyFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("roleArn", "RoleArn", (properties.RoleArn != null ? cfn_parse.FromCloudFormation.getString(properties.RoleArn) : undefined)); - ret.addPropertyResult("timeout", "Timeout", (properties.Timeout != null ? cfn_parse.FromCloudFormation.getNumber(properties.Timeout) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -// @ts-ignore TS6133 -function flattenCfnJobOldConfigProperty(props: cdk.IResolvable | CfnJob.OldConfigProperty): cdk.IResolvable | CfnJob.OldConfigProperty { - if (cdk.isResolvableObject(props)) return props; - return { - "roleArn": (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? props.roleArn - }; -} - -/** - * Determine whether the given properties match those of a \`OldConfigProperty\` - * - * @param properties - the TypeScript properties of a \`OldConfigProperty\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnJobOldConfigPropertyValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("roleArn", cdk.validateString)(properties.roleArn)); - return errors.wrap("supplied properties not correct for \\"OldConfigProperty\\""); -} - -// @ts-ignore TS6133 -function convertCfnJobOldConfigPropertyToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnJobOldConfigPropertyValidator(properties).assertSuccess(); - return { - "RoleArn": cdk.stringToCloudFormation(properties.roleArn) - }; -} - -// @ts-ignore TS6133 -function CfnJobOldConfigPropertyFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("roleArn", "RoleArn", (properties.RoleArn != null ? cfn_parse.FromCloudFormation.getString(properties.RoleArn) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * A reference to a Job resource. - * - * @struct - * @stability external - */ -export interface JobReference { - -} - -/** - * Determine whether the given properties match those of a \`CfnJobProps\` - * - * @param properties - the TypeScript properties of a \`CfnJobProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnJobPropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("config", CfnJobOldConfigPropertyValidator)(properties.config)); - return errors.wrap("supplied properties not correct for \\"CfnJobProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnJobPropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnJobPropsValidator(properties).assertSuccess(); - return { - "Config": convertCfnJobOldConfigPropertyToCloudFormation(properties.config) - }; -} - -// @ts-ignore TS6133 -function CfnJobPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("config", "Config", (properties.Config != null ? CfnJobOldConfigPropertyFromCloudFormation(properties.Config) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -}" -`; - -exports[`resource with relationship reference 1`] = ` -"/* eslint-disable prettier/prettier, @stylistic/max-len */ -import * as cdk from "aws-cdk-lib"; -import * as constructs from "constructs"; -import * as cfn_parse from "aws-cdk-lib/core/lib/helpers-internal"; -import * as cdk_errors from "aws-cdk-lib/core/lib/errors"; - -/** - * Indicates that this resource can be referenced as a Role. - * - * @stability experimental - */ -export interface IRoleRef extends constructs.IConstruct { - /** - * A reference to a Role resource. - */ - readonly roleRef: RoleReference; -} - -/** - * @cloudformationResource AWS::IAM::Role - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export class CfnRole extends cdk.CfnResource implements cdk.IInspectable, IRoleRef, cdk.ITaggable { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Role"; - - /** - * Build a CfnRole from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnRole { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnRolePropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnRole(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - /** - * @cloudformationAttribute RoleArn - */ - public readonly attrRoleArn: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnRoleProps = {}) { - super(scope, id, { - "type": CfnRole.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.attrRoleArn = cdk.Token.asString(this.getAtt("RoleArn", cdk.ResolutionTypeHint.STRING)); - } - - public get roleRef(): RoleReference { - return { - "roleArn": this.attrRoleArn - }; - } - - protected get cfnProperties(): Record { - return {}; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnRole.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnRolePropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnRole\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html - */ -export interface CfnRoleProps { - -} - -/** - * A reference to a Role resource. - * - * @struct - * @stability external - */ -export interface RoleReference { - /** - * The ARN of the Role resource. - */ - readonly roleArn: string; -} - -/** - * Determine whether the given properties match those of a \`CfnRoleProps\` - * - * @param properties - the TypeScript properties of a \`CfnRoleProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnRolePropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - return errors.wrap("supplied properties not correct for \\"CfnRoleProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnRolePropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnRolePropsValidator(properties).assertSuccess(); - return {}; -} - -// @ts-ignore TS6133 -function CfnRolePropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -} - -/** - * Indicates that this resource can be referenced as a Function. - * - * @stability experimental - */ -export interface IFunctionRef extends constructs.IConstruct { - /** - * A reference to a Function resource. - */ - readonly functionRef: FunctionReference; -} - -/** - * @cloudformationResource AWS::IAM::Function - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-function.html - */ -export class CfnFunction extends cdk.CfnResource implements cdk.IInspectable, IFunctionRef { - /** - * The CloudFormation resource type name for this resource class. - */ - public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::IAM::Function"; - - /** - * Build a CfnFunction from CloudFormation properties - * - * A factory method that creates a new instance of this class from an object - * containing the CloudFormation properties of this resource. - * Used in the @aws-cdk/cloudformation-include module. - * - * @internal - */ - public static _fromCloudFormation(scope: constructs.Construct, id: string, resourceAttributes: any, options: cfn_parse.FromCloudFormationOptions): CfnFunction { - resourceAttributes = resourceAttributes || {}; - const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); - const propsResult = CfnFunctionPropsFromCloudFormation(resourceProperties); - if (cdk.isResolvableObject(propsResult.value)) { - throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); - } - const ret = new CfnFunction(scope, id, propsResult.value); - for (const [propKey, propVal] of Object.entries(propsResult.extraProperties)) { - ret.addPropertyOverride(propKey, propVal); - } - options.parser.handleAttributes(ret, resourceAttributes, id); - return ret; - } - - public roleArn?: string; - - /** - * @param scope Scope in which this resource is defined - * @param id Construct identifier for this resource (unique in its scope) - * @param props Resource properties - */ - public constructor(scope: constructs.Construct, id: string, props: CfnFunctionProps = {}) { - super(scope, id, { - "type": CfnFunction.CFN_RESOURCE_TYPE_NAME, - "properties": props - }); - - this.roleArn = (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? props.roleArn; - } - - public get functionRef(): FunctionReference { - return {}; - } - - protected get cfnProperties(): Record { - return { - "roleArn": this.roleArn - }; - } - - /** - * Examines the CloudFormation resource and discloses attributes - * - * @param inspector tree inspector to collect and process attributes - */ - public inspect(inspector: cdk.TreeInspector): void { - inspector.addAttribute("aws:cdk:cloudformation:type", CfnFunction.CFN_RESOURCE_TYPE_NAME); - inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); - } - - protected renderProperties(props: Record): Record { - return convertCfnFunctionPropsToCloudFormation(props); - } -} - -/** - * Properties for defining a \`CfnFunction\` - * - * @struct - * @stability external - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-function.html - */ -export interface CfnFunctionProps { - /** - * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-function.html#cfn-iam-function-rolearn - */ - readonly roleArn?: IRoleRef | string; -} - -/** - * A reference to a Function resource. - * - * @struct - * @stability external - */ -export interface FunctionReference { - -} - -/** - * Determine whether the given properties match those of a \`CfnFunctionProps\` - * - * @param properties - the TypeScript properties of a \`CfnFunctionProps\` - * - * @returns the result of the validation. - */ -// @ts-ignore TS6133 -function CfnFunctionPropsValidator(properties: any): cdk.ValidationResult { - if (!cdk.canInspect(properties)) return cdk.VALIDATION_SUCCESS; - const errors = new cdk.ValidationResults(); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - errors.collect(new cdk.ValidationResult("Expected an object, but received: " + JSON.stringify(properties))); - } - errors.collect(cdk.propertyValidator("roleArn", cdk.validateString)(properties.roleArn)); - return errors.wrap("supplied properties not correct for \\"CfnFunctionProps\\""); -} - -// @ts-ignore TS6133 -function convertCfnFunctionPropsToCloudFormation(properties: any): any { - if (!cdk.canInspect(properties)) return properties; - CfnFunctionPropsValidator(properties).assertSuccess(); - return { - "RoleArn": cdk.stringToCloudFormation(properties.roleArn) - }; -} - -// @ts-ignore TS6133 -function CfnFunctionPropsFromCloudFormation(properties: any): cfn_parse.FromCloudFormationResult { - if (cdk.isResolvableObject(properties)) { - return new cfn_parse.FromCloudFormationResult(properties); - } - properties = ((properties == null) ? {} : properties); - if (!(properties && typeof properties == 'object' && !Array.isArray(properties))) { - return new cfn_parse.FromCloudFormationResult(properties); - } - const ret = new cfn_parse.FromCloudFormationPropertyObject(); - ret.addPropertyResult("roleArn", "RoleArn", (properties.RoleArn != null ? cfn_parse.FromCloudFormation.getString(properties.RoleArn) : undefined)); - ret.addUnrecognizedPropertiesAsExtra(properties); - return ret; -}" -`; diff --git a/tools/@aws-cdk/spec2cdk/test/relationships.test.ts b/tools/@aws-cdk/spec2cdk/test/relationships.test.ts deleted file mode 100644 index dd2a4141ec5cb..0000000000000 --- a/tools/@aws-cdk/spec2cdk/test/relationships.test.ts +++ /dev/null @@ -1,331 +0,0 @@ -import { Service, SpecDatabase, emptyDatabase } from '@aws-cdk/service-spec-types'; -import { TypeScriptRenderer } from '@cdklabs/typewriter'; -import { AstBuilder } from '../lib/cdk/ast'; - -const renderer = new TypeScriptRenderer(); -let db: SpecDatabase; -let service: Service; - -beforeEach(() => { - db = emptyDatabase(); - - service = db.allocate('service', { - name: 'aws-iam', - shortName: 'iam', - capitalized: 'IAM', - cloudFormationNamespace: 'AWS::IAM', - }); -}); - -test('resource with relationship reference', () => { - // Target resource - const targetResource = db.allocate('resource', { - name: 'Role', - attributes: { - RoleArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::Role', - }); - db.link('hasResource', service, targetResource); - - // Source resource with relationship - const sourceResource = db.allocate('resource', { - name: 'Function', - attributes: {}, - properties: { - RoleArn: { - type: { type: 'string' }, - relationshipRefs: [{ - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }], - }, - }, - cloudFormationType: 'AWS::IAM::Function', - }); - db.link('hasResource', service, sourceResource); - - const ast = AstBuilder.forService(service, { db }); - const rendered = renderer.render(ast.module); - - expect(rendered).toMatchSnapshot(); -}); - -test('resource with multiple relationship references', () => { - // Target resource 1 - const roleResource = db.allocate('resource', { - name: 'Role', - attributes: { - RoleArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::Role', - }); - db.link('hasResource', service, roleResource); - - // Target resource 2 - const userResource = db.allocate('resource', { - name: 'User', - attributes: { - UserArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::User', - }); - db.link('hasResource', service, userResource); - - // Source resource with multiple relationships - const policyResource = db.allocate('resource', { - name: 'Policy', - attributes: {}, - properties: { - PrincipalArn: { - type: { type: 'string' }, - relationshipRefs: [ - { - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }, - { - cloudFormationType: 'AWS::IAM::User', - propertyName: 'UserArn', - }, - ], - }, - }, - cloudFormationType: 'AWS::IAM::Policy', - }); - db.link('hasResource', service, policyResource); - - const ast = AstBuilder.forService(service, { db }); - const rendered = renderer.render(ast.module); - - expect(rendered).toMatchSnapshot(); -}); - -test('resource with nested relationship requiring flattening', () => { - // Target resource - const roleResource = db.allocate('resource', { - name: 'Role', - attributes: { - RoleArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::Role', - }); - db.link('hasResource', service, roleResource); - - // Type definition with relationship - const configType = db.allocate('typeDefinition', { - name: 'ExecutionConfig', - properties: { - RoleArn: { - type: { type: 'string' }, - relationshipRefs: [{ - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }], - }, - }, - }); - - // Source resource with nested property - const taskResource = db.allocate('resource', { - name: 'Task', - attributes: {}, - properties: { - ExecutionConfig: { - type: { type: 'ref', reference: { $ref: configType.$id } }, - }, - }, - cloudFormationType: 'AWS::IAM::Task', - }); - db.link('hasResource', service, taskResource); - db.link('usesType', taskResource, configType); - - const ast = AstBuilder.forService(service, { db }); - const rendered = renderer.render(ast.module); - - expect(rendered).toMatchSnapshot(); -}); - -test('resource with array of nested properties with relationship', () => { - // Target resource - const roleResource = db.allocate('resource', { - name: 'Role', - attributes: { - RoleArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::Role', - }); - db.link('hasResource', service, roleResource); - - // Type definition with relationship - const permissionType = db.allocate('typeDefinition', { - name: 'Permission', - properties: { - RoleArn: { - type: { type: 'string' }, - relationshipRefs: [{ - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }], - }, - }, - }); - - // Source resource with array of nested properties - const resourceResource = db.allocate('resource', { - name: 'Resource', - attributes: {}, - properties: { - Permissions: { - type: { type: 'array', element: { type: 'ref', reference: { $ref: permissionType.$id } } }, - }, - }, - cloudFormationType: 'AWS::IAM::Resource', - }); - db.link('hasResource', service, resourceResource); - db.link('usesType', resourceResource, permissionType); - - const ast = AstBuilder.forService(service, { db }); - const rendered = renderer.render(ast.module); - - expect(rendered).toMatchSnapshot(); -}); - -test('resource with nested relationship with type history', () => { - // Target resource - const roleResource = db.allocate('resource', { - name: 'Role', - attributes: { - RoleArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::Role', - }); - db.link('hasResource', service, roleResource); - - // Old type definition - const oldConfigType = db.allocate('typeDefinition', { - name: 'OldConfig', - properties: { - RoleArn: { - type: { type: 'string' }, - relationshipRefs: [{ - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }], - }, - }, - }); - - // New type definition - const configType = db.allocate('typeDefinition', { - name: 'Config', - properties: { - RoleArn: { - type: { type: 'string' }, - relationshipRefs: [{ - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }], - }, - Timeout: { - type: { type: 'integer' }, - }, - }, - }); - - // Source resource - const jobResource = db.allocate('resource', { - name: 'Job', - attributes: {}, - properties: { - Config: { - type: { type: 'ref', reference: { $ref: configType.$id } }, - previousTypes: [{ type: 'ref', reference: { $ref: oldConfigType.$id } }], - }, - }, - cloudFormationType: 'AWS::IAM::Job', - }); - db.link('hasResource', service, jobResource); - db.link('usesType', jobResource, configType); - db.link('usesType', jobResource, oldConfigType); - - const ast = AstBuilder.forService(service, { db }); - const rendered = renderer.render(ast.module); - - expect(rendered).toMatchSnapshot(); -}); - -test('relationship have arns appear first in the constructor chain', () => { - // Target resource - const roleResource = db.allocate('resource', { - name: 'Role', - primaryIdentifier: ['RoleName', 'OtherPrimaryId'], - attributes: { - RoleArn: { - type: { type: 'string' }, - }, - }, - properties: {}, - cloudFormationType: 'AWS::IAM::Role', - }); - db.link('hasResource', service, roleResource); - - // Type definition with relationship - const configType = db.allocate('typeDefinition', { - name: 'ExecutionConfig', - properties: { - RoleArn: { - type: { type: 'string' }, - relationshipRefs: [{ - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleName', - }, { - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'RoleArn', - }, { - cloudFormationType: 'AWS::IAM::Role', - propertyName: 'OtherPrimaryId', - }], - }, - }, - }); - - // Source resource with nested property - const taskResource = db.allocate('resource', { - name: 'Task', - attributes: {}, - properties: { - ExecutionConfig: { - type: { type: 'ref', reference: { $ref: configType.$id } }, - }, - }, - cloudFormationType: 'AWS::IAM::Task', - }); - db.link('hasResource', service, taskResource); - db.link('usesType', taskResource, configType); - - const ast = AstBuilder.forService(service, { db }); - const rendered = renderer.render(ast.module); - - const chain = '"roleArn": (props.roleArn as IRoleRef)?.roleRef?.roleArn ?? (props.roleArn as IRoleRef)?.roleRef?.roleName ?? (props.roleArn as IRoleRef)?.roleRef?.otherPrimaryId ?? props.roleArn'; - - expect(rendered).toContain(chain); -});