amazon-web-servicesaws-cloudformationamazon-cognito

How to Avoid Deletion of Manually Created Cognito User Pool During CloudFormation Stack Deployment


I have an existing Cognito User Pool that I manually created in the AWS Console. I recently added it to my CloudFormation template to be referenced by other resources in my stack, but I don't want CloudFormation to modify or delete it. However, every time I deploy my template, I see the following changeset indicating that the Cognito User Pool is marked for deletion:

CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------
Operation                             LogicalResourceId                     ResourceType                          Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------
* Modify                              WebSocketConnectFunction              AWS::Lambda::Function                 False
* Modify                              WebSocketConnectIntegration           AWS::ApiGatewayV2::Integration        False
* Modify                              WebSocketDefaultFunction              AWS::Lambda::Function                 False
* Modify                              WebSocketDefaultIntegration           AWS::ApiGatewayV2::Integration        False
* Modify                              WebSocketDisconnectFunction           AWS::Lambda::Function                 False
* Modify                              WebSocketDisconnectIntegration        AWS::ApiGatewayV2::Integration        False
- Delete                              CognitoUserPool                       AWS::Cognito::UserPool                N/A

template.yaml:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Web Application Backend

Parameters:
  ExistingCognitoUserPoolId:
    Type: String
    Description: ID of the existing Cognito User Pool
    Default: ap-example-2_aKzfw0K4N

Resources:
  WebSocketApi:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: WebSocketAPI
      ProtocolType: WEBSOCKET
      RouteSelectionExpression: $request.body.action

  WebSocketConnectIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref WebSocketApi
      IntegrationType: AWS_PROXY
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${WebSocketConnectFunction.Arn}/invocations

  WebSocketDisconnectIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref WebSocketApi
      IntegrationType: AWS_PROXY
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${WebSocketDisconnectFunction.Arn}/invocations

  WebSocketDefaultIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref WebSocketApi
      IntegrationType: AWS_PROXY
      IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${WebSocketDefaultFunction.Arn}/invocations

  WebSocketConnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref WebSocketApi
      RouteKey: $connect
      AuthorizationType: NONE
      Target: !Sub integrations/${WebSocketConnectIntegration}

  WebSocketDisconnectRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref WebSocketApi
      RouteKey: $disconnect
      AuthorizationType: NONE
      Target: !Sub integrations/${WebSocketDisconnectIntegration}

  WebSocketDefaultRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref WebSocketApi
      RouteKey: $default
      AuthorizationType: NONE
      Target: !Sub integrations/${WebSocketDefaultIntegration}

  WebSocketConnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: server.wsConnectHandler
      Runtime: nodejs18.x
      CodeUri: .
      Tracing: Active

  OnConnectPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref WebSocketConnectFunction
      Principal: apigateway.amazonaws.com

  WebSocketDisconnectFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: server.wsDisconnectHandler
      Runtime: nodejs18.x
      CodeUri: .
      Tracing: Active

  OnDisconnectPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref WebSocketDisconnectFunction
      Principal: apigateway.amazonaws.com

  WebSocketDefaultFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: server.wsDefaultHandler
      Runtime: nodejs18.x
      CodeUri: .
      Tracing: Active
      Timeout: 900
      Policies:
        - Statement:
            Effect: Allow
            Action:
              - execute-api:ManageConnections
            Resource: 
              - !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${WebSocketApi}/Test/POST/@connections/*

  OnDefaultPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref WebSocketDefaultFunction
      Principal: apigateway.amazonaws.com

Outputs:
  WebSocketApiUrl:
    Value: !Sub wss://${WebSocketApi}.execute-api.${AWS::Region}.amazonaws.com/Stage
    Description: URL of the WebSocket API

  CognitoUserPoolId:
    Value: !Ref ExistingCognitoUserPoolId
    Description: ID of the existing Cognito User Pool

Solution

  • How did it initially end up under CloudFormation management if you created it through the console? The output suggests that it was managed by CloudFormation at some point and now is not present in the template anymore.

    If you don't want to modify or delete it through your template, why do you need it in there in the first place?

    I would either:

    or

    IMO it's always a good idea to have all you resources as IaC and no resources created through clicking in the console.

    See the documentation on importing existing resources into CloudFormation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import.html