From 09fc592f8debaf0b66ad5bec57d677ab776c0d6f Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Thu, 2 Oct 2025 11:53:01 +0200 Subject: [PATCH 1/2] feat: introduce reference interfaces, but don't require them yet This introduces the `IXxxRef` interfaces from https://github.com/aws/aws-cdk/pull/35032, without actually having the L2s extend them yet. This avoids introducing the implementation burden of them to the L2 interfaces, but does allow pre-implementation in advance of their requirement. Fix a bunch of build problems --- ...integ.pipeline-elastic-beanstalk-deploy.ts | 17 +- .../test/databrew/integ.start-job-run.ts | 3 + packages/@aws-cdk/aws-ec2-alpha/awslint.json | 20 +- .../@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts | 13 + .../@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts | 7 + .../aws-cdk-lib/aws-apigateway/lib/api-key.ts | 10 +- .../aws-apigateway/lib/domain-name.ts | 17 +- .../aws-apigateway/lib/gateway-response.ts | 19 +- .../aws-apigateway/lib/resource.ts | 13 +- .../aws-cdk-lib/aws-apigateway/lib/restapi.ts | 10 +- .../aws-cdk-lib/aws-apigateway/lib/stage.ts | 11 +- .../aws-apigateway/lib/usage-plan.ts | 10 +- .../aws-apigateway/lib/vpc-link.ts | 10 +- .../aws-cloudfront/lib/cache-policy.ts | 19 +- .../aws-cloudfront/lib/distribution.ts | 9 +- .../lib/experimental/edge-function.ts | 13 + .../aws-cloudfront/lib/function.ts | 12 +- .../aws-cloudfront/lib/key-group.ts | 10 +- .../aws-cloudfront/lib/key-value-store.ts | 12 +- .../lib/origin-request-policy.ts | 18 +- .../aws-cloudfront/lib/realtime-log-config.ts | 6 +- .../lib/response-headers-policy.ts | 24 +- .../aws-cloudfront/lib/web-distribution.ts | 8 +- .../aws-cloudfront/test/function.test.ts | 19 + .../lib/elastic-beanstalk/deploy-action.ts | 11 +- .../aws-cdk-lib/aws-ec2/lib/bastion-host.ts | 7 + .../aws-ec2/lib/client-vpn-endpoint-types.ts | 3 +- .../aws-ec2/lib/client-vpn-endpoint.ts | 14 +- packages/aws-cdk-lib/aws-ec2/lib/instance.ts | 10 +- packages/aws-cdk-lib/aws-ec2/lib/key-pair.ts | 16 +- .../aws-ec2/lib/launch-template.ts | 20 +- .../aws-cdk-lib/aws-ec2/lib/network-acl.ts | 29 +- .../aws-ec2/lib/placement-group.ts | 15 +- .../aws-cdk-lib/aws-ec2/lib/prefix-list.ts | 26 +- .../aws-cdk-lib/aws-ec2/lib/security-group.ts | 23 +- packages/aws-cdk-lib/aws-ec2/lib/volume.ts | 10 +- .../aws-ec2/lib/vpc-endpoint-service.ts | 13 +- .../aws-cdk-lib/aws-ec2/lib/vpc-endpoint.ts | 15 +- .../aws-cdk-lib/aws-ec2/lib/vpc-flow-logs.ts | 10 +- packages/aws-cdk-lib/aws-ec2/lib/vpc.ts | 23 +- packages/aws-cdk-lib/aws-ec2/lib/vpn.ts | 15 +- .../aws-cdk-lib/aws-iam/lib/access-key.ts | 6 +- packages/aws-cdk-lib/aws-iam/lib/group.ts | 11 +- .../aws-iam/lib/instance-profile.ts | 13 +- packages/aws-cdk-lib/aws-iam/lib/lazy-role.ts | 5 + .../aws-cdk-lib/aws-iam/lib/managed-policy.ts | 30 +- .../aws-iam/lib/oidc-provider-native.ts | 16 +- .../aws-cdk-lib/aws-iam/lib/oidc-provider.ts | 23 +- packages/aws-cdk-lib/aws-iam/lib/policy.ts | 15 +- .../aws-iam/lib/private/immutable-role.ts | 8 + .../aws-iam/lib/private/imported-role.ts | 20 +- .../aws-iam/lib/private/precreated-role.ts | 8 + packages/aws-cdk-lib/aws-iam/lib/role.ts | 11 +- .../aws-cdk-lib/aws-iam/lib/saml-provider.ts | 11 +- packages/aws-cdk-lib/aws-iam/lib/user.ts | 18 +- packages/aws-cdk-lib/aws-kms/lib/alias.ts | 33 +- packages/aws-cdk-lib/aws-kms/lib/key.ts | 14 +- .../aws-cdk-lib/aws-kms/test/alias.test.ts | 13 + packages/aws-cdk-lib/aws-lambda/lib/alias.ts | 16 +- .../aws-lambda/lib/code-signing-config.ts | 16 +- .../aws-lambda/lib/event-source-mapping.ts | 18 +- .../aws-lambda/lib/function-base.ts | 28 +- .../aws-lambda/lib/lambda-version.ts | 22 +- packages/aws-cdk-lib/aws-lambda/lib/layers.ts | 15 +- .../aws-cdk-lib/aws-lambda/test/alias.test.ts | 20 + .../aws-lambda/test/function.test.ts | 15 + .../aws-lambda/test/lambda-version.test.ts | 18 + .../aws-logs/test/loggroup.test.ts | 27 + .../aws-cdk-lib/aws-s3/lib/bucket-policy.ts | 8 +- packages/aws-cdk-lib/aws-s3/lib/bucket.ts | 19 +- packages/aws-cdk-lib/awslint.json | 63 +- packages/awslint/lib/rules/core-types.ts | 9 +- packages/awslint/lib/rules/docs.ts | 4 + packages/awslint/lib/rules/resource.ts | 6 + tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts | 1 + .../spec2cdk/lib/cdk/resource-class.ts | 75 +- .../spec2cdk/lib/cdk/resource-decider.ts | 111 +- .../spec2cdk/lib/naming/conventions.ts | 23 + .../test/__snapshots__/resources.test.ts.snap | 1577 +++++++++++++++++ .../spec2cdk/test/conventions.test.ts | 13 + .../@aws-cdk/spec2cdk/test/resources.test.ts | 273 +++ yarn.lock | 7 - 82 files changed, 3044 insertions(+), 165 deletions(-) create mode 100644 tools/@aws-cdk/spec2cdk/test/__snapshots__/resources.test.ts.snap create mode 100644 tools/@aws-cdk/spec2cdk/test/conventions.test.ts create mode 100644 tools/@aws-cdk/spec2cdk/test/resources.test.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-elastic-beanstalk-deploy.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-elastic-beanstalk-deploy.ts index ef7e08a3123b5..4f747a16c7552 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-elastic-beanstalk-deploy.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-codepipeline-actions/test/integ.pipeline-elastic-beanstalk-deploy.ts @@ -4,7 +4,7 @@ import * as elasticbeanstalk from 'aws-cdk-lib/aws-elasticbeanstalk'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as deploy from 'aws-cdk-lib/aws-s3-deployment'; -import { App, Fn, RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { App, AssumptionError, Fn, RemovalPolicy, Stack } from 'aws-cdk-lib'; import * as integ from '@aws-cdk/integ-tests-alpha'; import * as cpactions from 'aws-cdk-lib/aws-codepipeline-actions'; @@ -49,9 +49,15 @@ const serviceRole = new iam.Role(stack, 'service-role', { managedPolicies: [ { managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth', + get node(): any { + throw new AssumptionError('Cannot reference node here'); + }, }, { managedPolicyArn: 'arn:aws:iam::aws:policy/AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy', + get node(): any { + throw new AssumptionError('Cannot reference node here'); + }, }, ], }); @@ -62,12 +68,21 @@ const instanceProfileRole = new iam.Role(stack, 'instance-profile-role', { managedPolicies: [ { managedPolicyArn: 'arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier', + get node(): any { + throw new AssumptionError('Cannot reference node here'); + }, }, { managedPolicyArn: 'arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker', + get node(): any { + throw new AssumptionError('Cannot reference node here'); + }, }, { managedPolicyArn: 'arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier', + get node(): any { + throw new AssumptionError('Cannot reference node here'); + }, }, ], }); diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts index 4d4634c39ea84..3ce1741f82e5b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-stepfunctions-tasks/test/databrew/integ.start-job-run.ts @@ -24,6 +24,9 @@ class GlueDataBrewJobStack extends cdk.Stack { const role = new iam.Role(this, 'DataBrew Role', { managedPolicies: [{ managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSGlueDataBrewServiceRole', + get node(): any { + throw new cdk.AssumptionError('Cannot reference node here'); + }, }], path: '/', assumedBy: new iam.ServicePrincipal('databrew.amazonaws.com'), diff --git a/packages/@aws-cdk/aws-ec2-alpha/awslint.json b/packages/@aws-cdk/aws-ec2-alpha/awslint.json index 23608305b3668..e2167a076c1d4 100644 --- a/packages/@aws-cdk/aws-ec2-alpha/awslint.json +++ b/packages/@aws-cdk/aws-ec2-alpha/awslint.json @@ -1,10 +1,12 @@ { - "exclude": [ - "attribute-tag:@aws-cdk/aws-ec2-alpha.RouteTable.routeTableId", - "from-method:@aws-cdk/aws-ec2-alpha.Route", - "from-method:@aws-cdk/aws-ec2-alpha.TransitGateway", - "from-method:@aws-cdk/aws-ec2-alpha.TransitGatewayRouteTableAssociation", - "from-method:@aws-cdk/aws-ec2-alpha.TransitGatewayRouteTablePropagation", - "from-method:@aws-cdk/aws-ec2-alpha.TransitGatewayVpcAttachment" - ] -} \ No newline at end of file + "exclude": [ + "attribute-tag:@aws-cdk/aws-ec2-alpha.RouteTable.routeTableId", + "from-method:@aws-cdk/aws-ec2-alpha.Route", + "from-method:@aws-cdk/aws-ec2-alpha.TransitGateway", + "from-method:@aws-cdk/aws-ec2-alpha.TransitGatewayRouteTableAssociation", + "from-method:@aws-cdk/aws-ec2-alpha.TransitGatewayRouteTablePropagation", + "from-method:@aws-cdk/aws-ec2-alpha.TransitGatewayVpcAttachment", + "docs-public-apis:@aws-cdk/aws-ec2-alpha.SubnetV2.subnetRef", + "docs-public-apis:@aws-cdk/aws-ec2-alpha.VpcV2Base.vpcRef" + ] +} diff --git a/packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts b/packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts index 08ac4b2f42002..3171c6791a519 100644 --- a/packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts +++ b/packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts @@ -6,6 +6,7 @@ import { CidrBlock, CidrBlockIpv6, defaultSubnetName } from './util'; import { RouteTable } from './route'; import { addConstructMetadata, MethodMetadata } from 'aws-cdk-lib/core/lib/metadata-resource'; import { propertyInjectable } from 'aws-cdk-lib/core/lib/prop-injectable'; +import { SubnetReference } from 'aws-cdk-lib/aws-ec2/lib/ec2.generated'; /** * Interface to define subnet CIDR @@ -194,6 +195,12 @@ export class SubnetV2 extends Resource implements ISubnetV2 { */ public readonly routeTable: IRouteTable = { routeTableId: attrs.routeTableId! }; + public get subnetRef(): SubnetReference { + return { + subnetId: this.subnetId, + }; + } + /** * Associate a Network ACL with this subnet * Required here since it is implemented in the ISubnetV2 @@ -245,6 +252,12 @@ export class SubnetV2 extends Resource implements ISubnetV2 { */ public readonly ipv6CidrBlock?: string; + public get subnetRef(): SubnetReference { + return { + subnetId: this.subnetId, + }; + } + /** * The type of subnet (public or private) that this subnet represents. * @attribute SubnetType diff --git a/packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts b/packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts index 2b5fd5877ceba..974e89e9eb4f4 100644 --- a/packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts +++ b/packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts @@ -6,6 +6,7 @@ import { EgressOnlyInternetGateway, InternetGateway, NatConnectivityType, NatGat import { ISubnetV2 } from './subnet-v2'; import { AccountPrincipal, Effect, PolicyStatement, Role } from 'aws-cdk-lib/aws-iam'; import { IVPCCidrBlock } from './vpc-v2'; +import { VPCReference } from 'aws-cdk-lib/aws-ec2/lib/ec2.generated'; /** * Options to define EgressOnlyInternetGateway for VPC @@ -327,6 +328,12 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 { }; } + public get vpcRef(): VPCReference { + return { + vpcId: this.vpcId, + }; + } + /** * Adds a VPN Gateway to this VPC * @deprecated use enableVpnGatewayV2 for compatibility with VPCV2.Route diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/api-key.ts b/packages/aws-cdk-lib/aws-apigateway/lib/api-key.ts index 4b64d47d74e88..476fbfc88e3ec 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/api-key.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/api-key.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnApiKey } from './apigateway.generated'; +import { ApiKeyReference, CfnApiKey, IApiKeyRef } from './apigateway.generated'; import { ResourceOptions } from './resource'; import { IRestApi } from './restapi'; import { IStage } from './stage'; @@ -14,7 +14,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; * API keys are alphanumeric string values that you distribute to * app developer customers to grant access to your API */ -export interface IApiKey extends IResourceBase { +export interface IApiKey extends IResourceBase, IApiKeyRef { /** * The API key ID. * @attribute @@ -138,6 +138,12 @@ abstract class ApiKeyBase extends Resource implements IApiKey { resourceArns: [this.keyArn], }); } + + public get apiKeyRef(): ApiKeyReference { + return { + apiKeyId: this.keyId, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/domain-name.ts b/packages/aws-cdk-lib/aws-apigateway/lib/domain-name.ts index f55bf1a809e29..947aa702669ee 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/domain-name.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/domain-name.ts @@ -1,12 +1,12 @@ import { Construct } from 'constructs'; -import { CfnDomainName } from './apigateway.generated'; +import { CfnDomainName, DomainNameReference, IDomainNameRef } from './apigateway.generated'; import { BasePathMapping, BasePathMappingOptions } from './base-path-mapping'; import { EndpointType, IRestApi } from './restapi'; import { IStage } from './stage'; import * as apigwv2 from '../../aws-apigatewayv2'; import * as acm from '../../aws-certificatemanager'; import { IBucket } from '../../aws-s3'; -import { IResource, Names, Resource, Token } from '../../core'; +import { Arn, IResource, Names, Resource, Stack, Token } from '../../core'; import { ValidationError } from '../../core/lib/errors'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -94,7 +94,7 @@ export interface DomainNameProps extends DomainNameOptions { readonly mapping?: IRestApi; } -export interface IDomainName extends IResource { +export interface IDomainName extends IResource, IDomainNameRef { /** * The domain name (e.g. `example.com`) * @@ -132,12 +132,22 @@ export class DomainName extends Resource implements IDomainName { public readonly domainName = attrs.domainName; public readonly domainNameAliasDomainName = attrs.domainNameAliasTarget; public readonly domainNameAliasHostedZoneId = attrs.domainNameAliasHostedZoneId; + + public readonly domainNameRef = { + domainName: this.domainName, + domainNameArn: Arn.format({ + service: 'apigateway', + resource: 'domainnames', + resourceName: attrs.domainName, + }, Stack.of(scope)), + }; } return new Import(scope, id); } public readonly domainName: string; + public readonly domainNameRef: DomainNameReference; public readonly domainNameAliasDomainName: string; public readonly domainNameAliasHostedZoneId: string; private readonly basePaths = new Set(); @@ -168,6 +178,7 @@ export class DomainName extends Resource implements IDomainName { }); this.domainName = resource.ref; + this.domainNameRef = resource.domainNameRef; this.domainNameAliasDomainName = edge ? resource.attrDistributionDomainName diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/gateway-response.ts b/packages/aws-cdk-lib/aws-apigateway/lib/gateway-response.ts index 7eb62ee350e2f..76ccfa3f4f627 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/gateway-response.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/gateway-response.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnGatewayResponse, CfnGatewayResponseProps } from './apigateway.generated'; +import { CfnGatewayResponse, CfnGatewayResponseProps, GatewayResponseReference, IGatewayResponseRef } from './apigateway.generated'; import { IRestApi } from './restapi'; import { IResource, Resource } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -8,7 +8,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents gateway response resource. */ -export interface IGatewayResponse extends IResource { +export interface IGatewayResponse extends IResource, IGatewayResponseRef { } /** @@ -61,6 +61,20 @@ export class GatewayResponse extends Resource implements IGatewayResponse { /** Uniquely identifies this class. */ public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-apigateway.GatewayResponse'; + /** + * Reference an existing GatewayResponse given a gateway response ID. + */ + public static fromGatewayResponseId(scope: Construct, id: string, gatewayResponseId: string): IGatewayResponse { + class Import extends Resource implements IGatewayResponse { + public readonly gatewayResponseRef = { + gatewayResponseId: gatewayResponseId, + }; + } + return new Import(scope, id); + } + + public readonly gatewayResponseRef: GatewayResponseReference; + constructor(scope: Construct, id: string, props: GatewayResponseProps) { super(scope, id); // Enhanced CDK Analytics Telemetry @@ -86,6 +100,7 @@ export class GatewayResponse extends Resource implements IGatewayResponse { }); } + this.gatewayResponseRef = resource.gatewayResponseRef; this.node.defaultChild = resource; } diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts b/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts index 6354f91908e72..0ab22237c352f 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/resource.ts @@ -1,16 +1,16 @@ import { Construct } from 'constructs'; -import { CfnResource, CfnResourceProps } from './apigateway.generated'; +import { CfnResource, CfnResourceProps, IResourceRef, ResourceReference } from './apigateway.generated'; import { Cors, CorsOptions } from './cors'; import { Integration } from './integration'; import { MockIntegration } from './integrations'; -import { Method, MethodOptions, AuthorizationType } from './method'; +import { AuthorizationType, Method, MethodOptions } from './method'; import { IRestApi, RestApi } from './restapi'; import { IResource as IResourceBase, Resource as ResourceConstruct } from '../../core'; import { ValidationError } from '../../core/lib/errors'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; -export interface IResource extends IResourceBase { +export interface IResource extends IResourceBase, IResourceRef { /** * The parent of this resource or undefined for the root resource. */ @@ -383,6 +383,13 @@ export abstract class ResourceBase extends ResourceConstruct implements IResourc public get url(): string { return this.restApi.urlForPath(this.path); } + + public get resourceRef(): ResourceReference { + return { + resourceId: this.resourceId, + restApiId: this.api.restApiId, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/restapi.ts b/packages/aws-cdk-lib/aws-apigateway/lib/restapi.ts index b008a77e3c19e..0bebe1dd599b1 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/restapi.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/restapi.ts @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import { ApiDefinition } from './api-definition'; import { ApiKey, ApiKeyOptions, IApiKey } from './api-key'; import { ApiGatewayMetrics } from './apigateway-canned-metrics.generated'; -import { CfnAccount, CfnRestApi } from './apigateway.generated'; +import { CfnAccount, CfnRestApi, IRestApiRef, RestApiReference } from './apigateway.generated'; import { CorsOptions } from './cors'; import { Deployment } from './deployment'; import { DomainName, DomainNameOptions } from './domain-name'; @@ -27,7 +27,7 @@ import { APIGATEWAY_DISABLE_CLOUDWATCH_ROLE } from '../../cx-api'; const RESTAPI_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.RestApiBase'); const APIGATEWAY_RESTAPI_SYMBOL = Symbol.for('@aws-cdk/aws-apigateway.RestApi'); -export interface IRestApi extends IResourceBase { +export interface IRestApi extends IResourceBase, IRestApiRef { /** * The ID of this API Gateway RestApi. * @attribute @@ -738,6 +738,12 @@ export abstract class RestApiBase extends Resource implements IRestApi, iam.IRes ...props, }).attachTo(this); } + + public get restApiRef(): RestApiReference { + return { + restApiId: this.restApiId, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/stage.ts b/packages/aws-cdk-lib/aws-apigateway/lib/stage.ts index 1e519e97efe6b..25bc06982c93e 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/stage.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/stage.ts @@ -2,7 +2,7 @@ import { Construct } from 'constructs'; import { AccessLogFormat, IAccessLogDestination } from './access-log'; import { IApiKey, ApiKeyOptions, ApiKey } from './api-key'; import { ApiGatewayMetrics } from './apigateway-canned-metrics.generated'; -import { CfnStage } from './apigateway.generated'; +import { CfnStage, IStageRef, StageReference } from './apigateway.generated'; import { Deployment } from './deployment'; import { IRestApi, RestApiBase } from './restapi'; import { parseMethodOptionsPath } from './util'; @@ -15,7 +15,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents an APIGateway Stage. */ -export interface IStage extends IResource { +export interface IStage extends IResource, IStageRef { /** * Name of this stage. * @attribute @@ -357,6 +357,13 @@ export abstract class StageBase extends Resource implements IStage { ...props, }).attachTo(this); } + + public get stageRef(): StageReference { + return { + stageName: this.stageName, + restApiId: this.restApi.restApiId, + }; + } } @propertyInjectable diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/usage-plan.ts b/packages/aws-cdk-lib/aws-apigateway/lib/usage-plan.ts index 97f516d629f7c..90ed23e22dee8 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/usage-plan.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/usage-plan.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { IApiKey } from './api-key'; -import { CfnUsagePlan, CfnUsagePlanKey } from './apigateway.generated'; +import { CfnUsagePlan, CfnUsagePlanKey, IUsagePlanRef, UsagePlanReference } from './apigateway.generated'; import { Method } from './method'; import { IRestApi } from './restapi'; import { Stage } from './stage'; @@ -161,7 +161,7 @@ export interface AddApiKeyOptions { /** * A UsagePlan, either managed by this CDK app, or imported. */ -export interface IUsagePlan extends IResource { +export interface IUsagePlan extends IResource, IUsagePlanRef { /** * Id of the usage plan * @attribute @@ -211,6 +211,12 @@ abstract class UsagePlanBase extends Resource implements IUsagePlan { resource.overrideLogicalId(options?.overrideLogicalId); } } + + public get usagePlanRef(): UsagePlanReference { + return { + usagePlanId: this.usagePlanId, + }; + } } @propertyInjectable diff --git a/packages/aws-cdk-lib/aws-apigateway/lib/vpc-link.ts b/packages/aws-cdk-lib/aws-apigateway/lib/vpc-link.ts index 544d23e64dfec..983e12a2c4b7c 100644 --- a/packages/aws-cdk-lib/aws-apigateway/lib/vpc-link.ts +++ b/packages/aws-cdk-lib/aws-apigateway/lib/vpc-link.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnVpcLink } from './apigateway.generated'; +import { CfnVpcLink, IVpcLinkRef, VpcLinkReference } from './apigateway.generated'; import * as elbv2 from '../../aws-elasticloadbalancingv2'; import { IResource, Lazy, Names, Resource } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; @@ -8,7 +8,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents an API Gateway VpcLink */ -export interface IVpcLink extends IResource { +export interface IVpcLink extends IResource, IVpcLinkRef { /** * Physical ID of the VpcLink resource * @attribute @@ -56,6 +56,9 @@ export class VpcLink extends Resource implements IVpcLink { public static fromVpcLinkId(scope: Construct, id: string, vpcLinkId: string): IVpcLink { class Import extends Resource implements IVpcLink { public vpcLinkId = vpcLinkId; + public vpcLinkRef = { + vpcLinkId: vpcLinkId, + }; } return new Import(scope, id); @@ -67,6 +70,8 @@ export class VpcLink extends Resource implements IVpcLink { */ public readonly vpcLinkId: string; + public readonly vpcLinkRef: VpcLinkReference; + private readonly _targets = new Array(); constructor(scope: Construct, id: string, props: VpcLinkProps = {}) { @@ -83,6 +88,7 @@ export class VpcLink extends Resource implements IVpcLink { targetArns: Lazy.list({ produce: () => this.renderTargets() }), }); + this.vpcLinkRef = cfnResource.vpcLinkRef; this.vpcLinkId = cfnResource.ref; if (props.targets) { diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/cache-policy.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/cache-policy.ts index 7f71cd318daa0..e35bfc0b77b17 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/cache-policy.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/cache-policy.ts @@ -1,5 +1,5 @@ -import { Construct } from 'constructs'; -import { CfnCachePolicy } from './cloudfront.generated'; +import { Construct, Node } from 'constructs'; +import { CachePolicyReference, CfnCachePolicy, ICachePolicyRef } from './cloudfront.generated'; import { Duration, Names, Resource, Stack, Token, UnscopedValidationError, ValidationError, withResolved } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -7,7 +7,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents a Cache Policy */ -export interface ICachePolicy { +export interface ICachePolicy extends ICachePolicyRef { /** * The ID of the cache policy * @attribute @@ -130,18 +130,30 @@ export class CachePolicy extends Resource implements ICachePolicy { public static fromCachePolicyId(scope: Construct, id: string, cachePolicyId: string): ICachePolicy { return new class extends Resource implements ICachePolicy { public readonly cachePolicyId = cachePolicyId; + public readonly cachePolicyRef = { + cachePolicyId: cachePolicyId, + }; }(scope, id); } /** Use an existing managed cache policy. */ private static fromManagedCachePolicy(managedCachePolicyId: string): ICachePolicy { return new class implements ICachePolicy { + public get node(): Node { + throw new UnscopedValidationError('The result of fromManagedCachePolicy can not be used in this API'); + } + public readonly cachePolicyId = managedCachePolicyId; + public readonly cachePolicyRef = { + cachePolicyId: managedCachePolicyId, + }; }(); } public readonly cachePolicyId: string; + public readonly cachePolicyRef: CachePolicyReference; + constructor(scope: Construct, id: string, props: CachePolicyProps = {}) { super(scope, id, { physicalName: props.cachePolicyName, @@ -185,6 +197,7 @@ export class CachePolicy extends Resource implements ICachePolicy { }, }); + this.cachePolicyRef = resource.cachePolicyRef; this.cachePolicyId = resource.ref; } diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts index 7239d8aa5c46a..c4a82d7561e18 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/distribution.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { ICachePolicy } from './cache-policy'; -import { CfnDistribution, CfnMonitoringSubscription } from './cloudfront.generated'; +import { CfnDistribution, CfnMonitoringSubscription, DistributionReference, IDistributionRef } from './cloudfront.generated'; import { FunctionAssociation } from './function'; import { GeoRestriction } from './geo-restriction'; import { IKeyGroup } from './key-group'; @@ -23,7 +23,7 @@ import { CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021 } from '../../cx-api'; /** * Interface for CloudFront distributions */ -export interface IDistribution extends IResource { +export interface IDistribution extends IResource, IDistributionRef { /** * The domain name of the Distribution, such as d111111abcdef8.cloudfront.net. * @@ -297,6 +297,9 @@ export class Distribution extends Resource implements IDistribution { public readonly domainName: string; public readonly distributionDomainName: string; public readonly distributionId: string; + public readonly distributionRef = { + distributionId: attrs.distributionId, + }; constructor() { super(scope, id); @@ -320,6 +323,7 @@ export class Distribution extends Resource implements IDistribution { public readonly domainName: string; public readonly distributionDomainName: string; public readonly distributionId: string; + public readonly distributionRef: DistributionReference; private readonly httpVersion: HttpVersion; private readonly defaultBehavior: CacheBehavior; @@ -396,6 +400,7 @@ export class Distribution extends Resource implements IDistribution { }, }); + this.distributionRef = distribution.distributionRef; this.domainName = distribution.attrDomainName; this.distributionDomainName = distribution.attrDomainName; this.distributionId = distribution.ref; diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/edge-function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/edge-function.ts index 9470d9879a7a8..335553ad02a7e 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/edge-function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/experimental/edge-function.ts @@ -88,6 +88,19 @@ export class EdgeFunction extends Resource implements lambda.IVersion { this.node.defaultChild = this._edgeFunction; } + public get versionRef(): lambda.VersionReference { + return { + functionArn: this.functionArn, + }; + } + + public get functionRef(): lambda.FunctionReference { + return { + functionArn: this.functionArn, + functionName: this.functionName, + }; + } + public get lambda(): lambda.IFunction { return this._edgeFunction; } diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/function.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/function.ts index ce2aff9e7e32a..df301c7cdb2b0 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/function.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/function.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import { Construct } from 'constructs'; -import { CfnFunction } from './cloudfront.generated'; +import { CfnFunction, FunctionReference, IFunctionRef } from './cloudfront.generated'; import { IKeyValueStore } from './key-value-store'; import { IResource, Lazy, Names, Resource, Stack, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -73,7 +73,7 @@ class FileCode extends FunctionCode { /** * Represents a CloudFront Function */ -export interface IFunction extends IResource { +export interface IFunction extends IResource, IFunctionRef { /** * The name of the function. * @attribute @@ -170,6 +170,9 @@ export class Function extends Resource implements IFunction { public readonly functionName = attrs.functionName; public readonly functionArn = attrs.functionArn; public readonly functionRuntime = attrs.functionRuntime ?? FunctionRuntime.JS_1_0.value; + public readonly functionRef = { + functionArn: attrs.functionArn, + }; }(scope, id); } @@ -194,6 +197,8 @@ export class Function extends Resource implements IFunction { */ public readonly functionRuntime: string; + public readonly functionRef: FunctionReference; + constructor(scope: Construct, id: string, props: FunctionProps) { super(scope, id); // Enhanced CDK Analytics Telemetry @@ -221,6 +226,9 @@ export class Function extends Resource implements IFunction { this.functionArn = resource.attrFunctionArn; this.functionStage = resource.attrStage; + this.functionRef = { + functionArn: this.functionArn, + }; } private generateName(): string { diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/key-group.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/key-group.ts index 1dc6acbfb8557..a37e590d9aeff 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/key-group.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/key-group.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnKeyGroup } from './cloudfront.generated'; +import { CfnKeyGroup, IKeyGroupRef, KeyGroupReference } from './cloudfront.generated'; import { IPublicKey } from './public-key'; import { IResource, Names, Resource } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -8,7 +8,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents a Key Group */ -export interface IKeyGroup extends IResource { +export interface IKeyGroup extends IResource, IKeyGroupRef { /** * The ID of the key group. * @attribute @@ -52,10 +52,15 @@ export class KeyGroup extends Resource implements IKeyGroup { public static fromKeyGroupId(scope: Construct, id: string, keyGroupId: string): IKeyGroup { return new class extends Resource implements IKeyGroup { public readonly keyGroupId = keyGroupId; + public readonly keyGroupRef = { + keyGroupId: keyGroupId, + }; }(scope, id); } public readonly keyGroupId: string; + public readonly keyGroupRef: KeyGroupReference; + constructor(scope: Construct, id: string, props: KeyGroupProps) { super(scope, id); // Enhanced CDK Analytics Telemetry @@ -69,6 +74,7 @@ export class KeyGroup extends Resource implements IKeyGroup { }, }); + this.keyGroupRef = resource.keyGroupRef; this.keyGroupId = resource.ref; } diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/key-value-store.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/key-value-store.ts index 937a21a208b4f..68c000abbea37 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/key-value-store.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/key-value-store.ts @@ -2,10 +2,10 @@ import * as fs from 'fs'; import { join } from 'path'; import { Construct } from 'constructs'; -import { CfnKeyValueStore } from './cloudfront.generated'; +import { CfnKeyValueStore, IKeyValueStoreRef, KeyValueStoreReference } from './cloudfront.generated'; import * as s3 from '../../aws-s3'; import * as s3_assets from '../../aws-s3-assets'; -import { Resource, IResource, Lazy, Names, Stack, Arn, ArnFormat, FileSystem, ValidationError } from '../../core'; +import { Arn, ArnFormat, FileSystem, IResource, Lazy, Names, Resource, Stack, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -191,7 +191,7 @@ export interface KeyValueStoreProps { /** * A CloudFront Key Value Store. */ -export interface IKeyValueStore extends IResource { +export interface IKeyValueStore extends IResource, IKeyValueStoreRef { /** * The ARN of the Key Value Store. * @@ -235,6 +235,10 @@ export class KeyValueStore extends Resource implements IKeyValueStore { return new class Import extends Resource implements IKeyValueStore { readonly keyValueStoreArn: string = keyValueStoreArn; readonly keyValueStoreId: string = storeId!; + readonly keyValueStoreRef = { + keyValueStoreArn: keyValueStoreArn, + keyValueStoreName: storeId!, + }; constructor() { super(scope, id, { environmentFromArn: keyValueStoreArn, @@ -250,6 +254,7 @@ export class KeyValueStore extends Resource implements IKeyValueStore { readonly keyValueStoreArn: string; readonly keyValueStoreId: string; readonly keyValueStoreStatus: string; + readonly keyValueStoreRef: KeyValueStoreReference; constructor(scope: Construct, id: string, props?: KeyValueStoreProps) { super(scope, id, { @@ -266,6 +271,7 @@ export class KeyValueStore extends Resource implements IKeyValueStore { importSource: props?.source?._bind(this), }); + this.keyValueStoreRef = resource.keyValueStoreRef; this.keyValueStoreArn = resource.attrArn; this.keyValueStoreId = resource.attrId; this.keyValueStoreStatus = resource.attrStatus; diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/origin-request-policy.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/origin-request-policy.ts index 1b2cf143cea41..dade440dc6db1 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/origin-request-policy.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/origin-request-policy.ts @@ -1,5 +1,5 @@ -import { Construct } from 'constructs'; -import { CfnOriginRequestPolicy } from './cloudfront.generated'; +import { Construct, Node } from 'constructs'; +import { CfnOriginRequestPolicy, IOriginRequestPolicyRef, OriginRequestPolicyReference } from './cloudfront.generated'; import { Names, Resource, Token, UnscopedValidationError, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -7,7 +7,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents a Origin Request Policy */ -export interface IOriginRequestPolicy { +export interface IOriginRequestPolicy extends IOriginRequestPolicyRef { /** * The ID of the origin request policy * @attribute @@ -79,17 +79,28 @@ export class OriginRequestPolicy extends Resource implements IOriginRequestPolic public static fromOriginRequestPolicyId(scope: Construct, id: string, originRequestPolicyId: string): IOriginRequestPolicy { return new class extends Resource implements IOriginRequestPolicy { public readonly originRequestPolicyId = originRequestPolicyId; + public readonly originRequestPolicyRef = { + originRequestPolicyId: originRequestPolicyId, + }; }(scope, id); } /** Use an existing managed origin request policy. */ private static fromManagedOriginRequestPolicy(managedOriginRequestPolicyId: string): IOriginRequestPolicy { return new class implements IOriginRequestPolicy { + public get node(): Node { + throw new UnscopedValidationError('The result of fromManagedOriginRequestPolicy can not be used in this API'); + } + public readonly originRequestPolicyId = managedOriginRequestPolicyId; + public readonly originRequestPolicyRef = { + originRequestPolicyId: managedOriginRequestPolicyId, + }; }(); } public readonly originRequestPolicyId: string; + public readonly originRequestPolicyRef: OriginRequestPolicyReference; constructor(scope: Construct, id: string, props: OriginRequestPolicyProps = {}) { super(scope, id, { @@ -126,6 +137,7 @@ export class OriginRequestPolicy extends Resource implements IOriginRequestPolic }, }); + this.originRequestPolicyRef = resource.originRequestPolicyRef; this.originRequestPolicyId = resource.ref; } } diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/realtime-log-config.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/realtime-log-config.ts index e0360b83e00b2..a72894f309c1b 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/realtime-log-config.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/realtime-log-config.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnRealtimeLogConfig } from './cloudfront.generated'; +import { CfnRealtimeLogConfig, IRealtimeLogConfigRef, RealtimeLogConfigReference } from './cloudfront.generated'; import { Endpoint } from '../'; import { IResource, Lazy, Names, Resource, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -8,7 +8,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents Realtime Log Configuration */ -export interface IRealtimeLogConfig extends IResource { +export interface IRealtimeLogConfig extends IResource, IRealtimeLogConfigRef { /** * The name of the realtime log config. * @attribute @@ -58,6 +58,7 @@ export class RealtimeLogConfig extends Resource implements IRealtimeLogConfig { public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-cloudfront.RealtimeLogConfig'; public readonly realtimeLogConfigName: string; public readonly realtimeLogConfigArn: string; + public readonly realtimeLogConfigRef: RealtimeLogConfigReference; constructor(scope: Construct, id: string, props: RealtimeLogConfigProps) { super(scope, id, { @@ -78,6 +79,7 @@ export class RealtimeLogConfig extends Resource implements IRealtimeLogConfig { name: this.physicalName, samplingRate: props.samplingRate, }); + this.realtimeLogConfigRef = resource.realtimeLogConfigRef; this.realtimeLogConfigArn = this.getResourceArnAttribute(resource.attrArn, { service: 'cloudfront', diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts index ea7816c0c59b1..9617057d1c3ed 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/response-headers-policy.ts @@ -1,13 +1,17 @@ -import { Construct } from 'constructs'; -import { CfnResponseHeadersPolicy } from './cloudfront.generated'; -import { Duration, Names, Resource, Token, ValidationError, withResolved } from '../../core'; +import { Construct, Node } from 'constructs'; +import { + CfnResponseHeadersPolicy, + IResponseHeadersPolicyRef, + ResponseHeadersPolicyReference, +} from './cloudfront.generated'; +import { Duration, Names, Resource, Token, UnscopedValidationError, ValidationError, withResolved } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents a response headers policy. */ -export interface IResponseHeadersPolicy { +export interface IResponseHeadersPolicy extends IResponseHeadersPolicyRef { /** * The ID of the response headers policy * @attribute @@ -97,17 +101,28 @@ export class ResponseHeadersPolicy extends Resource implements IResponseHeadersP public static fromResponseHeadersPolicyId(scope: Construct, id: string, responseHeadersPolicyId: string): IResponseHeadersPolicy { class Import extends Resource implements IResponseHeadersPolicy { public readonly responseHeadersPolicyId = responseHeadersPolicyId; + public readonly responseHeadersPolicyRef = { + responseHeadersPolicyId: responseHeadersPolicyId, + }; } return new Import(scope, id); } private static fromManagedResponseHeadersPolicy(managedResponseHeadersPolicyId: string): IResponseHeadersPolicy { return new class implements IResponseHeadersPolicy { + public get node(): Node { + throw new UnscopedValidationError('The result of fromManagedResponseHeadersPolicy can not be used in this API'); + } + public readonly responseHeadersPolicyId = managedResponseHeadersPolicyId; + public readonly responseHeadersPolicyRef = { + responseHeadersPolicyId: managedResponseHeadersPolicyId, + }; }; } public readonly responseHeadersPolicyId: string; + public readonly responseHeadersPolicyRef: ResponseHeadersPolicyReference; constructor(scope: Construct, id: string, props: ResponseHeadersPolicyProps = {}) { super(scope, id, { @@ -132,6 +147,7 @@ export class ResponseHeadersPolicy extends Resource implements IResponseHeadersP }, }); + this.responseHeadersPolicyRef = resource.responseHeadersPolicyRef; this.responseHeadersPolicyId = resource.ref; } diff --git a/packages/aws-cdk-lib/aws-cloudfront/lib/web-distribution.ts b/packages/aws-cdk-lib/aws-cloudfront/lib/web-distribution.ts index eed039f5d366c..0eea5b9db7756 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/lib/web-distribution.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/lib/web-distribution.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnDistribution } from './cloudfront.generated'; +import { CfnDistribution, DistributionReference } from './cloudfront.generated'; import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, PriceClass, ViewerProtocolPolicy, SSLMethod, SecurityPolicyProtocol } from './distribution'; import { FunctionAssociation } from './function'; import { GeoRestriction } from './geo-restriction'; @@ -760,6 +760,9 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu public readonly domainName: string; public readonly distributionDomainName: string; public readonly distributionId: string; + public readonly distributionRef = { + distributionId: attrs.distributionId, + }; constructor() { super(scope, id); @@ -807,6 +810,8 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu */ public readonly distributionId: string; + public readonly distributionRef: DistributionReference; + /** * Maps our methods to the string arrays they are */ @@ -1001,6 +1006,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu } const distribution = new CfnDistribution(this, 'CFDistribution', { distributionConfig }); + this.distributionRef = distribution.distributionRef; this.node.defaultChild = distribution; this.domainName = distribution.attrDomainName; this.distributionDomainName = distribution.attrDomainName; diff --git a/packages/aws-cdk-lib/aws-cloudfront/test/function.test.ts b/packages/aws-cdk-lib/aws-cloudfront/test/function.test.ts index 09e94497cfb00..bef44a941323e 100644 --- a/packages/aws-cdk-lib/aws-cloudfront/test/function.test.ts +++ b/packages/aws-cdk-lib/aws-cloudfront/test/function.test.ts @@ -292,5 +292,24 @@ describe('CloudFront Function', () => { }, }); }); + + test('CloudFront FunctionRef uses GetAtt, not Ref', () => { + // Both GetAtt and Ref are valid ways to satisfy the contract, but only + // GetAtt is backwards compatible. + const stack = new Stack(); + + const fn = new Function(stack, 'TestFn', { + code: FunctionCode.fromInline('code'), + runtime: FunctionRuntime.JS_2_0, + keyValueStore: undefined, + }); + + expect(stack.resolve(fn.functionRef.functionArn)).toEqual({ + 'Fn::GetAtt': [ + 'TestFn04335C60', + 'FunctionARN', + ], + }); + }); }); }); diff --git a/packages/aws-cdk-lib/aws-codepipeline-actions/lib/elastic-beanstalk/deploy-action.ts b/packages/aws-cdk-lib/aws-codepipeline-actions/lib/elastic-beanstalk/deploy-action.ts index 05251be979a49..6f8edf3210bb9 100644 --- a/packages/aws-cdk-lib/aws-codepipeline-actions/lib/elastic-beanstalk/deploy-action.ts +++ b/packages/aws-cdk-lib/aws-codepipeline-actions/lib/elastic-beanstalk/deploy-action.ts @@ -1,6 +1,6 @@ -import { Construct } from 'constructs'; +import { Construct, Node } from 'constructs'; import * as codepipeline from '../../../aws-codepipeline'; -import { Aws } from '../../../core'; +import { AssumptionError, Aws } from '../../../core'; import { Action } from '../action'; import { deployArtifactBounds } from '../common'; @@ -51,7 +51,12 @@ export class ElasticBeanstalkDeployAction extends Action { ): codepipeline.ActionConfig { // Per https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/AWSHowTo.iam.managed-policies.html // it doesn't seem we can scope this down further for the codepipeline action. - options.role.addManagedPolicy({ managedPolicyArn: `arn:${Aws.PARTITION}:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk` }); + options.role.addManagedPolicy({ + get node(): Node { + throw new AssumptionError('Cannot access node'); + }, + managedPolicyArn: `arn:${Aws.PARTITION}:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk`, + }); // the Action's Role needs to read from the Bucket to get artifacts options.bucket.grantRead(options.role); diff --git a/packages/aws-cdk-lib/aws-ec2/lib/bastion-host.ts b/packages/aws-cdk-lib/aws-ec2/lib/bastion-host.ts index 5fd32e94d4852..086180eb19f78 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/bastion-host.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/bastion-host.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { InstanceArchitecture, InstanceClass, InstanceSize, InstanceType } from '.'; import { CloudFormationInit } from './cfn-init'; import { Connections } from './connections'; +import { InstanceReference } from './ec2.generated'; import { ApplyCloudFormationInitOptions, IInstance, Instance } from './instance'; import { AmazonLinuxCpuType, IMachineImage, MachineImage } from './machine-image'; import { IPeer } from './peer'; @@ -242,6 +243,12 @@ export class BastionHostLinux extends Resource implements IInstance { }); } + public get instanceRef(): InstanceReference { + return { + instanceId: this.instanceId, + }; + } + /** * Returns the AmazonLinuxCpuType corresponding to the given instance architecture * @param architecture the instance architecture value to convert diff --git a/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint-types.ts b/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint-types.ts index 19577829cb489..9c51b9f695aa8 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint-types.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint-types.ts @@ -1,11 +1,12 @@ import { IDependable } from 'constructs'; import { IConnectable } from './connections'; +import { IClientVpnEndpointRef } from './ec2.generated'; import { IResource } from '../../core'; /** * A client VPN endpoint */ -export interface IClientVpnEndpoint extends IResource, IConnectable { +export interface IClientVpnEndpoint extends IResource, IConnectable, IClientVpnEndpointRef { /** * The endpoint ID */ diff --git a/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint.ts b/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint.ts index e3852c60027db..417ad0885006a 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/client-vpn-endpoint.ts @@ -3,7 +3,7 @@ import { ClientVpnAuthorizationRule, ClientVpnAuthorizationRuleOptions } from '. import { IClientVpnConnectionHandler, IClientVpnEndpoint, TransportProtocol, VpnPort } from './client-vpn-endpoint-types'; import { ClientVpnRoute, ClientVpnRouteOptions } from './client-vpn-route'; import { Connections } from './connections'; -import { CfnClientVpnEndpoint, CfnClientVpnTargetNetworkAssociation } from './ec2.generated'; +import { CfnClientVpnEndpoint, CfnClientVpnTargetNetworkAssociation, ClientVpnEndpointReference } from './ec2.generated'; import { CidrBlock } from './network-util'; import { ISecurityGroup, SecurityGroup } from './security-group'; import { IVpc, SubnetSelection } from './vpc'; @@ -306,6 +306,12 @@ export class ClientVpnEndpoint extends Resource implements IClientVpnEndpoint { public readonly endpointId = attrs.endpointId; public readonly connections = new Connections({ securityGroups: attrs.securityGroups }); public readonly targetNetworksAssociated: IDependable = new DependencyGroup(); + + public get clientVpnEndpointRef(): ClientVpnEndpointReference { + return { + clientVpnEndpointId: this.endpointId, + }; + } } return new Import(scope, id); } @@ -435,6 +441,12 @@ export class ClientVpnEndpoint extends Resource implements IClientVpnEndpoint { } } + public get clientVpnEndpointRef(): ClientVpnEndpointReference { + return { + clientVpnEndpointId: this.endpointId, + }; + } + /** * Adds an authorization rule to this endpoint */ diff --git a/packages/aws-cdk-lib/aws-ec2/lib/instance.ts b/packages/aws-cdk-lib/aws-ec2/lib/instance.ts index 28fb29d42f4fb..3490950d2c1c5 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/instance.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/instance.ts @@ -3,7 +3,7 @@ import { Construct } from 'constructs'; import { InstanceRequireImdsv2Aspect } from './aspects'; import { CloudFormationInit } from './cfn-init'; import { Connections, IConnectable } from './connections'; -import { CfnInstance } from './ec2.generated'; +import { CfnInstance, IInstanceRef, InstanceReference } from './ec2.generated'; import { InstanceType } from './instance-types'; import { IKeyPair } from './key-pair'; import { CpuCredits, InstanceInitiatedShutdownBehavior } from './launch-template'; @@ -27,7 +27,7 @@ import * as cxapi from '../../cx-api'; */ const NAME_TAG: string = 'Name'; -export interface IInstance extends IResource, IConnectable, iam.IGrantable { +export interface IInstance extends IResource, IConnectable, iam.IGrantable, IInstanceRef { /** * The instance's ID * @@ -685,6 +685,12 @@ export class Instance extends Resource implements IInstance { } } + public get instanceRef(): InstanceReference { + return { + instanceId: this.instanceId, + }; + } + /** * Add the security group to the instance. * diff --git a/packages/aws-cdk-lib/aws-ec2/lib/key-pair.ts b/packages/aws-cdk-lib/aws-ec2/lib/key-pair.ts index 97528dbec48ec..1cea153cbcf15 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/key-pair.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/key-pair.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnKeyPair } from './ec2.generated'; +import { CfnKeyPair, IKeyPairRef, KeyPairReference } from './ec2.generated'; import { OperatingSystemType } from './machine-image'; import { StringParameter, IStringParameter } from '../../aws-ssm'; import { Resource, ResourceProps, Names, Lazy, IResource, ValidationError } from '../../core'; @@ -96,7 +96,7 @@ export interface KeyPairAttributes { /** * An EC2 Key Pair. */ -export interface IKeyPair extends IResource { +export interface IKeyPair extends IResource, IKeyPairRef { /** * The name of the key pair. * @@ -148,6 +148,12 @@ export class KeyPair extends Resource implements IKeyPair { this.type = attrs.type; } + public get keyPairRef(): KeyPairReference { + return { + keyName: this.keyPairName, + }; + } + /** * Used internally to determine whether the key pair is compatible with an OS type. * @@ -234,6 +240,12 @@ export class KeyPair extends Resource implements IKeyPair { this.format = keyFormat; } + public get keyPairRef(): KeyPairReference { + return { + keyName: this.keyPairName, + }; + } + /** * Whether the key material was imported. * diff --git a/packages/aws-cdk-lib/aws-ec2/lib/launch-template.ts b/packages/aws-cdk-lib/aws-ec2/lib/launch-template.ts index 29903871cd8e7..642333af25c9e 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/launch-template.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/launch-template.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { Connections, IConnectable } from './connections'; -import { CfnLaunchTemplate } from './ec2.generated'; +import { CfnLaunchTemplate, ILaunchTemplateRef, LaunchTemplateReference } from './ec2.generated'; import { InstanceType } from './instance-types'; import { IKeyPair } from './key-pair'; import { IMachineImage, MachineImageConfig, OperatingSystemType } from './machine-image'; @@ -75,7 +75,7 @@ export enum InstanceInitiatedShutdownBehavior { /** * Interface for LaunchTemplate-like objects. */ -export interface ILaunchTemplate extends IResource { +export interface ILaunchTemplate extends IResource, ILaunchTemplateRef { /** * The version number of this launch template to use * @@ -527,6 +527,16 @@ export class LaunchTemplate extends Resource implements ILaunchTemplate, iam.IGr public readonly versionNumber = attrs.versionNumber ?? LaunchTemplateSpecialVersions.DEFAULT_VERSION; public readonly launchTemplateId? = attrs.launchTemplateId; public readonly launchTemplateName? = attrs.launchTemplateName; + + public get launchTemplateRef(): LaunchTemplateReference { + if (!this.launchTemplateId) { + throw new ValidationError('You must set launchTemplateId in LaunchTemplate.fromLaunchTemplateAttributes() in order to use the LaunchTemplate in this API', this); + } + + return { + launchTemplateId: this.launchTemplateId, + }; + } } return new Import(scope, id); } @@ -852,6 +862,12 @@ export class LaunchTemplate extends Resource implements ILaunchTemplate, iam.IGr this.versionNumber = Token.asString(resource.getAtt('LatestVersionNumber')); } + public get launchTemplateRef(): LaunchTemplateReference { + return { + launchTemplateId: this.launchTemplateId!, + }; + } + private renderMetadataOptions(props: LaunchTemplateProps) { let requireMetadataOptions = false; // if requireImdsv2 is true, httpTokens must be required. diff --git a/packages/aws-cdk-lib/aws-ec2/lib/network-acl.ts b/packages/aws-cdk-lib/aws-ec2/lib/network-acl.ts index 5788cde1d905b..9840de8605b88 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/network-acl.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/network-acl.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnNetworkAcl, CfnNetworkAclEntry, CfnSubnetNetworkAclAssociation } from './ec2.generated'; +import { CfnNetworkAcl, CfnNetworkAclEntry, CfnSubnetNetworkAclAssociation, INetworkAclEntryRef, INetworkAclRef, ISubnetNetworkAclAssociationRef, NetworkAclEntryReference, NetworkAclReference, SubnetNetworkAclAssociationReference } from './ec2.generated'; import { AclCidr, AclTraffic } from './network-acl-types'; import { ISubnet, IVpc, SubnetSelection } from './vpc'; import { IResource, Resource, Tags } from '../../core'; @@ -13,10 +13,8 @@ const NAME_TAG: string = 'Name'; /** * A NetworkAcl - * - * */ -export interface INetworkAcl extends IResource { +export interface INetworkAcl extends IResource, INetworkAclRef { /** * ID for the current Network ACL * @attribute @@ -37,6 +35,12 @@ export interface INetworkAcl extends IResource { abstract class NetworkAclBase extends Resource implements INetworkAcl { public abstract readonly networkAclId: string; + public get networkAclRef(): NetworkAclReference { + return { + networkAclId: this.networkAclId, + }; + } + /** * Add a new entry to the ACL */ @@ -175,7 +179,7 @@ export enum Action { * * */ -export interface INetworkAclEntry extends IResource { +export interface INetworkAclEntry extends IResource, INetworkAclEntryRef { /** * The network ACL. */ @@ -190,6 +194,7 @@ export interface INetworkAclEntry extends IResource { */ abstract class NetworkAclEntryBase extends Resource implements INetworkAclEntry { public abstract readonly networkAcl: INetworkAcl; + public abstract readonly networkAclEntryRef: NetworkAclEntryReference; } /** @@ -280,7 +285,9 @@ export interface NetworkAclEntryProps extends CommonNetworkAclEntryOptions { export class NetworkAclEntry extends NetworkAclEntryBase { /** Uniquely identifies this class. */ public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-ec2.NetworkAclEntry'; + public readonly networkAcl: INetworkAcl; + public readonly networkAclEntryRef: NetworkAclEntryReference; constructor(scope: Construct, id: string, props: NetworkAclEntryProps) { super(scope, id, { @@ -291,7 +298,7 @@ export class NetworkAclEntry extends NetworkAclEntryBase { this.networkAcl = props.networkAcl; - new CfnNetworkAclEntry(this, 'Resource', { + const resource = new CfnNetworkAclEntry(this, 'Resource', { networkAclId: this.networkAcl.networkAclId, ruleNumber: props.ruleNumber, ruleAction: props.ruleAction ?? Action.ALLOW, @@ -299,6 +306,7 @@ export class NetworkAclEntry extends NetworkAclEntryBase { ...props.traffic.toTrafficConfig(), ...props.cidr.toCidrConfig(), }); + this.networkAclEntryRef = resource.networkAclEntryRef; } } @@ -307,7 +315,7 @@ export class NetworkAclEntry extends NetworkAclEntryBase { * * */ -export interface ISubnetNetworkAclAssociation extends IResource { +export interface ISubnetNetworkAclAssociation extends IResource, ISubnetNetworkAclAssociationRef { /** * ID for the current SubnetNetworkAclAssociation * @attribute @@ -352,7 +360,14 @@ export interface SubnetNetworkAclAssociationProps { */ abstract class SubnetNetworkAclAssociationBase extends Resource implements ISubnetNetworkAclAssociation { public abstract readonly subnetNetworkAclAssociationAssociationId: string; + + public get subnetNetworkAclAssociationRef(): SubnetNetworkAclAssociationReference { + return { + associationId: this.subnetNetworkAclAssociationAssociationId, + }; + } } + @propertyInjectable export class SubnetNetworkAclAssociation extends SubnetNetworkAclAssociationBase { /** Uniquely identifies this class. */ diff --git a/packages/aws-cdk-lib/aws-ec2/lib/placement-group.ts b/packages/aws-cdk-lib/aws-ec2/lib/placement-group.ts index edf0f64bc219c..56d20ee178b88 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/placement-group.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/placement-group.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnPlacementGroup } from './ec2.generated'; +import { CfnPlacementGroup, IPlacementGroupRef, PlacementGroupReference } from './ec2.generated'; import { IResource, Resource, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -9,7 +9,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; * * @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html */ -export interface IPlacementGroup extends IResource { +export interface IPlacementGroup extends IResource, IPlacementGroupRef { /** * The name of this placement group * @@ -160,6 +160,11 @@ export class PlacementGroup extends Resource implements IPlacementGroup { public static fromPlacementGroupName(scope: Construct, id: string, placementGroupName: string): IPlacementGroup { class Import extends Resource implements IPlacementGroup { public readonly placementGroupName = placementGroupName; + public get placementGroupRef(): PlacementGroupReference { + return { + groupName: this.placementGroupName, + }; + } } return new Import(scope, id); @@ -211,4 +216,10 @@ export class PlacementGroup extends Resource implements IPlacementGroup { resourceName: this.physicalName, }); } + + public get placementGroupRef(): PlacementGroupReference { + return { + groupName: this.placementGroupName, + }; + } } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/prefix-list.ts b/packages/aws-cdk-lib/aws-ec2/lib/prefix-list.ts index e996b3ac7acc8..c546940be0b43 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/prefix-list.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/prefix-list.ts @@ -1,16 +1,16 @@ import { Construct } from 'constructs'; import { Connections } from './connections'; -import { CfnPrefixList } from './ec2.generated'; +import { CfnPrefixList, IPrefixListRef, PrefixListReference } from './ec2.generated'; import { IPeer } from './peer'; import * as cxschema from '../../cloud-assembly-schema'; -import { IResource, Lazy, Resource, Names, ContextProvider, Token, ValidationError } from '../../core'; +import { ContextProvider, IResource, Lazy, Names, Resource, Stack, Token, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * A prefix list */ -export interface IPrefixList extends IResource, IPeer { +export interface IPrefixList extends IResource, IPeer, IPrefixListRef { /** * The ID of the prefix list * @@ -89,6 +89,8 @@ abstract class PrefixListBase extends Resource implements IPrefixList { */ public readonly canInlineRule = false; + public abstract readonly prefixListRef: PrefixListReference; + /** * A unique identifier for this connection peer */ @@ -158,6 +160,17 @@ export class PrefixList extends PrefixListBase { public static fromPrefixListId(scope: Construct, id: string, prefixListId: string): IPrefixList { class Import extends PrefixListBase { public readonly prefixListId = prefixListId; + + public get prefixListRef(): PrefixListReference { + return { + prefixListArn: Stack.of(scope).formatArn({ + service: 'ec2', + resource: 'prefix-list', + resourceName: this.prefixListId, + }), + prefixListId, + }; + } } return new Import(scope, id); } @@ -291,4 +304,11 @@ export class PrefixList extends PrefixListBase { this.version = prefixList.attrVersion; this.addressFamily = prefixList.addressFamily; } + + public get prefixListRef(): PrefixListReference { + return { + prefixListArn: this.prefixListArn, + prefixListId: this.prefixListId, + }; + } } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts b/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts index 821d38c819bd1..623cb772bfba0 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/security-group.ts @@ -1,11 +1,22 @@ import { Construct } from 'constructs'; import { Connections } from './connections'; -import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress } from './ec2.generated'; +import { CfnSecurityGroup, CfnSecurityGroupEgress, CfnSecurityGroupIngress, ISecurityGroupRef, SecurityGroupReference } from './ec2.generated'; import { IPeer, Peer } from './peer'; import { Port } from './port'; import { IVpc } from './vpc'; import * as cxschema from '../../cloud-assembly-schema'; -import { Annotations, ContextProvider, IResource, Lazy, Names, Resource, ResourceProps, Stack, Token, ValidationError } from '../../core'; +import { + Annotations, + ContextProvider, + IResource, + Lazy, + Names, + Resource, + ResourceProps, + Stack, + Token, + ValidationError, +} from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; import * as cxapi from '../../cx-api'; @@ -17,7 +28,7 @@ const SECURITY_GROUP_DISABLE_INLINE_RULES_CONTEXT_KEY = '@aws-cdk/aws-ec2.securi /** * Interface for security group-like objects */ -export interface ISecurityGroup extends IResource, IPeer { +export interface ISecurityGroup extends IResource, IPeer, ISecurityGroupRef { /** * ID for the current security group * @attribute @@ -79,6 +90,12 @@ abstract class SecurityGroupBase extends Resource implements ISecurityGroup { Object.defineProperty(this, SECURITY_GROUP_SYMBOL, { value: true }); } + public get securityGroupRef(): SecurityGroupReference { + return { + securityGroupId: this.securityGroupId, + }; + } + public get uniqueId() { return Names.nodeUniqueId(this.node); } diff --git a/packages/aws-cdk-lib/aws-ec2/lib/volume.ts b/packages/aws-cdk-lib/aws-ec2/lib/volume.ts index b8e408032fbce..446c383e14a5e 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/volume.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/volume.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnVolume } from './ec2.generated'; +import { CfnVolume, IVolumeRef, VolumeReference } from './ec2.generated'; import { IInstance } from './instance'; import { AccountRootPrincipal, Grant, IGrantable } from '../../aws-iam'; import { IKey, ViaServicePrincipal } from '../../aws-kms'; @@ -265,7 +265,7 @@ export enum EbsDeviceVolumeType { /** * An EBS Volume in AWS EC2. */ -export interface IVolume extends IResource { +export interface IVolume extends IResource, IVolumeRef { /** * The EBS Volume's ID * @@ -514,6 +514,12 @@ abstract class VolumeBase extends Resource implements IVolume { public abstract readonly availabilityZone: string; public abstract readonly encryptionKey?: IKey; + public get volumeRef(): VolumeReference { + return { + volumeId: this.volumeId, + }; + } + public grantAttachVolume(grantee: IGrantable, instances?: IInstance[]): Grant { const result = Grant.addToPrincipal({ grantee, diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint-service.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint-service.ts index 2c9c6fb5993ef..2cc09d4401a6e 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint-service.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint-service.ts @@ -1,5 +1,10 @@ import { Construct } from 'constructs'; -import { CfnVPCEndpointService, CfnVPCEndpointServicePermissions } from './ec2.generated'; +import { + CfnVPCEndpointService, + CfnVPCEndpointServicePermissions, + IVPCEndpointServiceRef, + VPCEndpointServiceReference, +} from './ec2.generated'; import { ArnPrincipal } from '../../aws-iam'; import { Aws, Fn, IResource, Resource, Stack, Token, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -38,7 +43,7 @@ export interface IVpcEndpointServiceLoadBalancer { * A VPC endpoint service. * */ -export interface IVpcEndpointService extends IResource { +export interface IVpcEndpointService extends IResource, IVPCEndpointServiceRef { /** * The service name of the VPC Endpoint Service that clients use to connect to, * like com.amazonaws.vpce..vpce-svc-xxxxxxxxxxxxxxxx @@ -172,6 +177,10 @@ export class VpcEndpointService extends Resource implements IVpcEndpointService }); } } + + public get vpcEndpointServiceRef(): VPCEndpointServiceReference { + return this.endpointService.vpcEndpointServiceRef; + } } /** diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint.ts index 4e1d9c501e88f..4ba09e67bb364 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc-endpoint.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { Connections, IConnectable } from './connections'; -import { CfnVPCEndpoint } from './ec2.generated'; +import { CfnVPCEndpoint, IVPCEndpointRef, VPCEndpointReference } from './ec2.generated'; import { Peer } from './peer'; import { Port } from './port'; import { ISecurityGroup, SecurityGroup } from './security-group'; @@ -15,7 +15,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * A VPC endpoint. */ -export interface IVpcEndpoint extends IResource { +export interface IVpcEndpoint extends IResource, IVPCEndpointRef { /** * The VPC endpoint identifier. * @attribute @@ -28,6 +28,12 @@ export abstract class VpcEndpoint extends Resource implements IVpcEndpoint { protected policyDocument?: iam.PolicyDocument; + public get vpcEndpointRef(): VPCEndpointReference { + return { + vpcEndpointId: this.vpcEndpointId, + }; + } + /** * Adds a statement to the policy document of the VPC endpoint. The statement * must have a Principal. @@ -981,6 +987,11 @@ export class InterfaceVpcEndpoint extends VpcEndpoint implements IInterfaceVpcEn defaultPort: Port.tcp(attrs.port), securityGroups, }); + public get vpcEndpointRef(): VPCEndpointReference { + return { + vpcEndpointId: this.vpcEndpointId, + }; + } } return new Import(scope, id); diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc-flow-logs.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc-flow-logs.ts index f217f2206b363..428281d2a4ba3 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc-flow-logs.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc-flow-logs.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnFlowLog } from './ec2.generated'; +import { CfnFlowLog, FlowLogReference, IFlowLogRef } from './ec2.generated'; import { ISubnet, IVpc } from './vpc'; import * as iam from '../../aws-iam'; import * as logs from '../../aws-logs'; @@ -17,7 +17,7 @@ const NAME_TAG: string = 'Name'; /** * A FlowLog */ -export interface IFlowLog extends IResource { +export interface IFlowLog extends IResource, IFlowLogRef { /** * The Id of the VPC Flow Log * @@ -804,6 +804,12 @@ abstract class FlowLogBase extends Resource implements IFlowLog { * @attribute */ public abstract readonly flowLogId: string; + + public get flowLogRef(): FlowLogReference { + return { + flowLogId: this.flowLogId, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts index deb37e89639e7..721c430b05f23 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts @@ -3,6 +3,10 @@ import { ClientVpnEndpoint, ClientVpnEndpointOptions } from './client-vpn-endpoi import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCCidrBlock, CfnVPCGatewayAttachment, CfnVPNGatewayRoutePropagation, + ISubnetRef, + IVPCRef, + SubnetReference, + VPCReference, } from './ec2.generated'; import { AllocatedSubnet, IIpAddresses, RequestedSubnet, IpAddresses, IIpv6Addresses, Ipv6Addresses } from './ip-addresses'; import { NatProvider } from './nat'; @@ -29,7 +33,7 @@ import { EC2_RESTRICT_DEFAULT_SECURITY_GROUP } from '../../cx-api'; const VPC_SUBNET_SYMBOL = Symbol.for('@aws-cdk/aws-ec2.VpcSubnet'); const FAKE_AZ_NAME = 'fake-az'; -export interface ISubnet extends IResource { +export interface ISubnet extends IResource, ISubnetRef { /** * The Availability Zone the subnet is located in */ @@ -74,7 +78,7 @@ export interface IRouteTable { readonly routeTableId: string; } -export interface IVpc extends IResource { +export interface IVpc extends IResource, IVPCRef { /** * Identifier for this VPC * @attribute @@ -471,6 +475,12 @@ abstract class VpcBase extends Resource implements IVpc { */ protected _vpnGatewayId?: string; + public get vpcRef(): VPCReference { + return { + vpcId: this.vpcId, + }; + } + /** * Returns IDs of selected subnets */ @@ -2099,6 +2109,8 @@ export class Subnet extends Resource implements ISubnet { */ public readonly routeTable: IRouteTable; + public readonly subnetRef: SubnetReference; + public readonly internetConnectivityEstablished: IDependable; private readonly _internetConnectivityEstablished = new DependencyGroup(); @@ -2129,6 +2141,7 @@ export class Subnet extends Resource implements ISubnet { this.subnetAvailabilityZone = subnet.attrAvailabilityZone; this.subnetIpv6CidrBlocks = subnet.attrIpv6CidrBlocks; this.subnetOutpostArn = subnet.attrOutpostArn; + this.subnetRef = subnet.subnetRef; // subnet.attrNetworkAclAssociationId is the default ACL after the subnet // was just created. However, the ACL can be replaced at a later time. @@ -2686,6 +2699,12 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat }; } + public get subnetRef(): SubnetReference { + return { + subnetId: this.subnetId, + }; + } + public get availabilityZone(): string { if (!this._availabilityZone) { // eslint-disable-next-line max-len diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpn.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpn.ts index 67c0cb987f6de..f59a079d697b0 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpn.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpn.ts @@ -5,6 +5,8 @@ import { CfnVPNConnection, CfnVPNConnectionRoute, CfnVPNGateway, + IVPNConnectionRef, + IVPNGatewayRef, VPNConnectionReference, VPNGatewayReference, } from './ec2.generated'; import { IVpc, SubnetSelection } from './vpc'; import * as cloudwatch from '../../aws-cloudwatch'; @@ -12,7 +14,7 @@ import { IResource, Resource, SecretValue, Token, ValidationError } from '../../ import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; -export interface IVpnConnection extends IResource { +export interface IVpnConnection extends IResource, IVPNConnectionRef { /** * The id of the VPN connection. * @attribute VpnConnectionId @@ -38,7 +40,7 @@ export interface IVpnConnection extends IResource { /** * The virtual private gateway interface */ -export interface IVpnGateway extends IResource { +export interface IVpnGateway extends IResource, IVPNGatewayRef { /** * The virtual private gateway Id @@ -172,6 +174,8 @@ export class VpnGateway extends Resource implements IVpnGateway { */ public readonly gatewayId: string; + public readonly vpnGatewayRef: VPNGatewayReference; + constructor(scope: Construct, id: string, props: VpnGatewayProps) { super(scope, id); // Enhanced CDK Analytics Telemetry @@ -182,6 +186,7 @@ export class VpnGateway extends Resource implements IVpnGateway { // to be created for the CfnVPNGateway (and 'Resource' would not do that). const vpnGW = new CfnVPNGateway(this, 'Default', props); this.gatewayId = vpnGW.ref; + this.vpnGatewayRef = vpnGW.vpnGatewayRef; } } @@ -220,6 +225,12 @@ export abstract class VpnConnectionBase extends Resource implements IVpnConnecti public abstract readonly customerGatewayId: string; public abstract readonly customerGatewayIp: string; public abstract readonly customerGatewayAsn: number; + + public get vpnConnectionRef(): VPNConnectionReference { + return { + vpnConnectionId: this.customerGatewayId, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-iam/lib/access-key.ts b/packages/aws-cdk-lib/aws-iam/lib/access-key.ts index 7904225107984..4794d5e951d12 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/access-key.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/access-key.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnAccessKey } from './iam.generated'; +import { AccessKeyReference, CfnAccessKey, IAccessKeyRef } from './iam.generated'; import { IUser } from './user'; import { IResource, Resource, SecretValue } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; @@ -25,7 +25,7 @@ export enum AccessKeyStatus { * * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html */ -export interface IAccessKey extends IResource { +export interface IAccessKey extends IResource, IAccessKeyRef { /** * The Access Key ID. * @@ -79,6 +79,7 @@ export interface AccessKeyProps { export class AccessKey extends Resource implements IAccessKey { /** Uniquely identifies this class. */ public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-iam.AccessKey'; + public readonly accessKeyRef: AccessKeyReference; public readonly accessKeyId: string; public readonly secretAccessKey: SecretValue; @@ -93,6 +94,7 @@ export class AccessKey extends Resource implements IAccessKey { }); this.accessKeyId = accessKey.ref; + this.accessKeyRef = accessKey.accessKeyRef; this.secretAccessKey = SecretValue.resourceAttribute(accessKey.attrSecretAccessKey); } diff --git a/packages/aws-cdk-lib/aws-iam/lib/group.ts b/packages/aws-cdk-lib/aws-iam/lib/group.ts index bb9673cea5724..83c744a7775c5 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/group.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/group.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnGroup } from './iam.generated'; +import { CfnGroup, GroupReference, IGroupRef } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy } from './managed-policy'; import { Policy } from './policy'; @@ -16,7 +16,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; * * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_groups.html */ -export interface IGroup extends IIdentity { +export interface IGroup extends IIdentity, IGroupRef { /** * Returns the IAM Group Name * @@ -85,6 +85,13 @@ abstract class GroupBase extends Resource implements IGroup { return new ArnPrincipal(this.groupArn).policyFragment; } + public get groupRef(): GroupReference { + return { + groupName: this.groupName, + groupArn: this.groupArn, + }; + } + /** * Attaches a policy to this group. * @param policy The policy to attach. diff --git a/packages/aws-cdk-lib/aws-iam/lib/instance-profile.ts b/packages/aws-cdk-lib/aws-iam/lib/instance-profile.ts index 4d30009fa20da..bad03c6bf0c46 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/instance-profile.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/instance-profile.ts @@ -1,15 +1,15 @@ import { Construct } from 'constructs'; -import { CfnInstanceProfile } from './iam.generated'; +import { CfnInstanceProfile, IInstanceProfileRef, InstanceProfileReference } from './iam.generated'; import { ServicePrincipal } from './principals'; import { IRole, Role } from './role'; -import { Resource, Arn, Stack, IResource, PhysicalName } from '../../core'; +import { Arn, IResource, PhysicalName, Resource, Stack } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * Represents an IAM Instance Profile */ -export interface IInstanceProfile extends IResource { +export interface IInstanceProfile extends IResource, IInstanceProfileRef { /** * The InstanceProfile's name. * @attribute @@ -99,6 +99,13 @@ abstract class InstanceProfileBase extends Resource implements IInstanceProfile public get role(): IRole | undefined { return this._role; } + + public get instanceProfileRef(): InstanceProfileReference { + return { + instanceProfileName: this.instanceProfileName, + instanceProfileArn: this.instanceProfileArn, + }; + } } /** diff --git a/packages/aws-cdk-lib/aws-iam/lib/lazy-role.ts b/packages/aws-cdk-lib/aws-iam/lib/lazy-role.ts index 3bc1d1e503d36..caac20887d3f6 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/lazy-role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/lazy-role.ts @@ -1,5 +1,6 @@ import { Construct } from 'constructs'; import { Grant } from './grant'; +import { RoleReference } from './iam.generated'; import { IManagedPolicy } from './managed-policy'; import { Policy } from './policy'; import { PolicyStatement } from './policy-statement'; @@ -99,6 +100,10 @@ export class LazyRole extends cdk.Resource implements IRole { return this.instantiate().roleArn; } + public get roleRef(): RoleReference { + return this.instantiate().roleRef; + } + /** * Returns the stable and unique string identifying the role (i.e. AIDAJQABLZS4A3QDU576Q) * diff --git a/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts b/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts index 93e53d082fb50..c9bcddd5b6fae 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/managed-policy.ts @@ -1,6 +1,6 @@ -import { Construct } from 'constructs'; +import { Construct, Node } from 'constructs'; import { IGroup } from './group'; -import { CfnManagedPolicy } from './iam.generated'; +import { CfnManagedPolicy, IManagedPolicyRef, ManagedPolicyReference } from './iam.generated'; import { PolicyDocument } from './policy-document'; import { PolicyStatement } from './policy-statement'; import { AddToPrincipalPolicyResult, IGrantable, IPrincipal, PrincipalPolicyFragment } from './principals'; @@ -15,7 +15,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * A managed policy */ -export interface IManagedPolicy { +export interface IManagedPolicy extends IManagedPolicyRef { /** * The ARN of the managed policy * @attribute @@ -123,6 +123,11 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl resource: 'policy', resourceName: managedPolicyName, }); + public get managedPolicyRef(): ManagedPolicyReference { + return { + policyArn: this.managedPolicyArn, + }; + } } return new Import(scope, id); } @@ -149,6 +154,11 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl public static fromManagedPolicyArn(scope: Construct, id: string, managedPolicyArn: string): IManagedPolicy { class Import extends Resource implements IManagedPolicy { public readonly managedPolicyArn = managedPolicyArn; + public get managedPolicyRef(): ManagedPolicyReference { + return { + policyArn: this.managedPolicyArn, + }; + } } return new Import(scope, id); } @@ -172,6 +182,14 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl resource: 'policy', resourceName: managedPolicyName, }); + public get managedPolicyRef(): ManagedPolicyReference { + return { + policyArn: this.managedPolicyArn, + }; + } + public get node(): Node { + throw new UnscopedValidationError('The result of fromAwsManagedPolicyName can not be used in this API'); + } } return new AwsManagedPolicy(); } @@ -279,6 +297,12 @@ export class ManagedPolicy extends Resource implements IManagedPolicy, IGrantabl this.node.addValidation({ validate: () => this.validateManagedPolicy() }); } + public get managedPolicyRef(): ManagedPolicyReference { + return { + policyArn: this.managedPolicyArn, + }; + } + /** * Adds a statement to the policy document. */ diff --git a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider-native.ts b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider-native.ts index 2d379f3e66643..0ecc6c8c67af5 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider-native.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider-native.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnOIDCProvider } from './iam.generated'; +import { CfnOIDCProvider, IOIDCProviderRef, OIDCProviderReference } from './iam.generated'; import { Arn, IResource, Resource, Token, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -8,7 +8,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; * Represents an IAM OpenID Connect provider. * */ -export interface IOidcProvider extends IResource { +export interface IOidcProvider extends IResource, IOIDCProviderRef { /** * The Amazon Resource Name (ARN) of the IAM OpenID Connect provider. * @@ -134,6 +134,12 @@ export class OidcProviderNative extends Resource implements IOidcProvider { class Import extends Resource implements IOidcProvider { public readonly oidcProviderArn = oidcProviderArn; public readonly oidcProviderIssuer = resourceName; + + public get oidcProviderRef(): OIDCProviderReference { + return { + oidcProviderArn: this.oidcProviderArn, + }; + } } return new Import(scope, id); @@ -227,4 +233,10 @@ export class OidcProviderNative extends Resource implements IOidcProvider { this.oidcProviderThumbprints = Token.asString(props.thumbprints); } + + public get oidcProviderRef(): OIDCProviderReference { + return { + oidcProviderArn: this.oidcProviderArn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts index 00a2256f400b5..b3922e2fdbaea 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/oidc-provider.ts @@ -1,12 +1,6 @@ import { Construct } from 'constructs'; -import { - Arn, - CustomResource, - FeatureFlags, - IResource, - Resource, - Token, -} from '../../core'; +import { IOIDCProviderRef, OIDCProviderReference } from './iam.generated'; +import { Arn, CustomResource, FeatureFlags, IResource, Resource, Token } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; import { OidcProvider } from '../../custom-resource-handlers/dist/aws-iam/oidc-provider.generated'; @@ -18,7 +12,7 @@ const RESOURCE_TYPE = 'Custom::AWSCDKOpenIdConnectProvider'; * Represents an IAM OpenID Connect provider. * */ -export interface IOpenIdConnectProvider extends IResource { +export interface IOpenIdConnectProvider extends IResource, IOIDCProviderRef { /** * The Amazon Resource Name (ARN) of the IAM OpenID Connect provider. */ @@ -137,6 +131,11 @@ export class OpenIdConnectProvider extends Resource implements IOpenIdConnectPro class Import extends Resource implements IOpenIdConnectProvider { public readonly openIdConnectProviderArn = openIdConnectProviderArn; public readonly openIdConnectProviderIssuer = resourceName; + public get oidcProviderRef(): OIDCProviderReference { + return { + oidcProviderArn: this.openIdConnectProviderArn, + }; + } } return new Import(scope, id); @@ -189,6 +188,12 @@ export class OpenIdConnectProvider extends Resource implements IOpenIdConnectPro this.openIdConnectProviderthumbprints = Token.asString(resource.getAtt('Thumbprints')); } + public get oidcProviderRef(): OIDCProviderReference { + return { + oidcProviderArn: this.openIdConnectProviderArn, + }; + } + private getOrCreateProvider() { return OidcProvider.getOrCreateProvider(this, RESOURCE_TYPE, { policyStatements: [ diff --git a/packages/aws-cdk-lib/aws-iam/lib/policy.ts b/packages/aws-cdk-lib/aws-iam/lib/policy.ts index 8ff9fd8df455f..8dff699522d64 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/policy.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/policy.ts @@ -1,13 +1,13 @@ import { Construct } from 'constructs'; import { IGroup } from './group'; -import { CfnPolicy } from './iam.generated'; +import { CfnPolicy, IPolicyRef, PolicyReference } from './iam.generated'; import { PolicyDocument } from './policy-document'; import { PolicyStatement } from './policy-statement'; import { AddToPrincipalPolicyResult, IGrantable, IPrincipal, PrincipalPolicyFragment } from './principals'; import { generatePolicyName, undefinedIfEmpty } from './private/util'; import { IRole } from './role'; import { IUser } from './user'; -import { IResource, Lazy, Resource, UnscopedValidationError } from '../../core'; +import { IResource, Lazy, Resource, UnscopedValidationError, ValidationError } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -16,7 +16,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; * * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_manage.html */ -export interface IPolicy extends IResource { +export interface IPolicy extends IResource, IPolicyRef { /** * The name of this policy. * @@ -117,6 +117,10 @@ export class Policy extends Resource implements IPolicy, IGrantable { public static fromPolicyName(scope: Construct, id: string, policyName: string): IPolicy { class Import extends Resource implements IPolicy { public readonly policyName = policyName; + + public get policyRef(): PolicyReference { + throw new ValidationError('Cannot use a Policy.fromPolicyName() here.', this); + } } return new Import(scope, id); @@ -128,6 +132,7 @@ export class Policy extends Resource implements IPolicy, IGrantable { public readonly document = new PolicyDocument(); public readonly grantPrincipal: IPrincipal; + public readonly policyRef: PolicyReference; private readonly _policyName: string; private readonly roles = new Array(); @@ -172,6 +177,10 @@ export class Policy extends Resource implements IPolicy, IGrantable { groups: undefinedIfEmpty(() => this.groups.map(g => g.groupName)), }); + this.policyRef = { + policyId: resource.attrId, + }; + this._policyName = this.physicalName!; this.force = props.force ?? false; diff --git a/packages/aws-cdk-lib/aws-iam/lib/private/immutable-role.ts b/packages/aws-cdk-lib/aws-iam/lib/private/immutable-role.ts index 8e127abbae815..b80a940b4e692 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/private/immutable-role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/private/immutable-role.ts @@ -3,6 +3,7 @@ import { Resource } from '../../../core'; import { addConstructMetadata, MethodMetadata } from '../../../core/lib/metadata-resource'; import { propertyInjectable } from '../../../core/lib/prop-injectable'; import { Grant } from '../grant'; +import { RoleReference } from '../iam.generated'; import { IManagedPolicy } from '../managed-policy'; import { Policy } from '../policy'; import { PolicyStatement } from '../policy-statement'; @@ -49,6 +50,13 @@ export class ImmutableRole extends Resource implements IRole { this.node.defaultChild = role.node.defaultChild; } + public get roleRef(): RoleReference { + return { + roleName: this.roleName, + roleArn: this.roleArn, + }; + } + @MethodMetadata() public attachInlinePolicy(_policy: Policy): void { // do nothing diff --git a/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts b/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts index 862d562f0154e..a55b0e0f96729 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/private/imported-role.ts @@ -1,15 +1,22 @@ import { Construct } from 'constructs'; import { MAX_POLICY_NAME_LEN } from './util'; -import { FeatureFlags, Names, Resource, Token, TokenComparison, Annotations } from '../../../core'; +import { Annotations, FeatureFlags, Names, Resource, Token, TokenComparison } from '../../../core'; import { addConstructMetadata, MethodMetadata } from '../../../core/lib/metadata-resource'; import { propertyInjectable } from '../../../core/lib/prop-injectable'; import { IAM_IMPORTED_ROLE_STACK_SAFE_DEFAULT_POLICY_NAME } from '../../../cx-api'; import { Grant } from '../grant'; +import { RoleReference } from '../iam.generated'; import { IManagedPolicy, ManagedPolicy } from '../managed-policy'; import { Policy } from '../policy'; import { PolicyStatement } from '../policy-statement'; -import { IComparablePrincipal, IPrincipal, ArnPrincipal, AddToPrincipalPolicyResult, PrincipalPolicyFragment } from '../principals'; -import { IRole, FromRoleArnOptions } from '../role'; +import { + AddToPrincipalPolicyResult, + ArnPrincipal, + IComparablePrincipal, + IPrincipal, + PrincipalPolicyFragment, +} from '../principals'; +import { FromRoleArnOptions, IRole } from '../role'; import { AttachedPolicies } from '../util'; export interface ImportedRoleProps extends FromRoleArnOptions { @@ -46,6 +53,13 @@ export class ImportedRole extends Resource implements IRole, IComparablePrincipa this.principalAccount = props.account; } + public get roleRef(): RoleReference { + return { + roleName: this.roleName, + roleArn: this.roleArn, + }; + } + @MethodMetadata() public addToPolicy(statement: PolicyStatement): boolean { return this.addToPrincipalPolicy(statement).statementAdded; diff --git a/packages/aws-cdk-lib/aws-iam/lib/private/precreated-role.ts b/packages/aws-cdk-lib/aws-iam/lib/private/precreated-role.ts index 49945af103cc8..a4ce53077cc84 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/private/precreated-role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/private/precreated-role.ts @@ -4,6 +4,7 @@ import { PolicySynthesizer } from '../../../core/lib/helpers-internal'; import { addConstructMetadata, MethodMetadata } from '../../../core/lib/metadata-resource'; import { propertyInjectable } from '../../../core/lib/prop-injectable'; import { Grant } from '../grant'; +import { RoleReference } from '../iam.generated'; import { IManagedPolicy } from '../managed-policy'; import { Policy } from '../policy'; import { PolicyDocument } from '../policy-document'; @@ -105,6 +106,13 @@ export class PrecreatedRole extends Resource implements IRole { }); } + public get roleRef(): RoleReference { + return { + roleName: this.roleName, + roleArn: this.roleArn, + }; + } + @MethodMetadata() public attachInlinePolicy(policy: Policy): void { const statements = policy.document.toJSON()?.Statement; diff --git a/packages/aws-cdk-lib/aws-iam/lib/role.ts b/packages/aws-cdk-lib/aws-iam/lib/role.ts index a036dd24900dc..3922108ea4c1f 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/role.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/role.ts @@ -1,6 +1,6 @@ import { Construct, IConstruct, DependencyGroup, Node } from 'constructs'; import { Grant } from './grant'; -import { CfnRole } from './iam.generated'; +import { CfnRole, IRoleRef, RoleReference } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy, ManagedPolicy } from './managed-policy'; import { Policy } from './policy'; @@ -577,6 +577,13 @@ export class Role extends Resource implements IRole { this.node.addValidation({ validate: () => this.validateRole() }); } + public get roleRef(): RoleReference { + return { + roleName: this.roleName, + roleArn: this.roleArn, + }; + } + /** * Adds a permission to the role's default policy document. * If there is no default policy attached to this role, it will be created. @@ -787,7 +794,7 @@ export class Role extends Resource implements IRole { /** * A Role object */ -export interface IRole extends IIdentity { +export interface IRole extends IIdentity, IRoleRef { /** * Returns the ARN of this role. * diff --git a/packages/aws-cdk-lib/aws-iam/lib/saml-provider.ts b/packages/aws-cdk-lib/aws-iam/lib/saml-provider.ts index d9a29348baae0..211e40e19ba8c 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/saml-provider.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/saml-provider.ts @@ -1,6 +1,6 @@ import * as fs from 'fs'; import { Construct } from 'constructs'; -import { CfnSAMLProvider } from './iam.generated'; +import { CfnSAMLProvider, ISAMLProviderRef, SAMLProviderReference } from './iam.generated'; import { IResource, Resource, Token, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -8,7 +8,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; /** * A SAML provider */ -export interface ISamlProvider extends IResource { +export interface ISamlProvider extends IResource, ISAMLProviderRef { /** * The Amazon Resource Name (ARN) of the provider * @@ -83,6 +83,7 @@ export class SamlProvider extends Resource implements ISamlProvider { public static fromSamlProviderArn(scope: Construct, id: string, samlProviderArn: string): ISamlProvider { class Import extends Resource implements ISamlProvider { public readonly samlProviderArn = samlProviderArn; + public samlProviderRef: SAMLProviderReference = { samlProviderArn }; } return new Import(scope, id); } @@ -105,4 +106,10 @@ export class SamlProvider extends Resource implements ISamlProvider { this.samlProviderArn = samlProvider.ref; } + + public get samlProviderRef(): SAMLProviderReference { + return { + samlProviderArn: this.samlProviderArn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-iam/lib/user.ts b/packages/aws-cdk-lib/aws-iam/lib/user.ts index 547ae436ba7d8..67caa906d33c6 100644 --- a/packages/aws-cdk-lib/aws-iam/lib/user.ts +++ b/packages/aws-cdk-lib/aws-iam/lib/user.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { IGroup } from './group'; -import { CfnUser, CfnUserToGroupAddition } from './iam.generated'; +import { CfnUser, CfnUserToGroupAddition, IUserRef, UserReference } from './iam.generated'; import { IIdentity } from './identity-base'; import { IManagedPolicy } from './managed-policy'; import { Policy } from './policy'; @@ -16,7 +16,7 @@ import { propertyInjectable } from '../../core/lib/prop-injectable'; * * @see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html */ -export interface IUser extends IIdentity { +export interface IUser extends IIdentity, IUserRef { /** * The user's name * @attribute @@ -229,6 +229,13 @@ export class User extends Resource implements IIdentity, IUser { public addManagedPolicy(_policy: IManagedPolicy): void { throw new ValidationError('Cannot add managed policy to imported User', this); } + + public get userRef(): UserReference { + return { + userName: this.userName, + userArn: this.userArn, + }; + } } return new Import(scope, id); @@ -297,6 +304,13 @@ export class User extends Resource implements IIdentity, IUser { } } + public get userRef(): UserReference { + return { + userName: this.userName, + userArn: this.userArn, + }; + } + /** * Adds this user to a group. */ diff --git a/packages/aws-cdk-lib/aws-kms/lib/alias.ts b/packages/aws-cdk-lib/aws-kms/lib/alias.ts index 801ace2813050..aedf26d4c6490 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/alias.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/alias.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { IKey } from './key'; -import { CfnAlias } from './kms.generated'; +import { AliasReference, CfnAlias, IAliasRef, KeyReference } from './kms.generated'; import * as iam from '../../aws-iam'; import * as perms from './private/perms'; import { FeatureFlags, RemovalPolicy, Resource, Stack, Token, Tokenization, ValidationError } from '../../core'; @@ -15,7 +15,7 @@ const DISALLOWED_PREFIX = REQUIRED_ALIAS_PREFIX + 'aws/'; * A KMS Key alias. * An alias can be used in all places that expect a key. */ -export interface IAlias extends IKey { +export interface IAlias extends IKey, IAliasRef { /** * The name of the alias. * @@ -62,6 +62,22 @@ abstract class AliasBase extends Resource implements IAlias { public abstract readonly aliasTargetKey: IKey; + public get aliasRef(): AliasReference { + return { + aliasName: this.aliasName, + }; + } + + public get keyRef(): KeyReference { + // Not actually referering to the key: `IKeyRef` here is being used as a + // hypothetical `IKeyLikeRef`, and we need to return the Alias values using + // the Key interface. + return { + keyArn: this.aliasArn, + keyId: this.keyId, + }; + } + /** * The ARN of the alias. * @@ -209,6 +225,13 @@ export class Alias extends AliasBase { return { statementAdded: false }; } + public get keyRef(): KeyReference { + return { + keyArn: this.keyArn, + keyId: this.keyId, + }; + } + public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { if (!FeatureFlags.of(this).isEnabled(KMS_APPLY_IMPORTED_ALIAS_PERMISSIONS_TO_PRINCIPAL)) { return iam.Grant.drop(grantee, ''); @@ -229,6 +252,12 @@ export class Alias extends AliasBase { }); } + public get aliasRef(): AliasReference { + return { + aliasName: this.aliasName, + }; + } + public grantDecrypt(grantee: iam.IGrantable): iam.Grant { return this.grant(grantee, ...perms.DECRYPT_ACTIONS); } diff --git a/packages/aws-cdk-lib/aws-kms/lib/key.ts b/packages/aws-cdk-lib/aws-kms/lib/key.ts index aeebd716af54f..c5033a88458bc 100644 --- a/packages/aws-cdk-lib/aws-kms/lib/key.ts +++ b/packages/aws-cdk-lib/aws-kms/lib/key.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; import { Alias } from './alias'; import { KeyLookupOptions } from './key-lookup'; -import { CfnKey } from './kms.generated'; +import { CfnKey, IKeyRef, KeyReference } from './kms.generated'; import * as perms from './private/perms'; import * as iam from '../../aws-iam'; import * as cxschema from '../../cloud-assembly-schema'; @@ -26,8 +26,11 @@ import * as cxapi from '../../cx-api'; /** * A KMS Key, either managed by this CDK app, or imported. + * + * This interface does double duty: it represents an actual KMS keys, but it + * also represents things that can behave like KMS keys, like a key alias. */ -export interface IKey extends IResource { +export interface IKey extends IResource, IKeyRef { /** * The ARN of the key. * @@ -141,6 +144,13 @@ abstract class KeyBase extends Resource implements IKey { this.node.addValidation({ validate: () => this.policy?.validateForResourcePolicy() ?? [] }); } + public get keyRef(): KeyReference { + return { + keyArn: this.keyArn, + keyId: this.keyId, + }; + } + /** * Defines a new alias for the key. */ diff --git a/packages/aws-cdk-lib/aws-kms/test/alias.test.ts b/packages/aws-cdk-lib/aws-kms/test/alias.test.ts index 399747167a1f4..2c209274a945e 100644 --- a/packages/aws-cdk-lib/aws-kms/test/alias.test.ts +++ b/packages/aws-cdk-lib/aws-kms/test/alias.test.ts @@ -911,6 +911,19 @@ test('aliasArn should be a valid ARN', () => { }, stack)); }); +test('Alias keyRef should reference the Alias, not the underlying key', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'Test'); + const key = new Key(stack, 'Key'); + + // WHEN + const alias = key.addAlias('alias/foo'); + + // THEN + expect(alias.keyRef.keyArn).toEqual(alias.aliasArn); +}); + class AliasOutputsConstruct extends Construct { constructor(scope: Construct, id: string, key: IKey) { super(scope, id); diff --git a/packages/aws-cdk-lib/aws-lambda/lib/alias.ts b/packages/aws-cdk-lib/aws-lambda/lib/alias.ts index e7b0797d34db3..9c4703d7adca5 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/alias.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/alias.ts @@ -3,7 +3,7 @@ import { Architecture } from './architecture'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { IFunction, QualifiedFunctionBase } from './function-base'; import { extractQualifierFromArn, IVersion } from './lambda-version'; -import { CfnAlias } from './lambda.generated'; +import { AliasReference, CfnAlias, IAliasRef } from './lambda.generated'; import { ScalableFunctionAttribute } from './private/scalable-function-attribute'; import { AutoScalingOptions, IScalableFunctionAttribute } from './scalable-attribute-api'; import * as appscaling from '../../aws-applicationautoscaling'; @@ -14,7 +14,7 @@ import { ValidationError } from '../../core/lib/errors'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; -export interface IAlias extends IFunction { +export interface IAlias extends IFunction, IAliasRef { /** * Name of this alias. * @@ -109,6 +109,12 @@ export class Alias extends QualifiedFunctionBase implements IAlias { protected readonly canCreatePermissions = this._isStackAccount(); protected readonly qualifier = attrs.aliasName; + + public get aliasRef(): AliasReference { + return { + aliasArn: this.functionArn, + }; + } } return new Imported(scope, id); } @@ -202,6 +208,12 @@ export class Alias extends QualifiedFunctionBase implements IAlias { this.functionName = `${this.stack.splitArn(this.functionArn, ArnFormat.COLON_RESOURCE_NAME).resourceName!}:${this.aliasName}`; } + public get aliasRef(): AliasReference { + return { + aliasArn: this.functionArn, + }; + } + public get grantPrincipal() { return this.version.grantPrincipal; } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/code-signing-config.ts b/packages/aws-cdk-lib/aws-lambda/lib/code-signing-config.ts index 613c9d1adf882..f398e6d07aed4 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/code-signing-config.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/code-signing-config.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnCodeSigningConfig } from './lambda.generated'; +import { CfnCodeSigningConfig, CodeSigningConfigReference, ICodeSigningConfigRef } from './lambda.generated'; import { ISigningProfile } from '../../aws-signer'; import { ArnFormat, IResource, Resource, Stack } from '../../core'; import { ValidationError } from '../../core/lib/errors'; @@ -25,7 +25,7 @@ export enum UntrustedArtifactOnDeployment { /** * A Code Signing Config */ -export interface ICodeSigningConfig extends IResource { +export interface ICodeSigningConfig extends IResource, ICodeSigningConfigRef { /** * The ARN of Code Signing Config * @attribute @@ -100,6 +100,12 @@ export class CodeSigningConfig extends Resource implements ICodeSigningConfig { constructor() { super(scope, id); } + + public get codeSigningConfigRef(): CodeSigningConfigReference { + return { + codeSigningConfigArn: this.codeSigningConfigArn, + }; + } } return new Import(); } @@ -128,4 +134,10 @@ export class CodeSigningConfig extends Resource implements ICodeSigningConfig { this.codeSigningConfigArn = resource.attrCodeSigningConfigArn; this.codeSigningConfigId = resource.attrCodeSigningConfigId; } + + public get codeSigningConfigRef(): CodeSigningConfigReference { + return { + codeSigningConfigArn: this.codeSigningConfigArn, + }; + } } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/event-source-mapping.ts b/packages/aws-cdk-lib/aws-lambda/lib/event-source-mapping.ts index df14694d7bbc7..b89a3693013d3 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/event-source-mapping.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/event-source-mapping.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; import { IEventSourceDlq } from './dlq'; import { IFunction } from './function-base'; -import { CfnEventSourceMapping } from './lambda.generated'; +import { CfnEventSourceMapping, EventSourceMappingReference, IEventSourceMappingRef } from './lambda.generated'; import { ISchemaRegistry } from './schema-registry'; import * as iam from '../../aws-iam'; import { IKey } from '../../aws-kms'; @@ -352,7 +352,7 @@ export interface EventSourceMappingProps extends EventSourceMappingOptions { * Represents an event source mapping for a lambda function. * @see https://docs.aws.amazon.com/lambda/latest/dg/invocation-eventsourcemapping.html */ -export interface IEventSourceMapping extends cdk.IResource { +export interface IEventSourceMapping extends cdk.IResource, IEventSourceMappingRef { /** * The identifier for this EventSourceMapping * @attribute @@ -401,6 +401,13 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp class Import extends cdk.Resource implements IEventSourceMapping { public readonly eventSourceMappingId = eventSourceMappingId; public readonly eventSourceMappingArn = eventSourceMappingArn; + + public get eventSourceMappingRef(): EventSourceMappingReference { + return { + eventSourceMappingId, + eventSourceMappingArn, + }; + } } return new Import(scope, id); } @@ -570,6 +577,13 @@ export class EventSourceMapping extends cdk.Resource implements IEventSourceMapp this.eventSourceMappingArn = EventSourceMapping.formatArn(this, this.eventSourceMappingId); } + public get eventSourceMappingRef(): EventSourceMappingReference { + return { + eventSourceMappingId: this.eventSourceMappingId, + eventSourceMappingArn: this.eventSourceMappingArn, + }; + } + private validateKafkaConsumerGroupIdOrThrow(kafkaConsumerGroupId: string) { if (cdk.Token.isUnresolved(kafkaConsumerGroupId)) { return; diff --git a/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts b/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts index 0004962970d2b..0953ba81ce872 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/function-base.ts @@ -5,20 +5,20 @@ import { Architecture } from './architecture'; import { EventInvokeConfig, EventInvokeConfigOptions } from './event-invoke-config'; import { IEventSource } from './event-source'; import { EventSourceMapping, EventSourceMappingOptions } from './event-source-mapping'; -import { FunctionUrlAuthType, FunctionUrlOptions, FunctionUrl } from './function-url'; +import { FunctionUrl, FunctionUrlAuthType, FunctionUrlOptions } from './function-url'; import { IVersion } from './lambda-version'; -import { CfnPermission } from './lambda.generated'; +import { CfnPermission, FunctionReference, IFunctionRef, VersionReference } from './lambda.generated'; import { Permission } from './permission'; import { addAlias, flatMap } from './util'; import * as cloudwatch from '../../aws-cloudwatch'; import * as ec2 from '../../aws-ec2'; import * as iam from '../../aws-iam'; -import { Annotations, ArnFormat, IResource, Resource, Token, Stack, FeatureFlags } from '../../core'; +import { Annotations, ArnFormat, FeatureFlags, IResource, Resource, Stack, Token } from '../../core'; import { ValidationError } from '../../core/lib/errors'; import { MethodMetadata } from '../../core/lib/metadata-resource'; import * as cxapi from '../../cx-api'; -export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable { +export interface IFunction extends IResource, ec2.IConnectable, iam.IGrantable, IFunctionRef { /** * The name of the function. @@ -342,6 +342,13 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC */ private _hasAddedArrayTokenStatements: boolean = false; + public get functionRef(): FunctionReference { + return { + functionName: this.functionName, + functionArn: this.functionArn, + }; + } + /** * A warning will be added to functions under the following conditions: * - permissions that include `lambda:InvokeFunction` are added to the unqualified function. @@ -857,6 +864,19 @@ class LatestVersion extends FunctionBase implements IVersion { this.lambda = lambda; } + public get versionRef(): VersionReference { + return { + functionArn: this.functionArn, + }; + } + + public get functionRef(): FunctionReference { + return { + functionArn: this.functionArn, + functionName: this.functionName, + }; + } + public get functionArn() { return `${this.lambda.functionArn}:${this.version}`; } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/lambda-version.ts b/packages/aws-cdk-lib/aws-lambda/lib/lambda-version.ts index 4738d3b0ab95d..9b518678f73d9 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/lambda-version.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/lambda-version.ts @@ -5,7 +5,7 @@ import { Architecture } from './architecture'; import { EventInvokeConfigOptions } from './event-invoke-config'; import { Function } from './function'; import { IFunction, QualifiedFunctionBase } from './function-base'; -import { CfnVersion } from './lambda.generated'; +import { CfnVersion, IVersionRef, VersionReference } from './lambda.generated'; import { addAlias } from './util'; import * as cloudwatch from '../../aws-cloudwatch'; import { Fn, Lazy, RemovalPolicy, Token } from '../../core'; @@ -13,7 +13,7 @@ import { ValidationError } from '../../core/lib/errors'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; -export interface IVersion extends IFunction { +export interface IVersion extends IFunction, IVersionRef { /** * The most recently deployed version of this function. * @attribute @@ -154,6 +154,12 @@ export class Version extends QualifiedFunctionBase implements IVersion { } return this.functionArn; } + + public get versionRef(): VersionReference { + return { + functionArn: this.functionArn, + }; + } } return new Import(scope, id); } @@ -181,6 +187,12 @@ export class Version extends QualifiedFunctionBase implements IVersion { } return this.functionArn; } + + public get versionRef(): VersionReference { + return { + functionArn: this.functionArn, + }; + } } return new Import(scope, id); } @@ -231,6 +243,12 @@ export class Version extends QualifiedFunctionBase implements IVersion { } } + public get versionRef(): VersionReference { + return { + functionArn: this.functionArn, + }; + } + public get grantPrincipal() { return this.lambda.grantPrincipal; } diff --git a/packages/aws-cdk-lib/aws-lambda/lib/layers.ts b/packages/aws-cdk-lib/aws-lambda/lib/layers.ts index c5c196380a4bf..eb3b5d040e862 100644 --- a/packages/aws-cdk-lib/aws-lambda/lib/layers.ts +++ b/packages/aws-cdk-lib/aws-lambda/lib/layers.ts @@ -1,7 +1,12 @@ import { Construct } from 'constructs'; import { Architecture } from './architecture'; import { Code } from './code'; -import { CfnLayerVersion, CfnLayerVersionPermission } from './lambda.generated'; +import { + CfnLayerVersion, + CfnLayerVersionPermission, + ILayerVersionRef, + LayerVersionReference, +} from './lambda.generated'; import { Runtime } from './runtime'; import { IResource, RemovalPolicy, Resource } from '../../core'; import { ValidationError } from '../../core/lib/errors'; @@ -64,7 +69,7 @@ export interface LayerVersionProps extends LayerVersionOptions { readonly code: Code; } -export interface ILayerVersion extends IResource { +export interface ILayerVersion extends IResource, ILayerVersionRef { /** * The ARN of the Lambda Layer version that this Layer defines. * @attribute @@ -99,6 +104,12 @@ abstract class LayerVersionBase extends Resource implements ILayerVersion { public abstract readonly layerVersionArn: string; public abstract readonly compatibleRuntimes?: Runtime[]; + public get layerVersionRef(): LayerVersionReference { + return { + layerVersionArn: this.layerVersionArn, + }; + } + public addPermission(id: string, permission: LayerVersionPermission) { if (permission.organizationId != null && permission.accountId !== '*') { throw new ValidationError(`OrganizationId can only be specified if AwsAccountId is '*', but it is ${permission.accountId}`, this); diff --git a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts index 34ee5d33c52bd..6bd99e9648b51 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/alias.test.ts @@ -657,4 +657,24 @@ describe('alias', () => { Qualifier: aliasName, }); }); + + test('alias\' implementation of IFunctionRef should point to the alias', () => { + // GIVEN + const stack = new Stack(); + const fn = new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('hello()'), + handler: 'index.hello', + runtime: lambda.Runtime.NODEJS_LATEST, + }); + const aliasName = 'prod'; + + // WHEN + const alias = new lambda.Alias(stack, 'Alias', { + aliasName, + version: fn.currentVersion, + }); + + // THEN + expect(alias.functionRef.functionArn).toEqual(alias.functionArn); + }); }); 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 f7a97c82c2a99..29fd3fcafce92 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/function.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/function.test.ts @@ -2684,6 +2684,21 @@ describe('function', () => { expect(stack.resolve(version2.functionArn)).toEqual(expectedArn); }); + test('latestVersion functionRef ARN is the version ARN, not the plain ARN', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const fn = new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('hello()'), + handler: 'index.hello', + runtime: lambda.Runtime.NODEJS_LATEST, + }); + + // THEN + expect(fn.latestVersion.functionRef.functionArn).toEqual(fn.latestVersion.functionArn); + }); + test('default function with kmsKeyArn, environmentEncryption passed as props', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/aws-cdk-lib/aws-lambda/test/lambda-version.test.ts b/packages/aws-cdk-lib/aws-lambda/test/lambda-version.test.ts index 5b7743379f6ed..edc8b01465487 100644 --- a/packages/aws-cdk-lib/aws-lambda/test/lambda-version.test.ts +++ b/packages/aws-cdk-lib/aws-lambda/test/lambda-version.test.ts @@ -229,4 +229,22 @@ describe('lambda version', () => { version.addFunctionUrl(); }).toThrow(/FunctionUrl cannot be used with a Version/); }); + + test('version\'s implementation of IFunctionRef should point to the version', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new lambda.Function(stack, 'MyLambda', { + code: new lambda.InlineCode('hello()'), + handler: 'index.hello', + runtime: lambda.Runtime.NODEJS_LATEST, + }); + + // WHEN + const ver = new lambda.Version(stack, 'Version', { + lambda: fn, + }); + + // THEN + expect(ver.functionRef.functionArn).toEqual(ver.functionArn); + }); }); diff --git a/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts b/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts index 8398c5f08cf31..1d1e7cca5918d 100644 --- a/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts +++ b/packages/aws-cdk-lib/aws-logs/test/loggroup.test.ts @@ -986,6 +986,33 @@ describe('subscription filter', () => { }); }); +test('encrypting log group with referenced alias', () => { + const stack = new Stack(); + + const alias = kms.Alias.fromAliasName(stack, 'KmsAlias', 'alias/some-alias'); + + new LogGroup(stack, 'LogGroup', { + encryptionKey: alias, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + KmsKeyId: { + 'Fn::Join': [ + '', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':kms:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':alias/some-alias', + ], + ], + }, + }); +}); + test('create a Add Key transformer against a log group', () => { // GIVEN const stack = new Stack(); diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket-policy.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket-policy.ts index da61e88b52778..002dc781b3d1c 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket-policy.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket-policy.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { Bucket, IBucket } from './bucket'; -import { CfnBucket, CfnBucketPolicy } from './s3.generated'; +import { BucketPolicyReference, CfnBucket, CfnBucketPolicy, IBucketPolicyRef } from './s3.generated'; import { PolicyDocument } from '../../aws-iam'; import { RemovalPolicy, Resource, Token, Tokenization } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; @@ -49,7 +49,7 @@ export interface BucketPolicyProps { * */ @propertyInjectable -export class BucketPolicy extends Resource { +export class BucketPolicy extends Resource implements IBucketPolicyRef { /** Uniquely identifies this class. */ public static readonly PROPERTY_INJECTION_ID: string = 'aws-cdk-lib.aws-s3.BucketPolicy'; @@ -88,11 +88,14 @@ export class BucketPolicy extends Resource { }(cfnBucketPolicy, id, { bucket, }); + // mark the Bucket as having this Policy bucket.policy = ret; return ret; } + public readonly bucketPolicyRef: BucketPolicyReference; + /** * A policy document containing permissions to add to the specified bucket. * For more information, see Access Policy Language Overview in the Amazon @@ -116,6 +119,7 @@ export class BucketPolicy extends Resource { bucket: this.bucket.bucketName, policyDocument: this.document, }); + this.bucketPolicyRef = this.resource.bucketPolicyRef; if (props.removalPolicy) { this.resource.applyRemovalPolicy(props.removalPolicy); diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index 30a2dc224a7b5..76482f0343498 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -5,18 +5,20 @@ import { IBucketNotificationDestination } from './destination'; import { BucketNotifications } from './notifications-resource'; import * as perms from './perms'; import { LifecycleRule, StorageClass } from './rule'; -import { CfnBucket } from './s3.generated'; +import { BucketReference, CfnBucket, IBucketRef } from './s3.generated'; import { parseBucketArn, parseBucketName } from './util'; import * as events from '../../aws-events'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; import { + Annotations, CustomResource, Duration, FeatureFlags, Fn, IResource, Lazy, + PhysicalName, RemovalPolicy, Resource, ResourceProps, @@ -24,21 +26,21 @@ import { Tags, Token, Tokenization, - Annotations, - PhysicalName, } from '../../core'; import { UnscopedValidationError, ValidationError } from '../../core/lib/errors'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { CfnReference } from '../../core/lib/private/cfn-reference'; import { propertyInjectable } from '../../core/lib/prop-injectable'; -import { AutoDeleteObjectsProvider } from '../../custom-resource-handlers/dist/aws-s3/auto-delete-objects-provider.generated'; +import { + AutoDeleteObjectsProvider, +} from '../../custom-resource-handlers/dist/aws-s3/auto-delete-objects-provider.generated'; import * as cxapi from '../../cx-api'; import * as regionInformation from '../../region-info'; const AUTO_DELETE_OBJECTS_RESOURCE_TYPE = 'Custom::S3AutoDeleteObjects'; const AUTO_DELETE_OBJECTS_TAG = 'aws-cdk:auto-delete-objects'; -export interface IBucket extends IResource { +export interface IBucket extends IResource, IBucketRef { /** * The ARN of the bucket. * @attribute @@ -1150,6 +1152,13 @@ export abstract class BucketBase extends Resource implements IBucket { return ret; } + + public get bucketRef(): BucketReference { + return { + bucketArn: this.bucketArn, + bucketName: this.bucketName, + }; + } } export interface BlockPublicAccessOptions { diff --git a/packages/aws-cdk-lib/awslint.json b/packages/aws-cdk-lib/awslint.json index 63efaffaf78d1..8cdfaa9b69b4e 100644 --- a/packages/aws-cdk-lib/awslint.json +++ b/packages/aws-cdk-lib/awslint.json @@ -1002,6 +1002,67 @@ "construct-ctor-props-type:aws-cdk-lib.aws_inspector.AssessmentTemplate", "props-physical-name:aws-cdk-lib.aws_inspector.AssessmentTemplateProps", "attribute-tag:aws-cdk-lib.aws_scheduler.Schedule.scheduleGroup", - "docs-public-apis:aws-cdk-lib.aws_scheduler.ContextAttribute.name" + "docs-public-apis:aws-cdk-lib.aws_scheduler.ContextAttribute.name", + "docs-public-apis:aws-cdk-lib.aws_apigateway.ApiKey.apiKeyRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.DomainName.domainNameRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.GatewayResponse.gatewayResponseRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.RateLimitedApiKey.apiKeyRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.ResourceBase.resourceRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.RestApiBase.restApiRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.StageBase.stageRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.UsagePlan.usagePlanRef", + "docs-public-apis:aws-cdk-lib.aws_apigateway.VpcLink.vpcLinkRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.CachePolicy.cachePolicyRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.CloudFrontWebDistribution.distributionRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.Distribution.distributionRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.Function.functionRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.KeyGroup.keyGroupRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.KeyValueStore.keyValueStoreRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.OriginRequestPolicy.originRequestPolicyRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.RealtimeLogConfig.realtimeLogConfigRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.ResponseHeadersPolicy.responseHeadersPolicyRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.experimental.EdgeFunction.functionRef", + "docs-public-apis:aws-cdk-lib.aws_cloudfront.experimental.EdgeFunction.versionRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.BastionHostLinux.instanceRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.ClientVpnEndpoint.clientVpnEndpointRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.FlowLog.flowLogRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.Instance.instanceRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.KeyPair.keyPairRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.LaunchTemplate.launchTemplateRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.NetworkAcl.networkAclRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.NetworkAclEntry.networkAclEntryRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.PlacementGroup.placementGroupRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.PrefixList.prefixListRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.SecurityGroup.securityGroupRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.Subnet.subnetRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.SubnetNetworkAclAssociation.subnetNetworkAclAssociationRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.Volume.volumeRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.Vpc.vpcRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.VpcEndpoint.vpcEndpointRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.VpcEndpointService.vpcEndpointServiceRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.VpnConnectionBase.vpnConnectionRef", + "docs-public-apis:aws-cdk-lib.aws_ec2.VpnGateway.vpnGatewayRef", + "docs-public-apis:aws-cdk-lib.aws_iam.AccessKey.accessKeyRef", + "docs-public-apis:aws-cdk-lib.aws_iam.Group.groupRef", + "docs-public-apis:aws-cdk-lib.aws_iam.InstanceProfile.instanceProfileRef", + "docs-public-apis:aws-cdk-lib.aws_iam.LazyRole.roleRef", + "docs-public-apis:aws-cdk-lib.aws_iam.ManagedPolicy.managedPolicyRef", + "docs-public-apis:aws-cdk-lib.aws_iam.OidcProviderNative.oidcProviderRef", + "docs-public-apis:aws-cdk-lib.aws_iam.OpenIdConnectProvider.oidcProviderRef", + "docs-public-apis:aws-cdk-lib.aws_iam.Policy.policyRef", + "docs-public-apis:aws-cdk-lib.aws_iam.Role.roleRef", + "docs-public-apis:aws-cdk-lib.aws_iam.SamlProvider.samlProviderRef", + "docs-public-apis:aws-cdk-lib.aws_iam.User.userRef", + "docs-public-apis:aws-cdk-lib.aws_kms.Alias.aliasRef", + "docs-public-apis:aws-cdk-lib.aws_kms.Alias.keyRef", + "docs-public-apis:aws-cdk-lib.aws_kms.Key.keyRef", + "docs-public-apis:aws-cdk-lib.aws_lambda.Alias.aliasRef", + "docs-public-apis:aws-cdk-lib.aws_lambda.CodeSigningConfig.codeSigningConfigRef", + "docs-public-apis:aws-cdk-lib.aws_lambda.EventSourceMapping.eventSourceMappingRef", + "docs-public-apis:aws-cdk-lib.aws_lambda.FunctionBase.functionRef", + "docs-public-apis:aws-cdk-lib.aws_lambda.LayerVersion.layerVersionRef", + "docs-public-apis:aws-cdk-lib.aws_lambda.Version.versionRef", + "docs-public-apis:aws-cdk-lib.aws_s3.BucketBase.bucketRef", + "docs-public-apis:aws-cdk-lib.aws_s3.BucketPolicy.bucketPolicyRef" ] } diff --git a/packages/awslint/lib/rules/core-types.ts b/packages/awslint/lib/rules/core-types.ts index eb5de41f61e2c..1c0e00f7a5dbf 100644 --- a/packages/awslint/lib/rules/core-types.ts +++ b/packages/awslint/lib/rules/core-types.ts @@ -44,11 +44,7 @@ export class CoreTypes { return false; } - if (!c.name.startsWith('Cfn')) { - return false; - } - - return true; + return c.name.startsWith('Cfn'); } /** @@ -86,10 +82,11 @@ export class CoreTypes { } /** - * Return true if the given interface type is a CFN class or prop type + * Return true if the given interface type is a CFN class, prop type or interface */ public static isCfnType(interfaceType: reflect.Type) { return interfaceType.name.startsWith('Cfn') + || /^I\w+Ref/.test(interfaceType.name) || (interfaceType.namespace && interfaceType.namespace.startsWith('Cfn')) // aws_service.CfnTheResource.SubType || (interfaceType.namespace && interfaceType.namespace.split('.', 2).at(1)?.startsWith('Cfn')); diff --git a/packages/awslint/lib/rules/docs.ts b/packages/awslint/lib/rules/docs.ts index ed70eda0273b8..5de5306098019 100644 --- a/packages/awslint/lib/rules/docs.ts +++ b/packages/awslint/lib/rules/docs.ts @@ -84,6 +84,10 @@ docsLinter.add({ if (isModuleExperimental(e.ctx.assembly)) { return; } + if ((e.ctx.kind === 'type' && CoreTypes.isCfnType(e.ctx.documentable)) + || (e.ctx.kind === 'interface-property' && CoreTypes.isCfnType(e.ctx.containingType))) { + return; + } const sym = e.ctx.documentable; e.assert(sym.docs.docs.stability !== Stability.Experimental, e.ctx.errorKey); }, diff --git a/packages/awslint/lib/rules/resource.ts b/packages/awslint/lib/rules/resource.ts index 243319b0defec..f9a51e89cd30e 100644 --- a/packages/awslint/lib/rules/resource.ts +++ b/packages/awslint/lib/rules/resource.ts @@ -79,6 +79,9 @@ export class ResourceReflection { /** * Attribute properties are all the properties that begin with the type name (e.g. bucketXxx). + * + * We are adding a `bucketRef` property as well that we don't necessarily want to hold to the same linter + * rules as the other attributes, so we don't consider it an attribute. */ private findAttributeProperties(): Attribute[] { const result = new Array(); @@ -87,6 +90,9 @@ export class ResourceReflection { if (p.protected) { continue; // skip any protected properties } + if (p.name.endsWith('Ref')) { + continue; // skip any "Ref" properties, these are not attributes + } const basename = camelize(this.cfn.basename); diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts index 195a844ac2bc2..e1477cacd98cd 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts @@ -37,6 +37,7 @@ export class CdkCore extends ExternalModule { public readonly CfnTag = Type.fromName(this, 'CfnTag'); public readonly TagManager = $T(Type.fromName(this, 'TagManager')); public readonly TagType = $T(Type.fromName(this, 'TagType')); + public readonly Fn = $T(Type.fromName(this, 'Fn')); public readonly ITaggable = Type.fromName(this, 'ITaggable'); public readonly ITaggableV2 = Type.fromName(this, 'ITaggableV2'); public readonly IResolvable = Type.fromName(this, 'IResolvable'); diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts index 7890e6efa5b0a..c1777c3eb34ab 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts @@ -20,10 +20,11 @@ import { Stability, ObjectLiteral, Module, + InterfaceType, } 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 { classNameFromResource, @@ -55,6 +56,20 @@ export class ResourceClass extends ClassType { private readonly resource: Resource, private readonly suffix?: string, ) { + let refInterface: InterfaceType | undefined; + if (shouldBuildReferenceInterface(resource)) { + // IBucketRef { bucketRef: BucketRef } + refInterface = new InterfaceType(scope, { + export: true, + name: `I${resource.name}${suffix ?? ''}Ref`, + extends: [CONSTRUCTS.IConstruct], + docs: { + summary: `Indicates that this resource can be referenced as a ${resource.name}.`, + stability: Stability.Experimental, + }, + }); + } + super(scope, { export: true, name: classNameFromResource(resource, suffix), @@ -67,7 +82,7 @@ export class ResourceClass extends ClassType { }), }, extends: CDK_CORE.CfnResource, - implements: [CDK_CORE.IInspectable, ...ResourceDecider.taggabilityInterfaces(resource)], + implements: [CDK_CORE.IInspectable, refInterface?.type, ...ResourceDecider.taggabilityInterfaces(resource)].filter(isDefined), }); this.module = Module.of(this); @@ -105,6 +120,8 @@ export class ResourceClass extends ClassType { cfnMapping.add(prop.cfnMapping); } + this.buildReferenceInterface(); + // Build the members of this class this.addProperty({ name: staticResourceTypeName(), @@ -153,6 +170,53 @@ export class ResourceClass extends ClassType { this.makeMustRenderStructs(); } + /** + * Build the reference interface for this resource + */ + private buildReferenceInterface() { + if (!shouldBuildReferenceInterface(this.resource)) { + return; + } + + // BucketRef { bucketName, bucketArn } + const refPropsStruct = new StructType(this.scope, { + export: true, + name: `${this.resource.name}${this.suffix ?? ''}Reference`, + docs: { + summary: `A reference to a ${this.resource.name} resource.`, + stability: Stability.External, + }, + }); + + // Build the shared interface + for (const { declaration } of this.decider.referenceProps ?? []) { + refPropsStruct.addProperty(declaration); + } + + /* Add the interface and the return types, but not the required property. + const refProperty = this.refInterface!.addProperty({ + name: `${this.decider.camelResourceName}Ref`, + type: refPropsStruct.type, + immutable: true, + docs: { + summary: `A reference to a ${this.resource.name} resource.`, + }, + }); + */ + + this.addProperty({ + name: `${this.decider.camelResourceName}Ref`, + type: refPropsStruct.type, + getterBody: Block.with( + stmt.ret(expr.object(Object.fromEntries(this.decider.referenceProps.map(({ declaration, cfnValue }) => [declaration.name, cfnValue])))), + ), + immutable: true, + docs: { + summary: `A reference to a ${this.resource.name} resource.`, + }, + }); + } + private makeFromCloudFormationFactory() { const factory = this.addMethod({ name: '_fromCloudFormation', @@ -389,3 +453,10 @@ export class ResourceClass extends ClassType { } } } + +/** + * Type guard to filter out undefined values. + */ +function isDefined(x: T | undefined): x is T { + return x !== undefined; +} diff --git a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts index 23906deef7805..a8db9a5c4c041 100644 --- a/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts +++ b/tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts @@ -4,12 +4,28 @@ import { CDK_CORE } from './cdk'; import { PropertyMapping } from './cloudformation-mapping'; import { NON_RESOLVABLE_PROPERTY_NAMES, TaggabilityStyle, resourceTaggabilityStyle } from './tagging'; import { TypeConverter } from './type-converter'; -import { attributePropertyName, cloudFormationDocLink, propertyNameFromCloudFormation } from '../naming'; +import { attributePropertyName, camelcasedResourceName, cloudFormationDocLink, propertyNameFromCloudFormation, referencePropertyName } from '../naming'; import { splitDocumentation } from '../util'; // 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 */ @@ -25,16 +41,21 @@ export class ResourceDecider { private readonly taggability?: TaggabilityStyle; + public readonly referenceProps = new Array(); public readonly propsProperties = new Array(); public readonly classProperties = new Array(); public readonly classAttributeProperties = new Array(); + public readonly camelResourceName: string; constructor(private readonly resource: Resource, private readonly converter: TypeConverter) { + this.camelResourceName = camelcasedResourceName(resource); this.taggability = resourceTaggabilityStyle(this.resource); this.convertProperties(); this.convertAttributes(); + 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)); this.classAttributeProperties.sort((p1, p2) => p1.propertySpec.name.localeCompare(p2.propertySpec.name)); @@ -59,6 +80,70 @@ 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. + */ + private 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 */ @@ -329,12 +414,20 @@ export class ResourceDecider { return CDK_CORE.TagType.AUTOSCALING_GROUP; case 'map': return CDK_CORE.TagType.MAP; + default: + assertNever(variant); } - - throw new Error(`Unknown variant: ${this.resource.tagInformation?.variant}`); } } +/** + * Utility function to ensure exhaustive checks for never type. + */ +function assertNever(x: never): never { + // eslint-disable-next-line @cdklabs/no-throw-default-error + throw new Error(`Unexpected object: ${x}`); +} + export interface PropsProperty { readonly propertySpec: PropertySpec; readonly validateRequiredInConstructor: boolean; @@ -372,3 +465,15 @@ 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/naming/conventions.ts b/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts index be7a7f7336ba7..e290eb1ae1cdf 100644 --- a/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts +++ b/tools/@aws-cdk/spec2cdk/lib/naming/conventions.ts @@ -47,6 +47,10 @@ export function structNameFromTypeDefinition(def: TypeDefinition) { return `${def.name}Property`; } +export function camelcasedResourceName(res: Resource, suffix?: string) { + return `${camelcase(res.name)}${suffix ?? ''}`; +} + export function classNameFromResource(res: Resource, suffix?: string) { return `Cfn${res.name}${suffix ?? ''}`; } @@ -55,6 +59,10 @@ export function propStructNameFromResource(res: Resource, suffix?: string) { return `${classNameFromResource(res, suffix)}Props`; } +export function interfaceNameFromResource(res: Resource, suffix?: string) { + return `I${classNameFromResource(res, suffix)}`; +} + export function cfnProducerNameFromType(struct: TypeDeclaration) { return `convert${qualifiedName(struct)}ToCloudFormation`; } @@ -87,6 +95,21 @@ export function attributePropertyName(attrName: string) { return propertyNameFromCloudFormation(`attr${attrName.replace(/[^a-zA-Z0-9]/g, '')}`); } +/** + * Make sure the resource name is included in the property + */ +export function referencePropertyName(propName: string, resourceName: string) { + // Some primaryIdentifier components are structurally deep, like AWS::QuickSight::RefreshSchedule's + // 'schedule/scheduleId', or AWS::S3::StorageLens's `configuration/id`. Only return the last part. + propName = propName.split('/').pop() ?? propName; + + if (['arn', 'id', 'name', 'url'].includes(propName.toLowerCase())) { + return `${camelcase(resourceName)}${propName.charAt(0).toUpperCase()}${propName.slice(1)}`; + } + + return camelcase(propName); +} + /** * 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__/resources.test.ts.snap b/tools/@aws-cdk/spec2cdk/test/__snapshots__/resources.test.ts.snap new file mode 100644 index 0000000000000..96e607ed78eb6 --- /dev/null +++ b/tools/@aws-cdk/spec2cdk/test/__snapshots__/resources.test.ts.snap @@ -0,0 +1,1577 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`resource interface when primaryIdentifier is a property 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * The identifier of the resource. + */ + public id?: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.id = props.id; + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": this.ref + }; + } + + protected get cfnProperties(): Record { + return { + "id": this.id + }; + } + + /** + * 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); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + /** + * The identifier of the resource. + * + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html#cfn-some-resource-id + */ + readonly id?: string; +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; +} + +/** + * 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("id", cdk.validateString)(properties.id)); + 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 { + "Id": cdk.stringToCloudFormation(properties.id) + }; +} + +// @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("id", "Id", (properties.Id != null ? cfn_parse.FromCloudFormation.getString(properties.Id) : undefined)); + ret.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource interface when primaryIdentifier is an attribute 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * The identifier of the resource + * + * @cloudformationAttribute Id + */ + public readonly attrId: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.attrId = cdk.Token.asString(this.getAtt("Id", cdk.ResolutionTypeHint.STRING)); + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": this.ref + }; + } + + 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", CfnResource.CFN_RESOURCE_TYPE_NAME); + inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); + } + + protected renderProperties(props: Record): Record { + return convertCfnResourcePropsToCloudFormation(props); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; +} + +/** + * 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))); + } + 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 {}; +} + +// @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.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource interface with "Arn" 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 Something. + * + * @stability experimental + */ +export interface ISomethingRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Something + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-something.html + */ +export class CfnSomething extends cdk.CfnResource implements cdk.IInspectable, ISomethingRef { + /** + * The CloudFormation resource type name for this resource class. + */ + public static readonly CFN_RESOURCE_TYPE_NAME: string = "AWS::Some::Something"; + + /** + * Build a CfnSomething 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): CfnSomething { + resourceAttributes = resourceAttributes || {}; + const resourceProperties = options.parser.parseValue(resourceAttributes.Properties); + const propsResult = CfnSomethingPropsFromCloudFormation(resourceProperties); + if (cdk.isResolvableObject(propsResult.value)) { + throw new cdk_errors.ValidationError("Unexpected IResolvable", scope); + } + const ret = new CfnSomething(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; + } + + /** + * The identifier of the resource + * + * @cloudformationAttribute Id + */ + public readonly attrId: string; + + /** + * The arn for something + * + * @cloudformationAttribute SomethingArn + */ + public readonly attrSomethingArn: 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: CfnSomethingProps = {}) { + super(scope, id, { + "type": CfnSomething.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.attrId = cdk.Token.asString(this.getAtt("Id", cdk.ResolutionTypeHint.STRING)); + this.attrSomethingArn = cdk.Token.asString(this.getAtt("SomethingArn", cdk.ResolutionTypeHint.STRING)); + } + + /** + * A reference to a Something resource. + */ + public get somethingRef(): SomethingReference { + return { + "somethingId": this.ref, + "somethingArn": this.attrSomethingArn + }; + } + + 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", CfnSomething.CFN_RESOURCE_TYPE_NAME); + inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); + } + + protected renderProperties(props: Record): Record { + return convertCfnSomethingPropsToCloudFormation(props); + } +} + +/** + * Properties for defining a \`CfnSomething\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-something.html + */ +export interface CfnSomethingProps { + +} + +/** + * A reference to a Something resource. + * + * @struct + * @stability external + */ +export interface SomethingReference { + /** + * The Id of the Something resource. + */ + readonly somethingId: string; + + /** + * The ARN of the Something resource. + */ + readonly somethingArn: string; +} + +/** + * Determine whether the given properties match those of a \`CfnSomethingProps\` + * + * @param properties - the TypeScript properties of a \`CfnSomethingProps\` + * + * @returns the result of the validation. + */ +// @ts-ignore TS6133 +function CfnSomethingPropsValidator(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 \\"CfnSomethingProps\\""); +} + +// @ts-ignore TS6133 +function convertCfnSomethingPropsToCloudFormation(properties: any): any { + if (!cdk.canInspect(properties)) return properties; + CfnSomethingPropsValidator(properties).assertSuccess(); + return {}; +} + +// @ts-ignore TS6133 +function CfnSomethingPropsFromCloudFormation(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; +}" +`; + +exports[`resource interface with "Arn" 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * The arn for the resource + * + * @cloudformationAttribute Arn + */ + public readonly attrArn: string; + + /** + * The identifier of the resource + * + * @cloudformationAttribute Id + */ + public readonly attrId: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.attrArn = cdk.Token.asString(this.getAtt("Arn", cdk.ResolutionTypeHint.STRING)); + this.attrId = cdk.Token.asString(this.getAtt("Id", cdk.ResolutionTypeHint.STRING)); + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": this.ref, + "resourceArn": this.attrArn + }; + } + + 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", CfnResource.CFN_RESOURCE_TYPE_NAME); + inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); + } + + protected renderProperties(props: Record): Record { + return convertCfnResourcePropsToCloudFormation(props); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; + + /** + * The ARN of the Resource resource. + */ + readonly resourceArn: string; +} + +/** + * 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))); + } + 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 {}; +} + +// @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.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource interface with Arn as a property and not a primaryIdentifier 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * The identifier of the resource + * + * @cloudformationAttribute Id + */ + public readonly attrId: string; + + /** + * The arn for the resource. + */ + public arn?: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.attrId = cdk.Token.asString(this.getAtt("Id", cdk.ResolutionTypeHint.STRING)); + this.arn = props.arn; + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": this.ref + }; + } + + protected get cfnProperties(): Record { + return { + "arn": this.arn + }; + } + + /** + * 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); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + /** + * The arn for the resource. + * + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html#cfn-some-resource-arn + */ + readonly arn?: string; +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; +} + +/** + * 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("arn", cdk.validateString)(properties.arn)); + 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 { + "Arn": cdk.stringToCloudFormation(properties.arn) + }; +} + +// @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("arn", "Arn", (properties.Arn != null ? cfn_parse.FromCloudFormation.getString(properties.Arn) : undefined)); + ret.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource interface with Arn as primaryIdentifier 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * The arn of the resource + * + * @cloudformationAttribute Arn + */ + public readonly attrArn: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.attrArn = cdk.Token.asString(this.getAtt("Arn", cdk.ResolutionTypeHint.STRING)); + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceArn": this.ref + }; + } + + 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", CfnResource.CFN_RESOURCE_TYPE_NAME); + inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); + } + + protected renderProperties(props: Record): Record { + return convertCfnResourcePropsToCloudFormation(props); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Arn of the Resource resource. + */ + readonly resourceArn: string; +} + +/** + * 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))); + } + 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 {}; +} + +// @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.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource interface with multiple primaryIdentifiers 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * Another identifier of the resource + * + * @cloudformationAttribute Another + */ + public readonly attrAnother: string; + + /** + * The identifier of the resource + * + * @cloudformationAttribute Id + */ + public readonly attrId: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.attrAnother = cdk.Token.asString(this.getAtt("Another", cdk.ResolutionTypeHint.STRING)); + this.attrId = cdk.Token.asString(this.getAtt("Id", cdk.ResolutionTypeHint.STRING)); + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": cdk.Fn.select(0, cdk.Fn.split("|", this.ref)), + "another": cdk.Fn.select(1, cdk.Fn.split("|", this.ref)) + }; + } + + 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", CfnResource.CFN_RESOURCE_TYPE_NAME); + inspector.addAttribute("aws:cdk:cloudformation:props", this.cfnProperties); + } + + protected renderProperties(props: Record): Record { + return convertCfnResourcePropsToCloudFormation(props); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; + + /** + * The Another of the Resource resource. + */ + readonly another: string; +} + +/** + * 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))); + } + 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 {}; +} + +// @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.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource with multiple primaryIdentifiers as properties 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * Another identifier of the resource. + */ + public anotherId?: string; + + /** + * The identifier of the resource. + */ + public id?: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.anotherId = props.anotherId; + this.id = props.id; + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": cdk.Fn.select(0, cdk.Fn.split("|", this.ref)), + "anotherId": cdk.Fn.select(1, cdk.Fn.split("|", this.ref)) + }; + } + + protected get cfnProperties(): Record { + return { + "anotherId": this.anotherId, + "id": this.id + }; + } + + /** + * 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); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + /** + * Another identifier of the resource. + * + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html#cfn-some-resource-anotherid + */ + readonly anotherId?: string; + + /** + * The identifier of the resource. + * + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html#cfn-some-resource-id + */ + readonly id?: string; +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; + + /** + * The AnotherId of the Resource resource. + */ + readonly anotherId: string; +} + +/** + * 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("anotherId", cdk.validateString)(properties.anotherId)); + errors.collect(cdk.propertyValidator("id", cdk.validateString)(properties.id)); + 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 { + "AnotherId": cdk.stringToCloudFormation(properties.anotherId), + "Id": cdk.stringToCloudFormation(properties.id) + }; +} + +// @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("anotherId", "AnotherId", (properties.AnotherId != null ? cfn_parse.FromCloudFormation.getString(properties.AnotherId) : undefined)); + ret.addPropertyResult("id", "Id", (properties.Id != null ? cfn_parse.FromCloudFormation.getString(properties.Id) : undefined)); + ret.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; + +exports[`resource with optional primary identifier gets property from ref 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 Resource. + * + * @stability experimental + */ +export interface IResourceRef extends constructs.IConstruct { + +} + +/** + * @cloudformationResource AWS::Some::Resource + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-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::Some::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; + } + + /** + * The identifier of the resource. + */ + public id?: 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: CfnResourceProps = {}) { + super(scope, id, { + "type": CfnResource.CFN_RESOURCE_TYPE_NAME, + "properties": props + }); + + this.id = props.id; + } + + /** + * A reference to a Resource resource. + */ + public get resourceRef(): ResourceReference { + return { + "resourceId": this.ref + }; + } + + protected get cfnProperties(): Record { + return { + "id": this.id + }; + } + + /** + * 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); + } +} + +/** + * Properties for defining a \`CfnResource\` + * + * @struct + * @stability external + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html + */ +export interface CfnResourceProps { + /** + * The identifier of the resource. + * + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-some-resource.html#cfn-some-resource-id + */ + readonly id?: string; +} + +/** + * A reference to a Resource resource. + * + * @struct + * @stability external + */ +export interface ResourceReference { + /** + * The Id of the Resource resource. + */ + readonly resourceId: string; +} + +/** + * 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("id", cdk.validateString)(properties.id)); + 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 { + "Id": cdk.stringToCloudFormation(properties.id) + }; +} + +// @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("id", "Id", (properties.Id != null ? cfn_parse.FromCloudFormation.getString(properties.Id) : undefined)); + ret.addUnrecognizedPropertiesAsExtra(properties); + return ret; +}" +`; diff --git a/tools/@aws-cdk/spec2cdk/test/conventions.test.ts b/tools/@aws-cdk/spec2cdk/test/conventions.test.ts new file mode 100644 index 0000000000000..8ddb28d7a69c4 --- /dev/null +++ b/tools/@aws-cdk/spec2cdk/test/conventions.test.ts @@ -0,0 +1,13 @@ +import { camelcasedResourceName } from '../lib/naming'; + +test('test the ref-naminification of OIDCProvider', () => { + const name = camelcasedResourceName({ + name: 'OIDCProvider', + cloudFormationType: 'AWS::IAM::OIDCProvider', + attributes: {}, + properties: {}, + $id: '', + }); + + expect(name).toEqual('oidcProvider'); +}); diff --git a/tools/@aws-cdk/spec2cdk/test/resources.test.ts b/tools/@aws-cdk/spec2cdk/test/resources.test.ts new file mode 100644 index 0000000000000..ca0d00b771f75 --- /dev/null +++ b/tools/@aws-cdk/spec2cdk/test/resources.test.ts @@ -0,0 +1,273 @@ +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(async () => { + db = emptyDatabase(); + + service = db.allocate('service', { + name: 'aws-some', + shortName: 'some', + capitalized: 'Some', + cloudFormationNamespace: 'AWS::Some', + }); +}); + +test('resource interface when primaryIdentifier is a property', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id'], + attributes: {}, + properties: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource with optional primary identifier gets property from ref', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id'], + attributes: {}, + properties: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource with multiple primaryIdentifiers as properties', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id', 'AnotherId'], + attributes: {}, + properties: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + AnotherId: { + type: { type: 'string' }, + documentation: 'Another identifier of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource interface when primaryIdentifier is an attribute', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id'], + properties: {}, + attributes: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource interface with multiple primaryIdentifiers', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id', 'Another'], + properties: {}, + attributes: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + Another: { + type: { type: 'string' }, + documentation: 'Another identifier of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource interface with "Arn"', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id'], + properties: {}, + attributes: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + Arn: { + type: { type: 'string' }, + documentation: 'The arn for the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource interface with "Arn"', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Something', + primaryIdentifier: ['Id'], + properties: {}, + attributes: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + SomethingArn: { + type: { type: 'string' }, + documentation: 'The arn for something', + }, + }, + cloudFormationType: 'AWS::Some::Something', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Something').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource interface with Arn as a property and not a primaryIdentifier', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Id'], + properties: { + Arn: { + type: { type: 'string' }, + documentation: 'The arn for the resource', + }, + }, + attributes: { + Id: { + type: { type: 'string' }, + documentation: 'The identifier of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); + +test('resource interface with Arn as primaryIdentifier', () => { + // GIVEN + const resource = db.allocate('resource', { + name: 'Resource', + primaryIdentifier: ['Arn'], + properties: {}, + attributes: { + Arn: { + type: { type: 'string' }, + documentation: 'The arn of the resource', + }, + }, + cloudFormationType: 'AWS::Some::Resource', + }); + db.link('hasResource', service, resource); + + // THEN + const foundResource = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Resource').only(); + + const ast = AstBuilder.forResource(foundResource, { db }); + + const rendered = renderer.render(ast.module); + + expect(rendered).toMatchSnapshot(); +}); diff --git a/yarn.lock b/yarn.lock index 82f81c63e8047..63cdc5ecbe597 100644 --- a/yarn.lock +++ b/yarn.lock @@ -152,13 +152,6 @@ dependencies: "@cdklabs/tskb" "^0.0.3" -"@aws-cdk/service-spec-types@^0.0.155": - version "0.0.155" - resolved "https://registry.npmjs.org/@aws-cdk/service-spec-types/-/service-spec-types-0.0.155.tgz#a38ad6291700b38ef4f2e763555065811cae6c67" - integrity sha512-Z4kwxvQesTkbD33uZorUicIUlHlP8/fVunOa/1LGqwQw55b1gbKiZE+2o3tgm2YIOHMEsP/p1ZjqW71cFtOCyg== - dependencies: - "@cdklabs/tskb" "^0.0.3" - "@aws-crypto/crc32@5.2.0": version "5.2.0" resolved "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz#cfcc22570949c98c6689cfcbd2d693d36cdae2e1" From 52ebce843b9c00b08d31814a6fcc2e166481b89d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 3 Oct 2025 12:02:29 +0200 Subject: [PATCH 2/2] chore(release): 2.214.1 --- CHANGELOG.v2.alpha.md | 2 ++ CHANGELOG.v2.md | 7 +++++++ version.v2.json | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.v2.alpha.md b/CHANGELOG.v2.alpha.md index a4173d60cdee2..50e88ef112d8d 100644 --- a/CHANGELOG.v2.alpha.md +++ b/CHANGELOG.v2.alpha.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.214.1-alpha.0](https://github.com/aws/aws-cdk/compare/v2.214.0-alpha.0...v2.214.1-alpha.0) (2025-10-03) + ## [2.214.0-alpha.0](https://github.com/aws/aws-cdk/compare/v2.213.0-alpha.0...v2.214.0-alpha.0) (2025-09-02) diff --git a/CHANGELOG.v2.md b/CHANGELOG.v2.md index 40a022d5fbc58..7f33a7f9ce7ea 100644 --- a/CHANGELOG.v2.md +++ b/CHANGELOG.v2.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.214.1](https://github.com/aws/aws-cdk/compare/v2.214.0...v2.214.1) (2025-10-03) + + +### Features + +* introduce reference interfaces, but don't require them yet ([09fc592](https://github.com/aws/aws-cdk/commit/09fc592f8debaf0b66ad5bec57d677ab776c0d6f)) + ## [2.214.0](https://github.com/aws/aws-cdk/compare/v2.213.0...v2.214.0) (2025-09-02) diff --git a/version.v2.json b/version.v2.json index 3f795abaa314e..ca0811ce42690 100644 --- a/version.v2.json +++ b/version.v2.json @@ -1,4 +1,4 @@ { - "version": "2.214.0", - "alphaVersion": "2.214.0-alpha.0" + "version": "2.214.1", + "alphaVersion": "2.214.1-alpha.0" } \ No newline at end of file