Skip to content

feat(gen2-migration): support kinesis analytics migration#14320

Merged
sai-ray merged 15 commits intogen2-migrationfrom
conroy/boilerplate
Jan 5, 2026
Merged

feat(gen2-migration): support kinesis analytics migration#14320
sai-ray merged 15 commits intogen2-migrationfrom
conroy/boilerplate

Conversation

@kaizencc
Copy link
Copy Markdown
Contributor

@kaizencc kaizencc commented Nov 3, 2025

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:

To suport other unsupported categories, cdk-from-cfn will 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:

  1. Type Safety & Interfaces:
  • 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>

  1. Code Generation:
  • 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

  1. Backend Integration:
  • synthesizer.ts : Updated to add analytics import ( import { analytics } from './analytics/resource') and analytics(backend) call in the generated backend.ts

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 env parameter must be renamed to amplify-env because env is a reserved property in CDK's StackProps that CDK uses for AWS account/region configuration. However, the generated condition code still references props.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: Added getAnalyticsStackParameters() and condition resolution in preTransmute()
  • migration-pipeline.ts: Added rootStackName and cfnClient to render options
  • command-handlers.ts: Pass CloudFormationClient and rootStackName to pipeline

Note: 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

  • PR description included
  • yarn test passes
  • Tests are changed or added
  • Relevant documentation is changed or added (and PR referenced)
  • New AWS SDK calls or CloudFormation actions have been added to relevant test and service IAM policies
  • Pull request labels are added

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@kaizencc kaizencc requested a review from a team as a code owner November 3, 2025 20:30
@kaizencc kaizencc changed the base branch from dev to gen2-migration November 3, 2025 20:30
@kaizencc kaizencc marked this pull request as draft November 3, 2025 20:31
kaizencc and others added 6 commits November 4, 2025 12:42
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.
@sai-ray
Copy link
Copy Markdown
Contributor

sai-ray commented Dec 22, 2025

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:

  1. Type Safety & Interfaces:
  • 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>

  1. Code Generation:
  • 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

  1. Backend Integration:
  • synthesizer.ts : Updated to add analytics import ( import { analytics } from './analytics/resource') and analytics(backend) call in the generated backend.ts

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:
The following limitations still exist and must be addressed in future work:

To suport other unsupported categories, cdk-from-cfn will need to support custom resources.

@sai-ray
Copy link
Copy Markdown
Contributor

sai-ray commented Dec 23, 2025

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 env parameter must be renamed to amplify-env because env is a reserved property in CDK's StackProps that CDK uses for AWS account/region configuration. However, the generated condition code still references props.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: Added getAnalyticsStackParameters() and condition resolution in preTransmute()
  • migration-pipeline.ts: Added rootStackName and cfnClient to render options
  • command-handlers.ts: Pass CloudFormationClient and rootStackName to pipeline

@sai-ray sai-ray marked this pull request as ready for review December 23, 2025 20:01
@sai-ray
Copy link
Copy Markdown
Contributor

sai-ray commented Dec 23, 2025

  • To fully support analytics codegen, cdk-from-cfn needs to generate nested stacks. Currently it generates code that extends cdk.Stack with scope: cdk.App, making it a standalone top-level stack. Gen2 requires analytics to be nested under the Amplify backend root stack, which would need cdk.NestedStack with scope: Construct.

  • To suport other unsupported categories, cdk-from-cfn will need to support custom resources.

Copy link
Copy Markdown
Contributor

@dgandhi62 dgandhi62 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly clarifying questions

@iliapolo iliapolo changed the title feat: support kinesis analytics migration feat(gen2-migration): support kinesis analytics migration Dec 30, 2025
@sai-ray
Copy link
Copy Markdown
Contributor

sai-ray commented Dec 31, 2025

Sample Gen1 CFN Template Input for reference:

kinesis-cloudformation-template.json:

{
  "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:

{projectName}Kinesis-stack.ts

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(),
    });
  }
}

@sai-ray sai-ray requested a review from dgandhi62 December 31, 2025 21:08
@sai-ray sai-ray enabled auto-merge December 31, 2025 21:08
@dgandhi62 dgandhi62 self-assigned this Jan 2, 2026
@dgandhi62
Copy link
Copy Markdown
Contributor

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

@sai-ray sai-ray merged commit 802d81b into gen2-migration Jan 5, 2026
4 checks passed
@sai-ray sai-ray deleted the conroy/boilerplate branch January 5, 2026 22:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants