Skip to content

AWS Cloud Formation - GSI not creating on existing table

0

I have a DynamoDB table and want to create a new GSI using CFN. But the CFN fails and throwing error message that the table already exists. Tried creating GSI using on existing table, it worked fine. Is it not possible to create GSI on an existing using CFN?

`AWSTemplateFormatVersion: "2010-09-09" # This is latest as of Oct 2023 Transform: [AWS::Serverless-2016-10-31] Description: Mira Actions Service

Metadata: cfn-lint: # Configure the template linter (sam validate --lint uses cfn-lint) config: ignore_checks: # W3002 is the warning: "This code may only work with package cli command as the property" # It shows up for AWS::Serverless::Application resources below and is fine to ignore - W3002 # W2001 is the warning: "UserPoolId not used". However we are using it in the included # api.yaml file, so we can ignore this warning. - W2001

Globals:

https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst

Doesn't seem like globals bubble down to nested stacks so defining them in each nested stack

Function: Timeout: 60

Parameters: MicroserviceClientId: Description: The Client ID for the Microservice App Client Type: String Environment: Description: The environment name (e.g., prod, dev, staging, gh ref, your user name). Type: String Default: dev UserPoolId: Description: The Cognito User Pool ID Type: String RootStackName: Description: The root stack name used for cross-stack references. Type: String ApiVersion: Type: String Description: The version of the API Default: v1 # NOTE This sets the API version which is reflected in the API URL. UtilityLayerArn: Description: The ARN of the Mira Utilities Layer Type: "AWS::SSM::Parameter::Value<String>" DataPlatformEventsStreamArn: Description: The ARN of the Data Platform Kinesis Events Stream Type: String ApplicationLogLevel: Description: Application logs are the logs that python function code generates. Type: String SystemLogLevel: Description: System logs are the logs that Lambda generates. Type: String ConfigBucketName: Description: The name of the S3 bucket to store the OpenTelemetry Collector configuration file. Type: String SignalPolicyTableArn: Description: The ARN of the signal policy table Type: String MiraNotificationsApi: Type: String Description: The Mira Notifications API Gateway endpoint URL MiraSignalPolicyTable: Description: The name of the signal policy table Type: String MiraAgentClientTable: Description: The DynamoDB table for storing Mira Client Agent Xref Data Type: String MiraAgentClientTableArn: Description: The ARN of the Mira Client Agent Xref Data table Type: String ActionsTableName: Description: The name of the actions table Type: String ActionsGSIName: Description: The name of GSI Type: String

Conditions: IsProductionEnvironment: !Equals [!Ref Environment, "Production"] IsTestingStagingProduction: !Or - !Equals [!Ref Environment, "Testing"] - !Equals [!Ref Environment, "Staging"] - !Equals [!Ref Environment, "Production"]

Mappings: OtelConfigServiceMap: Testing: "ServiceName": "actions-testing" Staging: "ServiceName": "actions-staging" Production: "ServiceName": "actions-production" Development: "ServiceName": "no-op"

Resources: MiraServiceActionProcessRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: "/" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: CognitoDescribePolicy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cognito-idp:DescribeUserPool - cognito-idp:DescribeUserPoolClient Resource: !Sub arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${UserPoolId}

    - PolicyName: DynamoDBMiraActionTablePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "dynamodb:DescribeStream"
              - "dynamodb:GetRecords"
              - "dynamodb:GetShardIterator"
              - "dynamodb:ListStreams"
              - "dynamodb:GetItem"
              - "dynamodb:Query"
            Resource: 
             - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${MiraActionsTable}
             - !Sub arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${MiraActionsTable}/stream/*  
             
    - PolicyName: LambdaCloudWatchPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
            Resource: "*"

    - PolicyName: LambdaInvokePolicies
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: lambda:InvokeFunction
            Resource:
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${MiraAWSActions.Outputs.MiraAwsActionProcessFunctionName}
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${MiraSatelliteActions.Outputs.MiraSatelliteActionsServerPowerOffFunctionName}
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${MiraAzureActions.Outputs.MiraAzureActionsDetachedIpFunctionName}
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${MiraAzureActions.Outputs.MiraAzureActionsServerFunctionName}
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${MiraAzureActions.Outputs.MiraAzureActionsTagFunctionName}
              - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${MiraGCPActions.Outputs.MiraGCPActionsTagFunctionName}
              
    - PolicyName: SQSSendMessagePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: sqs:SendMessage
            Resource: !GetAtt MiraActionsProcessorDeadLetterQueue.Arn

    - PolicyName: SSMReadPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: 
              - ssm:GetParameter
            Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${RootStackName}/internal/signals/ServiceSignalTableArn

    - PolicyName: SelfManagePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action: 
              - iam:PutRolePolicy
              - iam:ListRolePolicies
            Resource: "*"
                  

MiraActionsApi: Type: AWS::Serverless::Api # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-api.html Properties: MinimumCompressionSize: 1024 EndpointConfiguration: # Use REGIONAL for production and EDGE for testing and staging because of AWS Quotas per Account for EDGE Type: !If [IsTestingStagingProduction, "EDGE", "REGIONAL"] # Using the StageName as a versioning mechanism StageName: !Ref ApiVersion

  DefinitionBody:
    Fn::Transform:
      Name: AWS::Include
      Parameters:
        Location: ./api.yaml

MiraActionsLayer: Type: AWS::Serverless::LayerVersion # https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-layerversion.html Properties: Description: Shared code for the Actions service ContentUri: layers/ Metadata: BuildMethod: python3.11

Stores events handled by the EventProcessor in DynamoDB

MiraActionsTable: Type: AWS::DynamoDB::Table # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html Properties: TableName: !Ref ActionsTableName BillingMode: PAY_PER_REQUEST # Enable Point-in-time Recovery (Backup) PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true AttributeDefinitions: # These properties must be defined in order to use as sort keys in the primary and secondary key schemas - AttributeName: action_id AttributeType: S - AttributeName: tenant_id AttributeType: S - AttributeName: status AttributeType: S - AttributeName: provider AttributeType: S - AttributeName: action AttributeType: S - AttributeName: process_type AttributeType: S #used in GSI - AttributeName: policy_id AttributeType: S DeletionProtectionEnabled: !If [IsProductionEnvironment, "true", "false"] KeySchema: #https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-keyschema.html - AttributeName: tenant_id KeyType: HASH # Partition key - AttributeName: action_id KeyType: RANGE # Sort Key # As the table does not have a sort key on policy, we need the GSI. # This GSI is used only with Action Read API we included only those attributes specific to the API GlobalSecondaryIndexes: - IndexName: !Ref ActionGSIName KeySchema: - AttributeName: tenant_id # Partition Key KeyType: HASH - AttributeName: policy_id # Sort Key KeyType: RANGE Projection: ProjectionType: INCLUDE NonKeyAttributes: - action - created_at - resource_ids - status - policy_run_id - result - provider - event_timestamp # The following Local Secondary Indexes allow sorting on attributes other than the action_id. # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html LocalSecondaryIndexes: - IndexName: tenant_id-status-index KeySchema: - AttributeName: tenant_id KeyType: HASH - AttributeName: status KeyType: RANGE Projection: ProjectionType: ALL - IndexName: tenant_id-provider-index KeySchema: - AttributeName: tenant_id KeyType: HASH - AttributeName: provider KeyType: RANGE Projection: ProjectionType: ALL - IndexName: tenant_id-action-index KeySchema: - AttributeName: tenant_id KeyType: HASH - AttributeName: action KeyType: RANGE Projection: ProjectionType: ALL - IndexName: tenant_id-process_type-index KeySchema: - AttributeName: tenant_id KeyType: HASH - AttributeName: process_type KeyType: RANGE Projection: ProjectionType: ALL StreamSpecification: StreamViewType: NEW_IMAGE KinesisStreamSpecification: StreamArn: !Ref DataPlatformEventsStreamArn

MiraActionOptionsTable: Type: AWS::DynamoDB::Table # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html Properties: BillingMode: PAY_PER_REQUEST # Enable Point-in-time Recovery (Backup) PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true AttributeDefinitions: # These properties must be defined in order to use as sort keys in the primary and secondary key schemas - AttributeName: id AttributeType: S - AttributeName: resource AttributeType: S - AttributeName: action AttributeType: S - AttributeName: provider AttributeType: S DeletionProtectionEnabled: !If [IsProductionEnvironment, "true", "false"] KeySchema: #https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-dynamodb-table-keyschema.html - AttributeName: id KeyType: HASH # Partition key - AttributeName: resource KeyType: RANGE # Sort Key # The following Local Secondary Indexes allow sorting on attributes other than the action_id. # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html LocalSecondaryIndexes: - IndexName: sort-by-action KeySchema: - AttributeName: id KeyType: HASH - AttributeName: action KeyType: RANGE Projection: ProjectionType: ALL - IndexName: sort-by-provider KeySchema: - AttributeName: id KeyType: HASH - AttributeName: provider KeyType: RANGE Projection: ProjectionType: ALL

Lambda function to create a new action via POST /actions endpoint

MiraActionsCreate: Type: AWS::Serverless::Function Properties: CodeUri: actions_create/ Handler: app.handler Runtime: python3.11 Layers: - !Ref MiraActionsLayer - !Ref UtilityLayerArn - !Sub "arn:aws:lambda:${AWS::Region}:901920570463:layer:aws-otel-python-amd64-ver-1-21-0:1" Environment: Variables: MIRA_ACTIONS_TABLE: !Ref MiraActionsTable AWS_LAMBDA_EXEC_WRAPPER: /opt/otel-instrument OPENTELEMETRY_COLLECTOR_CONFIG_FILE: !Sub s3://${ConfigBucketName}.s3.${AWS::Region}.amazonaws.com/otel-collector-config.yaml HONEYCOMB_API_KEY: "{{resolve:secretsmanager:honeycomb_api_key:SecretString:HONEYCOMB_API_KEY}}" OTEL_SERVICE_NAME: !FindInMap [OtelConfigServiceMap, !Ref Environment, ServiceName] OTEL_PROPAGATORS: tracecontext OTEL_ENDPOINT: 'https://api.honeycomb.io:443'

  # Advanced logging controls for AWS Lambda
  LoggingConfig:
    LogFormat: JSON  # Application logs format, defaults to JSON (TEXT is optional)
    ApplicationLogLevel: !Ref ApplicationLogLevel # Application log level, defaults to INFO
    SystemLogLevel: !Ref SystemLogLevel # System log level, defaults to INFO

Connectors:
  DatabaseConnector:
    Properties:
      Destination:
        Id: MiraActionsTable
      Permi`
