Search code examples
amazon-web-servicesaws-cdkinfrastructure-as-code

How can I identify when the aws cdk app is running in the deployment state?


I have the following problem:

I have a stack created and after the resource has been created (specifically a rabbitmq broker with Amazon MQ) I want to configure some stuff that cannot be done with cdk and has to be done via an REST api call.

The thing is I just want to do the rest api call only in the deployment state once the broker is up and running and the rabbitmq_url is resolved.

I have something like the following, but is not working as expected:

# Doc Ref: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amazonmq-broker.html
rabbitmq_broker = amazonmq. \
    CfnBroker(self, "RabbitMQBroker",
              broker_name=f"{os.getenv('ENVIRONMENT_NAME')}-rabbitmq",
              engine_type="RABBITMQ",
              engine_version="3.8.17",
              deployment_mode="SINGLE_INSTANCE",
              host_instance_type="mq.t3.micro",
              subnet_ids=[[public_subnet.subnet_id for public_subnet in vpc.public_subnets][0]],
              logs=amazonmq.CfnBroker.LogListProperty(general=True),
              # This means we can access rabbitmq from internet without going into the VPC
              publicly_accessible=True,
              users=[amazonmq.CfnBroker.UserProperty(username=os.getenv('RABBITMQ_USER'),
                                                     password=os.getenv('RABBITMQ_PASSWORD'))],
              auto_minor_version_upgrade=True)

rabbitmq_url = f"{rabbitmq_broker.ref}.mq.{os.getenv('CDK_DEFAULT_REGION')}.amazonaws.com"

...

# Only executed when the token is resolved in the deploy stage
if not core.Token.is_unresolved(rabbitmq_broker.ref):
# Create virtual host 'kmhbackend' via RabbitMQ Management HTTP API
    response = requests.post(f"https://{rabbitmq_url}:15671/api/vhosts/kmhbackend",
                          auth=(os.getenv('RABBITMQ_USER'), os.getenv('RABBITMQ_PASSWORD')),
                          headers={'content-type': 'application/json'})

    if response.status_code != requests.codes.get('created'):
        Log.error("Warning: The virtual host has not been created")
        print("Warning: The virtual host has not been created")

How can I identify when cdk app is running in the deployment state?

I woudn't like to have to use a context variable only to identify this.

Thanks in advance


Solution

  • So after reading the documentation in deep, the solution is to create a custom resource in a new nested stack and add a dependency between the 2 stacks, so the custom resource will wait for the initial resource to be completely deployed.

    So the solution is like this:

    class MessageBrokerConfigStack(core.NestedStack):
        """
        This stack will take of configure rabbitmq once it's created by API calls
    
        * the RabbitMQ Management HTTP API at 'rabbitmq-web-console:15671' ,
        docs of API can be found in rabbitmq_url/api/index.html
    
        Warning: In the docs the port used for the RabbitMQ Management HTTP API is 15672,
        but with AWS the port to be used is 15671
        """
    
        def __init__(self, scope: core.Construct, id: str,
                     vpc: ec2.Vpc, rabbit_broker_url: str,
                     rabbitmq_user: str, rabbitmq_password: str,
                     **kwargs) -> None:
            super().__init__(scope, id, **kwargs)
        
            ...
            onEvent = lambda_. \
            Function(self, "CR_RabbitMQ_configurator_handler",
                     function_name="cr_cdk_rabbitmq_configurator_handler",
                     description="Function that will handle all the configuration for rabbitmq broker created by cdk",
                     runtime=lambda_.Runtime.PYTHON_3_7,
                     layers=[layer],
                     handler="config_rabbitmq.lambda_handler",
                     code=lambda_.Code.from_asset('aws_lambda/lambdas')
                     )
    
        config_rabbitmq_provider = cr.Provider(self, 'CR_RabbitMQ_configurator_provider',
                                               vpc=vpc,
                                               vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE),
                                               on_event_handler=onEvent)
    
        # Will create a custom resource in cloudformation, this custom resource is compose by a lambda that
        # will contain a function and this function will handle the different events
        # ref https://docs.aws.amazon.com/cdk/latest/guide/apps.html#lifecycle
        # ref https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_custom
        config_rabbitmq = core.CustomResource(self, 'CR_RabbitMQ_configurator',
                                              service_token=config_rabbitmq_provider.service_token,
                                              properties={
                                                  "rabbitmq_url": rabbit_broker_url,
                                                  "rabbit_user": rabbitmq_user,
                                                  "rabbit_password": rabbitmq_password
                                              })
    

    Then the lambda code will listen for the events and execute the desired actions.

    import requests
    
    
    def lambda_handler(event, context):
        """
        This method is the native input for AWS Lambda
        """
    
        print("lambda rabbitmq received")
        print(event)
        print(context)
    
        props = event['ResourceProperties']
        rabbitmq_url = props['rabbitmq_url']
        rabbit_user = props['rabbit_user']
        rabbit_password = props['rabbit_password']
    
        print(rabbitmq_url)
        print(rabbit_user)
        print(rabbit_password)
    
        if event["RequestType"] == "Create":
            # ref https://docs.aws.amazon.com/cdk/latest/guide/apps.html#lifecycle
            # ref https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_custom
            # Only executed when the token is resolved in the deploy stage
            # Create virtual host 'kmhbackend' via RabbitMQ Management HTTP API
            response = requests.put(f"https://{rabbitmq_url}:15671/api/vhosts/new_virtual_host",
                                    auth=(rabbit_user, rabbit_password),
                                    headers={'content-type': 'application/json'})
    
            print("response from rabbitmq:")
            print(response)
    
            if response.status_code != requests.codes.get('created'):
                print("Warning: The virtual host has not been created")
        
    

    Then on the parent stack we just need to do

    resource = Resource(....)
    
    configResource = ConfigResouce(...)
    
    configResource.add_dependency(resource)