AWS MSK Cloud Formation Tags problems - amazon-web-services

When creating AWS::MSK::Cluster with Cloud Formation I am not able to set Tags in the usual way:
Tags:
- Key: Name
Value: !Ref Identifier
Because of this error:
Property validation failure: [Value of property {/Tags} does not match type {Map}]
As of the time of writing, the documentation states that, instead of the usual Type: List of Tag, I should use: Type: Json.
Also the same documentation states that:
You can specify tags in JSON or in YAML, depending on which format you use for your template

After further investigation (and AWS support help), the working (only on creation) example looks like this:
Tags:
Name: !Ref Identifier
Additionally, tags cannot be modified (the docs actually state that tags change require replacement), when tried a slightly confusing error shows up:
CloudFormation cannot update a stack when a custom-named resource requires replacing. Rename kafka-eu-west-1-dev and update the stack again.

Related

Referencing serverless stack name after pseudo parameters plugin deprecation

I'm wondering what is the correct way to reference AWS Cloudformation pseudo parameters in a serverless.yml now that pseudo parameter plugin has been deprecated.
All pseudo parameters are not available with the dollar sign syntax (e.g. ${aws:stackName} is not) in a similar manner as ${aws:region} is, for example. The serverless documentation on pseudo parameters is very short and I am not sure I fully understand it. I have tried to use Ref: "AWS::StackName", but when I try to generate an output
Fn::Sub:
- "${Stack}-someOutputResourceName"
- Stack:
Ref: "AWS::StackName"
, I get an error with [...]/Fn::Sub/1/Stack] 'null' values are not allowed in templates.
The pseudo-plugin page claims that
All functionalities as provided by this plugin are now supported by Serverless Framework natively
If this is true, how should I go about using pseudo-parameters?
It seems that while the above method does not work, I am able to use the pseudo variable directly without using Ref:
Name: !Sub "${AWS::StackName}-someOutputResourceName"

YAML Syntax for ImportValue into CFN Parameter Default

I'm struggling with the YAML syntax to import a value that was exported by another CFN stack into the default value of a parameter in a new stack.
What I have at the moment is:
Parameters:
DBEndpoint:
Description: Hostname endpoint for RDS Database
Type: String
Default: Fn::ImportValue: 'db-endpoint'
Where db-endpoint is the value exported by the following YAML template snippet:
Outputs:
dbhost:
Description: "RDS Endpoint Address"
Value: !GetAtt DB.Endpoint.Address
Export:
Name: db-endpoint
The export works fine, but I get a parse error (Template format error: YAML not well-formed. ) when trying to load the template with the ImportValue line.
Update:
I have the YAML parsing correctly now, I think, but now get a new error.
With
Parameters:
DBEndpoint:
Description: Hostname endpoint for RDS Database
Type: String
Default: !ImportValue 'db-endpoint'
I get an error Template format error: Every Default member must be a string..
So, it seems closer, but still not working.
This answer implies this might not even be possible... is that the case?
!ImportValue 'db-endpoint' can't be used in Parameters. It can only be used in Resources and Outputs of your template. You have to "manually" (aka, outside of CloudFormation, e.g. by a wrapper script) set the default value of DBEndpoint to the actual value of your db-endpoint.

Cloudformation Error - Resource of type 'AWS::ApiGateway::Model' with identifier 'Empty' already exists

I am creating an APIGateway using Cloudformation. When attempting to create via the AWS CF Console I am recieving this error:
The EmptyModel resource is a AWS::ApiGateway::Model object that looks like this:
EmptyModel:
Type: "AWS::ApiGateway::Model"
Properties:
RestApiId: !Ref ApiGatewayRestApi
Name: "Empty"
Description: "This is a default empty schema model"
Schema: |
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title" : "Empty Schema",
"type" : "object"
}
ContentType: "application/json"
I am referencing this model on every one of my AWS::ApiGateway::Method objects in the CF Template like this:
What am I doing wrong? I used Former2 to reverse engineer my current api and get some of this template for the new api I am creating. So I am wondering if there is just something weird in this? Any help is hella appreciated.
I was able to identify this issue finally through much trial and error. Apparently Models are shared between all of the APIs in your account (or maybe just region)
So the error was indicating that there was already a model called "Empty" and that is because there was one, in a different API. I changed the name to "EmptyModel" and it worked great!
If you used former2 to create your template from existing resources, you can't just deploy the template obtained, as you will get the errors you are getting.
Instead you have to modify the template and import your resources to CloudFormation. Or easier, you have to delete existing resources, and then re-create them using CloudFormation.

Renaming Resource in CloudFormation Template