1 Answer
0

Creating a Global Secondary Index (GSI) on an existing DynamoDB table using CloudFormation is possible, but there are some important considerations:

  1. If you're trying to modify an existing table that was not originally created by CloudFormation, you'll need to import the table into your CloudFormation stack first. You can't directly modify resources that aren't managed by CloudFormation.

  2. When adding a GSI to an existing table using CloudFormation, you need to update the table's resource definition in your template. This includes:

    • Adding the new GSI to the GlobalSecondaryIndexes property
    • Ensuring that any new AttributeDefinitions required for the GSI are included
  3. The error message you're seeing about the table already existing suggests that you might be trying to create a new table resource instead of updating an existing one. Make sure you're using the correct resource name that matches your existing table.

  4. If you've imported the table into CloudFormation and are still having issues, you may need to use the UpdatePolicy attribute to control how CloudFormation handles updates to the table.

  5. When adding a GSI, be aware that it can take some time for the index to be created, especially for large tables. CloudFormation will wait for the operation to complete before considering the update successful.

  6. Ensure that your IAM permissions allow for updating DynamoDB tables and creating GSIs.

To resolve your specific issue:

  1. Verify that you're referencing the existing table correctly in your CloudFormation template.
  2. Make sure you're updating the existing table resource rather than trying to create a new one.
  3. Include all necessary AttributeDefinitions for your new GSI.
  4. Add the new GSI to the GlobalSecondaryIndexes property of your table resource.

If you continue to have issues, you may want to consider using the AWS CLI or SDK to add the GSI directly to the table outside of CloudFormation, and then import the updated table configuration back into your CloudFormation stack.

Sources
AWS::DynamoDB::Table - AWS CloudFormation
AWS::DynamoDB::GlobalTable - AWS CloudFormation

answered 2 years ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.