Skip to content

Commit

Permalink
l2 imp of AWS::CloudFormation::ResourceVersion
Browse files Browse the repository at this point in the history
- CF type https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-resourceversion.html
- creates log group + log access role if not provided
- param documentation taken from cloudformation docs
  • Loading branch information
695K0C9K1ZZmo6R committed Jan 21, 2025
1 parent a928748 commit d185888
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/aws-cdk-lib/aws-cloudformation/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './cloud-formation-capabilities';
export * from './custom-resource';
export * from './nested-stack';
export * from './resource-version';

// AWS::CloudFormation CloudFormation Resources:
export * from './cloudformation.generated';
110 changes: 110 additions & 0 deletions packages/aws-cdk-lib/aws-cloudformation/lib/resource-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Construct } from 'constructs';
import { CfnResourceVersion } from './cloudformation.generated';
import { IRole, Role, CompositePrincipal, ServicePrincipal, PolicyDocument, PolicyStatement, Effect } from '../../aws-iam';
import { ILogGroup, LogGroup, RetentionDays } from '../../aws-logs';
import { Asset } from '../../aws-s3-assets';
/**
* logging configuration for handler
*/
export interface ILogConfiguration {
/**
* The ARN of the role that CloudFormation should assume when sending log entries to CloudWatch logs.
*/
readonly logRole?: IRole;
/**
* The Amazon CloudWatch logs group to which CloudFormation sends error logging information when invoking the type's handlers.
*/
readonly logGroup?: ILogGroup;
}

/**
* properties for ResourceVersion construct
*/
export interface ResourceVersionProps {
/**
* The name of the resource being registered.
* We recommend that resource names adhere to the following pattern: company_or_organization::service::type.
*/
readonly typeName: string;
/**
* Logging configuration information for a resource.
* @default if not provided, it will be auto generated
*/
readonly logging?: ILogConfiguration;
/**
* Contains the resource project package that contains the necessary files for the resource you want to register.
*/
readonly handler: Asset;
/**
* IAM role for CloudFormation to assume when invoking the resource. If your resource calls AWS APIs in any of its handlers, you must create an IAM execution role that includes the necessary permissions to call those AWS APIs,
* and provision that execution role in your account. When CloudFormation needs to invoke the resource type handler, CloudFormation assumes this execution role to create a temporary session token,
* which it then passes to the resource type handler, thereby supplying your resource type with the appropriate credentials.
* @default no role will be passed to the resourceVersion
*/
readonly executionRole?: IRole;
}

/**
* L2 construct for https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-resourceversion.html
*/
export class ResourceVersion extends Construct {

/**
* resourceVersion resource created by this construct
*/
public readonly resourceVersion: CfnResourceVersion;
/**
* logRole referenced by resourceVersion
*/
public readonly logRole: IRole;
/**
* logGroup referenced by resourceVersion
*/
public readonly logGroup: ILogGroup;

constructor(scope: Construct, id: string, props: ResourceVersionProps) {
super(scope, id);

// default perms from https://github.com/aws-cloudformation/cloudformation-cli/blob/a73666d6b38099549c55f674748c095e0ef628ee/src/rpdk/core/data/managed-upload-infrastructure.yaml#L129
this.logRole =
props.logging?.logRole ??
new Role(this, 'LogRole', {
assumedBy: new CompositePrincipal(
new ServicePrincipal('hooks.cloudformation.amazonaws.com'),
new ServicePrincipal('resources.cloudformation.amazonaws.com'),
),
description: 'Role for CloudFormation Resource Version logging',
inlinePolicies: {
LogAndMetricsDeliveryRolePolicy:
new PolicyDocument({
statements: [
new PolicyStatement({
effect: Effect.ALLOW,
actions: ['cloudwatch:PutMetricData', 'cloudwatch:ListMetrics', 'logs:DescribeLogGroups'],
resources: ['*'],
}),
],
}),

},
});

this.logGroup =
props.logging?.logGroup ??
new LogGroup(this, 'LogGroup', {
logGroupName: `/aws/cloudformation/${props.typeName.split('::').join('-')}`,
retention: RetentionDays.TEN_YEARS,
});
this.logGroup.grantWrite(this.logRole);

this.resourceVersion = new CfnResourceVersion(this, 'CfnResourceVersion', {
typeName: props.typeName,
schemaHandlerPackage: props.handler.s3ObjectUrl,
loggingConfig: {
logRoleArn: this.logRole.roleArn,
logGroupName: this.logGroup.logGroupName,
},
executionRoleArn: props.executionRole?.roleArn,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { join } from 'path';
import { Template } from '../../assertions';
import { Role, ServicePrincipal } from '../../aws-iam';
import { Asset } from '../../aws-s3-assets';
import { App, Stack } from '../../core';
import { ResourceVersion } from '../lib';

test('it creates the resource-version and its related resources', () => {
const app = new App();
const stack = new Stack(app, 'Stack', {
env: { account: '1234', region: 'testregion' },
});
const id = 'unittest';
const asset = new Asset(stack, 'SampleAsset', {
path: join(__dirname, 'asset-directory-fixture'),
});
const executionRole = new Role(stack, 'role', {
assumedBy: new ServicePrincipal('unittest.amazonaws.com'),
});
const typeName = 'UNIT::TEST::TEST';
const rv = new ResourceVersion(stack, id, {
typeName,
handler: asset,
executionRole,
});

// it should create the l1 resource
Template.fromStack(stack).hasResourceProperties('AWS::CloudFormation::ResourceVersion', {
TypeName: typeName,
SchemaHandlerPackage: asset.s3ObjectUrl,
LoggingConfig: {
LogGroupName: {
Ref: 'unittestLogGroup321F9C37',
},
LogRoleArn: {
'Fn::GetAtt': ['unittestLogRoleEC6FE6AD', 'Arn'],
},
},
ExecutionRoleArn: {
'Fn::GetAtt': ['roleC7B7E775', 'Arn'],
},
});

// it should create the log group (if not provided)
Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', {
LogGroupName: `/aws/cloudformation/${typeName.split('::').join('-')}`,
});

// it should create the log delivery role (if not provided)
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
AssumeRolePolicyDocument: {
Statement: [
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'hooks.cloudformation.amazonaws.com',
},
},
{
Action: 'sts:AssumeRole',
Effect: 'Allow',
Principal: {
Service: 'resources.cloudformation.amazonaws.com',
},
},
],
Version: '2012-10-17',
},
});
});

0 comments on commit d185888

Please sign in to comment.