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
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)