feat(gen2-migration): support kinesis analytics migration#14320
feat(gen2-migration): support kinesis analytics migration#14320sai-ray merged 15 commits intogen2-migrationfrom
Conversation
Add complete codegen pipeline for Kinesis analytics migration including resource.ts generation and backend.ts integration. This completes the first phase of analytics migration by generating proper TypeScript code that integrates with Amplify Gen2's backend composition pattern.
|
This update builds on the initial PR by implementing the analytics (Kinesis) codegen pipeline for Gen1 to Gen2 migration. Previously, the migration generated the CDK stack file via cdk-from-cfn and now also generates the resource.ts wrapper and integrates it into backend.ts. Changes:
The generated resource.ts creates a NestedStack under the Amplify backend root stack using backend.createStack('analytics'), passing auth role names from backend.auth.resources. All parameter values (kinesisStreamName, authPolicyName, etc.) are derived from the resource name or runtime backend references. Remaining Limitations:
To suport other unsupported categories, |
|
This update resolves conditions in the CFN template in a preprocessing step prior to cdk-from-cfn. cdk-from-cfn generates local variables for CFN conditions (ex., The Additionally, Gen2 doesn't use CloudFormation parameters for environment. The generated condition variables reference parameters that don't exist in the Gen2 props interface, causing TypeScript type errors. To address this, we preprocess Gen1 templates to resolve CFN conditions before passing to cdk-from-cfn, following the same pattern used for supported categories. We fetch deployed stack parameters via CloudFormation DescribeStacks API, use the existing CFNConditionResolver to evaluate conditions with actual values, and remove the Conditions block after resolution. cdk-from-cfn never sees conditions, so no local variables are generated. The output contains only concrete resources matching the deployed state. Changes:
|
|
dgandhi62
left a comment
There was a problem hiding this comment.
Mainly clarifying questions
|
Sample Gen1 CFN Template Input for reference:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "{\"createdOn\":\"Mac\",\"createdBy\":\"Amplify\",\"createdWith\":\"14.2.3\",\"stackType\":\"analytics-Kinesis\",\"metadata\":{\"whyContinueWithGen1\":\"Prefer not to answer\"}}",
"Parameters": {
"env": {
"Type": "String"
},
"kinesisStreamName": {
"Type": "String"
},
"kinesisStreamShardCount": {
"Type": "Number",
"Default": 1
},
"authPolicyName": {
"Type": "String"
},
"unauthPolicyName": {
"Type": "String"
},
"authRoleName": {
"Type": "String"
},
"unauthRoleName": {
"Type": "String"
}
},
"Conditions": {
"ShouldNotCreateEnvResources": {
"Fn::Equals": [
{
"Ref": "env"
},
"NONE"
]
}
},
"Resources": {
"KinesisStream": {
"Type": "AWS::Kinesis::Stream",
"Properties": {
"Name": {
"Fn::Join": [
"-",
[
{
"Ref": "kinesisStreamName"
},
{
"Ref": "env"
}
]
]
},
"ShardCount": {
"Ref": "kinesisStreamShardCount"
}
}
},
"CognitoUnauthPolicy": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": {
"Ref": "unauthPolicyName"
},
"Roles": [
{
"Ref": "unauthRoleName"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kinesis:PutRecord",
"kinesis:PutRecords"
],
"Resource": {
"Fn::GetAtt": [
"KinesisStream",
"Arn"
]
}
}
]
}
}
},
"CognitoAuthPolicy": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyName": {
"Ref": "authPolicyName"
},
"Roles": [
{
"Ref": "authRoleName"
}
],
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kinesis:PutRecord",
"kinesis:PutRecords"
],
"Resource": {
"Fn::GetAtt": [
"KinesisStream",
"Arn"
]
}
}
]
}
}
}
},
"Outputs": {
"kinesisStreamArn": {
"Value": {
"Fn::GetAtt": [
"KinesisStream",
"Arn"
]
}
},
"kinesisStreamId": {
"Value": {
"Ref": "KinesisStream"
}
},
"kinesisStreamShardCount": {
"Value": {
"Ref": "kinesisStreamShardCount"
}
}
}
}The cdk-from-cfn output after preprocessing would look like:
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as kinesis from 'aws-cdk-lib/aws-kinesis';
import { Construct } from 'constructs';
export interface analyticsanalyticsappKinesisProps {
/**
*/
readonly amplifyEnv: string;
/**
*/
readonly kinesisStreamName: string;
/**
* @default 1
*/
readonly kinesisStreamShardCount?: number;
/**
*/
readonly authPolicyName: string;
/**
*/
readonly unauthPolicyName: string;
/**
*/
readonly authRoleName: string;
/**
*/
readonly unauthRoleName: string;
}
/**
* {"createdOn":"Mac","createdBy":"Amplify","createdWith":"14.2.3","stackType":"analytics-Kinesis","metadata":{"whyContinueWithGen1":"Prefer not to answer"}}
* */
export class analyticsanalyticsappKinesis extends Construct {
public readonly kinesisStreamArn;
public readonly kinesisStreamId;
public readonly kinesisStreamShardCount;
public constructor(scope: Construct, id: string, props: analyticsanalyticsappKinesisProps) {
super(scope, id);
// Applying default props
props = {
...props,
kinesisStreamShardCount: props.kinesisStreamShardCount ?? 1,
};
// Resources
const kinesisStream = new kinesis.CfnStream(this, 'KinesisStream', {
name: [
props.kinesisStreamName!,
props.amplifyEnv!,
].join('-'),
shardCount: props.kinesisStreamShardCount!,
});
const cognitoAuthPolicy = new iam.CfnPolicy(this, 'CognitoAuthPolicy', {
policyName: props.authPolicyName!,
roles: [
props.authRoleName!,
],
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
'kinesis:PutRecord',
'kinesis:PutRecords',
],
Resource: kinesisStream.attrArn,
},
],
},
});
const cognitoUnauthPolicy = new iam.CfnPolicy(this, 'CognitoUnauthPolicy', {
policyName: props.unauthPolicyName!,
roles: [
props.unauthRoleName!,
],
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
'kinesis:PutRecord',
'kinesis:PutRecords',
],
Resource: kinesisStream.attrArn,
},
],
},
});
// Outputs
this.kinesisStreamArn = kinesisStream.attrArn;
new cdk.CfnOutput(this, 'CfnOutputkinesisStreamArn', {
key: 'kinesisStreamArn',
value: this.kinesisStreamArn!.toString(),
});
this.kinesisStreamId = kinesisStream.ref;
new cdk.CfnOutput(this, 'CfnOutputkinesisStreamId', {
key: 'kinesisStreamId',
value: this.kinesisStreamId!.toString(),
});
this.kinesisStreamShardCount = props.kinesisStreamShardCount!;
new cdk.CfnOutput(this, 'CfnOutputkinesisStreamShardCount', {
key: 'kinesisStreamShardCount',
value: this.kinesisStreamShardCount!.toString(),
});
}
} |
|
Look good! Can we update the pr message to add all the updated features / support and solve the merge conflicts? I'll approve it after |
This PR supports kinesis analytics migration. Kinesis is a category that exists in Amplify Gen1 but is not supported in Amplify Gen2.
The general idea is to grab the CloudFormation nested stack that includes the Kinesis Stream definition, and send that to a new dependency,
cdk-from-cfn, which will spit out CDK L1 code that can live in conjunction with Amplify Gen2.The following limitations still exist and must be solved before this PR fully supports Kinesis Analytics:
cdk-from-cfninbackend.ts. We will need to pass in the parameters.cdk-from-cfn. We do this for the supported categories https://github.com/aws-amplify/amplify-cli/blob/migrations/packages/amplify-migration-template-gen/src/resolvers/cfn-condition-resolver.ts.To suport other unsupported categories,
cdk-from-cfnwill need to support custom resources.Update 1:
This update builds on the initial PR by implementing the analytics (Kinesis) codegen pipeline for Gen1 to Gen2 migration. Previously, the migration generated the CDK stack file via cdk-from-cfn and now also generates the resource.ts wrapper and integrates it into backend.ts.
Changes:
cdk-from-cfn.ts : Added KinesisAnalyticsDefinition and AnalyticsCodegenResult interfaces, removing all any types for proper type safety
app_analytics_definition_fetcher.ts : Fixed return type to use typed Record<string, KinesisAnalyticsDefinition>
generators/analytics/index.ts : Created new generator with renderAnalytics() function that generates resource.ts with proper Backend type and nested stack instantiation using TypeScript AST
migration-pipeline.ts : Updated to use TypescriptNodeArrayRenderer for generating analytics/resource.ts alongside the stack file
The generated resource.ts creates a NestedStack under the Amplify backend root stack using backend.createStack('analytics'), passing auth role names from backend.auth.resources. All parameter values (kinesisStreamName, authPolicyName, etc.) are derived from the resource name or runtime backend references.
Update 2:
This update resolves conditions in the CFN template in a preprocessing step prior to cdk-from-cfn. cdk-from-cfn generates local variables for CFN conditions (ex.,
const shouldNotCreateEnvResources = props.env! === 'NONE'). This causes issues when migrating analytics resources from Gen1 to Gen2.The
envparameter must be renamed toamplify-envbecauseenvis a reserved property in CDK's StackProps that CDK uses for AWS account/region configuration. However, the generated condition code still referencesprops.env.Additionally, Gen2 doesn't use CloudFormation parameters for environment. The generated condition variables reference parameters that don't exist in the Gen2 props interface, causing TypeScript type errors.
To address this, we preprocess Gen1 templates to resolve CFN conditions before passing to cdk-from-cfn, following the same pattern used for supported categories. We fetch deployed stack parameters via CloudFormation DescribeStacks API, use the existing CFNConditionResolver to evaluate conditions with actual values, and remove the Conditions block after resolution.
cdk-from-cfn never sees conditions, so no local variables are generated. The output contains only concrete resources matching the deployed state.
Changes:
cdk-from-cfn.ts: AddedgetAnalyticsStackParameters()and condition resolution inpreTransmute()migration-pipeline.ts: AddedrootStackNameandcfnClientto render optionscommand-handlers.ts: Pass CloudFormationClient and rootStackName to pipelineNote: Per discussion, Backend<any> is used because the alternatives are more complex without providing additional safety. Using a generic <T extends DefineBackendProps> causes TypeScript to lose type information about backend.auth.resources - it can't infer the specific authenticatedUserIamRole property and errors with "Property does not exist on type 'ReturnType<T[string]["getInstance"]>["resources"]'". Importing the backend instance directly (typeof backend) creates a circular dependency since backend.ts already imports this analytics module. While we could define a custom interface with the exact auth structure, that duplicates type information and doesn't use the Backend<T> type as requested. Since the actual backend type is validated at the call site in backend.ts, Backend<any> is the pragmatic solution. This could be further investigated.
Checklist
yarn testpassesBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.