Can you rename a resource in a CloudFormation template?
Let's say I've created a stack template that creates a single lambda function.
GetTheFunnyPhraseText:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../Lambda/
Handler: GetFunnyPhrase.handler
FunctionName: GetFunnyPhrase
Role: !GetAtt [ ExecuteFunctionGetFunnyPhrase, Arn ]
For whatever reason, I want to change the resource name GetTheFunnyPhraseText to GetFunnyPhrase. Is there a mechanism to change the name?
A couple things I tried...
Changing the resource name in the template. It looks like this acts like a delete on GetTheFunnyPhraseText and create of GetFunnyPhrase. Problem here is the resource creation happens before the resource deletion causing the action to fail because the Lambda function exists.
Create two drafts of the template. Draft-1: Change the function name for the existing resource(s). Draft-2: Delete the old resource (omit their definition from the template) and add the new resource. Execute the draft templates in sequence: 1st then 2nd. This works. It's just gross.
For folks that suggest not naming the function, understood; put a pin in that piece of feedback for the moment.
Now that CloudFormation import is available it's technically possible to do this, although it's tedious.
Here's what you'd need to do:
Update the definition for GetTheFunnyPhraseText to add DeletionPolicy: "Retain", upload to CloudFormation
Remove GetTheFunnyPhraseText entirely from your template. Upload to CloudFormation. This will not actually delete the underlying Lambda because of the previously added DeletionPolicy
Revert your template back to the previous state in Step #1 (add GetTheFunnyPhraseText back) and change the logical name to GetFunnyPhrase
Start the "Stack Actions" > "Import resources into stack" workflow.
Upload your reverted template (with the changed logical name, still including the DeletionPolicy)
The import process will notice the new GetFunnyPhrase logical name and ask you what actual FunctionName should be mapped to that name. Provide the existing GetFunnyPhrase Lambda name and complete the import.
Finally, you can re-upload your template and remove the DeletionPolicy
A tedious process for sure, but technically possible if you really don't want to delete the existing resource.
No! Renaming a resource's logical name is not possible in Cloud Formation.
As you tested as well, CloudFormation sees it as the removal of the old resource and creation of the new one. This is so because the logical resource IDs are bound to the physical IDs of the resources by CloudFormation after creation. But for CloudFormation template language, it only recognizes the logical ID while parsing the template so any changes to that would mean the resource associated to it is going to be changed.
In some resource types though you can set the physical IDs yourself by using certain name properties which are resource-specific, for example a resource AWS::RDS::DBInstance may have a property DBInstanceIdentifier which will be the physical name of the db instance itself.

Unresolved Parameter Store SecureString dynamic reference in CloudFormation template

According to AWS, when deploying infrastructure that requires secrets, i.e. passwords or similar, with CloudFormation, one popular solution consists in using the Parameter Store's SecureStrings from SSM.
However, despite existing CFN documentation describing step-by-step how to use the Dynamic References within the CFN templates, I can not manage to make use of the actual value of the SecureStrings.
Assuming the following JSON representation of the an existing SecureString stored in the SSM Parameter Store:
{
"MyRedshiftMasterUserPassword": {
"value": "Abcd2019",
"type": "SecureString"
}
}
and a YAML CFN template that uses it as stated in the documentation:
Resources
Redshift:
Type: 'AWS::Redshift::Cluster'
Properties:
NodeType: dc2.large
NumberOfNodes: !Ref RedshiftNodes
ClusterType: multi-node
AutomatedSnapshotRetentionPeriod: !Ref AutomatedSnapshotRetentionPeriod
DBName: datawarehouse_v1
MasterUsername: !Ref RedshiftMasterUsername
MasterUserPassword: '{{resolve:ssm-secure:MyRedshiftMasterUserPassword:1}}'
The above solution does not to work so either I am defining the template incorrectly or the support for this feature is not properly implemented which seems odd to me considering that it comes from AWS.
Particularly, I came across the following errors that all end up as UPDATE_FAILED stack:
Whenever the referenced Parameter Name to be resolved is long enough CloudFormation complains:
The parameter MasterUserPassword is not a valid password because it is longer than 64 characters. (Service: AmazonRedshift; Status Code: 400; Error Code: InvalidParameterValue; Request ID: 7be9bd43-2927-11e9-aa88-29bbdcae859e)
Additionally, even though specifically mentioned that slashes can be used in the template references, e.g. /infrastructure/datawarehouse/redshift/MyRedshiftMasterUserPassword following error is issued:
The parameter MasterUserPassword is not a valid password. Only printable ASCII characters except for '/', '#', '"', ' ', '\', ''' may be used. (Service: AmazonRedshift; Status Code: 400; Error Code: InvalidParameterValue)
thus, as result referenced SecureString do not seem to be compatible with the SSM ParameterStore hierarchy (parameters with slashes).
Moreover, removing any of the previously reported invalid character from the parameter name, then it complains of the following:
The parameter MasterUserPassword must contain at least 1 upper case letter. (Service: AmazonRedshift; Status Code: 400; Error Code: InvalidParameterValue; Request ID: 90a263bd-2929-11e9-80c0-ffcecf297c44)
In the end, although using a basic short non-slashed Parameter name in a template allows the stack to finish the Update operation dynamic reference still does not occur as the actual value used turns out to be supplied Parameter Name rather than the value referenced by this, e.g MyRedshiftMasterUserPassword instead of Abcd2019.
I am aware that AWS Secrets Manager could also be used but it is not free.
Opened support case with AWS requesting guidance for this particular strange behaviour of CloudFormation.
According to the support team, effectively this is indeed a known bug for the CloudFormation service without estimated time to fix though.
The resolution of the a SSM Parameter Store SecureString parameter when used as a Dynamic Reference in the particular case of the RedshiftMasterUserPassword property despite referenced in the documentation does not get properly resolved and parameter name is used instead.
Alternatively, they offer 2 workarounds whilst the issue gets fixed:
Get the 'MasterUserPassword' for Redshift from input parameter with property NoEcho set to true. NoEcho property allows you to mask the password value and you don't need to store the password in a template file. However, each time you update the stack you need to enter the password as input parameter. For your reference, below code snippet will be useful.
Second option, which is more versatile:
Define a Lambda backed Custom Resource in your template file, which queries the SSM service and returns the password to CloudFormation. In this scenario, You need to write a Custom Code for lambda function which uses AWS GetParameter API call to retrieve the value of SSM Secure String Parameter and returns the decrypted value to CloudFormation.
Other supported properties for dynamic reference seem to work fine.