When updating an AWS Glue Table is it possible to automatically update the partition metadata - aws-cloudformation

I have a partitioned S3 bucket. I am reading the data from the partitions using AWS Athena. I create the AWS Glue Table used in Athena via a CloudFormation stack. The relevant part of the stack is shown here
S3ServerAccessLogsTable:
Type: AWS::Glue::Table
DependsOn: S3ServerAccessLogsDatabase
Properties:
CatalogId: !Ref AWS::AccountId
DatabaseName:
Fn::ImportValue: S3ServerAccessLogsDatabase
TableInput:
Name: s3_server_access_logs
Description: !Sub
- AWS GLUE table for viewing server access logs in ${S3Bucket}
- S3Bucket: !Ref BucketName
TableType: EXTERNAL_TABLE
PartitionKeys:
- Name: bucket
Type: string
StorageDescriptor:
Columns:
- Name: bucket_owner
Type: string
- Name: bucket_name
Type: string
- Name: request_date_time
Type: string
- Name: remote_ip
Type: string
- Name: requester
Type: string
- Name: request_id
Type: string
- Name: operation
Type: string
- Name: key
Type: string
- Name: request_uri_operation
Type: string
- Name: request_uri_key
Type: string
- Name: request_uri_httpProtoversion
Type: string
- Name: http_status
Type: string
- Name: error_code
Type: string
- Name: bytes_sent
Type: bigint
- Name: object_size
Type: bigint
- Name: total_time
Type: string
- Name: turnaround_time
Type: string
- Name: referrer
Type: string
- Name: user_agent
Type: string
- Name: version_id
Type: string
- Name: host_id
Type: string
- Name: sig_v
Type: string
- Name: cipher_suite
Type: string
- Name: auth_type
Type: string
- Name: end_point
Type: string
- Name: tls_version
Type: string
InputFormat: org.apache.hadoop.mapred.TextInputFormat
OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
Location: !Sub
- s3://${S3LoggingBucket}/s3-server-access-logs/
- S3LoggingBucket: !Ref S3ServerAccessLogsBucket
SerdeInfo:
SerializationLibrary: org.apache.hadoop.hive.serde2.RegexSerDe
Parameters:
{
"serialization.format": "1",
"input.regex": '([^ ]*) ([^ ]*) \[(.*?)\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) \"([^ ]*) ([^ ]*) (- |[^ ]*)\" (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ("[^"]*") ([^ ]*)(?: ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*))?.*$'
}
If I update the AWS Glue Table in the CloudFormation template and then update the stack, I now need to run the command MSCK REPAIR TABLE <table_name>; in Athena so that the partition metadata will be mapped to the table. If I don't do this last step, there is no data visible when I query the table.
The problem is that I keep forgetting to run the MSCK REPAIR TABLE <table_name>; command and others who are not used to the process will definitely forget.
So I'm wondering if there is an automated way to handle this, so that the partition metadata is automatically reloaded when the AWS Glue table is updated?

Have you looked into partition projection? You can add the partition projection information to CloudFormation as well.

Related

How to specify Glue Table and schema in Glue crawler using cloud formation

I'm creating Glue Database, Glue Table with Schema, and Glue Crawler using CFT, please find my code below. In my Glue Crawler, I would like to specify the glue table "myTestTable" and schema in the Glue Crawler so that when any schema update happens (adding or removing any field) my crawler automatically updates with this new schema change.
How can I achieve this using CFT, it would be really appreciated if someone can help me to resolve this issue.
GlueDatabase:
Type: AWS::Glue::Database
Properties:
CatalogId: !Ref AWS::AccountId
DatabaseInput:
Name: myTestGlueDB
GlueTable:
Type: AWS::Glue::Table
Properties:
CatalogId: !Ref AWS::AccountId
DatabaseName: !Ref GlueDatabase
TableInput:
Parameters:
classification: parquet
Name: myTestTable
Owner: owner
Retention: 0
StorageDescriptor:
Columns:
- Name: productName
Type: string
- Name: productId
Type: string
InputFormat: org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat
OutputFormat: org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat
Compressed: false
NumberOfBuckets: -1
SerdeInfo:
SerializationLibrary: org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe
Parameters:
serialization.format: '1'
Location: s3://test-bucket
BucketColumns: []
SortColumns: []
StoredAsSubDirectories: false
PartitionKeys:
- Name: id
Type: string
- Name: year
Type: string
- Name: month
Type: string
- Name: day
Type: string
TableType: EXTERNAL_TABLE
GlueCrawler:
Type: AWS::Glue::Crawler
Properties:
Name: myTestCrawler
Role: GlueCrawlerRole
DatabaseName: myTestGlueDB
TablePrefix: myTable-
Targets:
S3Targets:
- Path: s3://test-bucket
SchemaChangePolicy:
UpdateBehavior: UPDATE_IN_DATABASE
DeleteBehavior: LOG
Configuration: "{\"Version\":1.0,\"Grouping\":{\"TableGroupingPolicy\":\"CombineCompatibleSchemas\"}}"

While deploying stack encountered ' Unsupported ActionTypeId' error

When I create cloudformation stack for codepipeline, it fails and the error message is "Encountered unsupported property ActionTypeId".
Template is:
AWSTemplateFormatVersion: '2010-09-09'
Description: Model and provision AWS CodePipeline and AWS CodeBuild
Parameters:
CodePipelineSourceBucketName:
Type: String
Description: Bucket to store source code
Default: covid19-codepipeline-source
CodePipelineSourceObjectKey:
Type: String
Description: Object key of zip file uploaded to source bucket
Default: source.zip
CodePipelineArtifactStoreBucketName:
Type: String
Description: Bucket to store artifacts created by codepipeline
Default: covid19-codepipeline-artifacts
CloudTrailLogsBucketName:
Type: String
Description: Bucket to store cloudtrail logs
Default: covid19-cloudtrail-logs
Resources:
# CodePipeline
DeployPipelineForGlueWorkflow:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub DeployPipelineForGlueWorkflow-${AWS::StackName}
RoleArn: !GetAtt CodePipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref CodePipelineArtifactStoreBucket
Stages:
-
Name: Source
Actions:
-
Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Version: "1"
Provider: S3
OutputArtifacts:
- Name: SourceOutput
Configuration:
S3Bucket: !Ref CodePipelineSourceBucket
S3ObjectKey: !Ref CodePipelineSourceObjectKey
PollForSourceChanges: false
RunOrder: 1
-
I checked the indentation as well but still facing same issue.

is it possible to create Kubernetes pods, services, replica controllers etc on AWS cloudfromation?

does AWS cloudformation supports creation of Kubernetes pods, services, replica controllers etc or setting up the EKS clusters and worker nodes and using Kubectl to create the resources are the only way?
Not out of the box, but you can if you use a custom resource type backed by a lambda function in CloudFormation.
The AWS EKS quickstart has an example:
AWSTemplateFormatVersion: "2010-09-09"
Description: "deploy an example workload into an existing kubernetes cluster (qs-1p817r5f9)"
Parameters:
KubeConfigPath:
Type: String
KubeConfigKmsContext:
Type: String
Default: "EKSQuickStart"
KubeClusterName:
Type: String
NodeInstanceProfile:
Type: String
QSS3BucketName:
AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$
ConstraintDescription: Quick Start bucket name can include numbers, lowercase
letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen
(-).
Default: aws-quickstart
Description: S3 bucket name for the Quick Start assets. This string can include
numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start
or end with a hyphen (-).
Type: String
QSS3KeyPrefix:
AllowedPattern: ^[0-9a-zA-Z-/.]*$
ConstraintDescription: Quick Start key prefix can include numbers, lowercase letters,
uppercase letters, hyphens (-), dots(.) and forward slash (/).
Default: quickstart-amazon-eks/
Description: S3 key prefix for the Quick Start assets. Quick Start key prefix
can include numbers, lowercase letters, uppercase letters, hyphens (-), dots(.) and
forward slash (/).
Type: String
QSS3BucketRegion:
Default: 'us-east-1'
Description: The AWS Region where the Quick Start S3 bucket (QSS3BucketName) is
hosted. When using your own bucket, you must specify this value.
Type: String
LambdaZipsBucketName:
Description: 'OPTIONAL: Bucket Name where the lambda zip files should be placed,
if left blank a bucket will be created.'
Type: String
Default: ''
K8sSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
VPCID:
Type: AWS::EC2::VPC::Id
ControlPlaneSecurityGroup:
Type: AWS::EC2::SecurityGroup::Id
Conditions:
CreateLambdaZipsBucket: !Equals
- !Ref 'LambdaZipsBucketName'
- ''
UsingDefaultBucket: !Equals [!Ref QSS3BucketName, 'aws-quickstart']
Resources:
WorkloadStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: !Sub
- 'https://${S3Bucket}.s3.${S3Region}.${AWS::URLSuffix}/${QSS3KeyPrefix}templates/example-workload.template.yaml'
- S3Region: !If [UsingDefaultBucket, !Ref 'AWS::Region', !Ref QSS3BucketRegion]
S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
Parameters:
KubeManifestLambdaArn: !GetAtt KubeManifestLambda.Arn
HelmLambdaArn: !GetAtt HelmLambda.Arn
KubeConfigPath: !Ref KubeConfigPath
KubeConfigKmsContext: !Ref KubeConfigKmsContext
KubeClusterName: !Ref KubeClusterName
NodeInstanceProfile: !Ref NodeInstanceProfile
CopyZips:
Type: Custom::CopyZips
Properties:
ServiceToken: !GetAtt 'CopyZipsFunction.Arn'
DestBucket: !Ref LambdaZipsBucketName
SourceBucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
Prefix: !Ref 'QSS3KeyPrefix'
Objects:
- functions/packages/Helm/lambda.zip
- functions/packages/DeleteBucketContents/lambda.zip
- functions/packages/KubeManifest/lambda.zip
- functions/packages/LambdaEniCleanup/lambda.zip
VPCLambdaCleanup:
Type: Custom::LambdaCleanup
Properties:
ServiceToken: !GetAtt VPCLambdaCleanupLambdaFunction.Arn
Region: !Ref "AWS::Region"
LambdaFunctionNames:
- !Ref KubeManifestLambda
VPCLambdaCleanupLambdaFunction:
DependsOn: CopyZips
Type: "AWS::Lambda::Function"
Properties:
Handler: lambda_function.lambda_handler
MemorySize: 128
Role: !GetAtt LambdaCleanUpFunctionRole.Arn
Runtime: python3.7
Timeout: 900
Code:
S3Bucket: !Ref LambdaZipsBucketName
S3Key: !Sub '${QSS3KeyPrefix}functions/packages/LambdaEniCleanup/lambda.zip'
HelmLambda:
DependsOn: CopyZips
Type: AWS::Lambda::Function
Properties:
Handler: lambda_function.lambda_handler
MemorySize: 128
Role: !GetAtt ManifestRole.Arn
Runtime: python3.6
Timeout: 900
Code:
S3Bucket: !Ref LambdaZipsBucketName
S3Key: !Sub '${QSS3KeyPrefix}functions/packages/Helm/lambda.zip'
VpcConfig:
SecurityGroupIds: [ !Ref EKSLambdaSecurityGroup ]
SubnetIds: !Ref K8sSubnetIds
KubeManifestLambda:
DependsOn: CopyZips
Type: AWS::Lambda::Function
Properties:
Handler: lambda_function.lambda_handler
MemorySize: 128
Role: !GetAtt ManifestRole.Arn
Runtime: python3.6
Timeout: 900
Code:
S3Bucket: !Ref LambdaZipsBucketName
S3Key: !Sub '${QSS3KeyPrefix}functions/packages/KubeManifest/lambda.zip'
VpcConfig:
SecurityGroupIds: [ !Ref EKSLambdaSecurityGroup ]
SubnetIds: !Ref K8sSubnetIds
DeleteBucketContentsLambda:
DependsOn: CopyZips
Type: AWS::Lambda::Function
Properties:
Handler: lambda_function.lambda_handler
MemorySize: 128
Role: !GetAtt DeleteBucketContentsRole.Arn
Runtime: python3.7
Timeout: 900
Code:
S3Bucket: !Ref LambdaZipsBucketName
S3Key: !Sub '${QSS3KeyPrefix}functions/packages/DeleteBucketContents/lambda.zip'
CopyZipsFunction:
Type: AWS::Lambda::Function
Properties:
Description: Copies objects from a source S3 bucket to a destination
Handler: index.handler
Runtime: python3.7
Role: !GetAtt CopyZipsRole.Arn
Timeout: 900
Code:
ZipFile: |
import json
import logging
import threading
import boto3
import cfnresponse
def copy_objects(source_bucket, dest_bucket, prefix, objects):
s3 = boto3.client('s3')
for o in objects:
key = prefix + o
copy_source = {
'Bucket': source_bucket,
'Key': key
}
print('copy_source: %s' % copy_source)
print('dest_bucket = %s'%dest_bucket)
print('key = %s' %key)
s3.copy_object(CopySource=copy_source, Bucket=dest_bucket,
Key=key)
def delete_objects(bucket, prefix, objects):
s3 = boto3.client('s3')
objects = {'Objects': [{'Key': prefix + o} for o in objects]}
s3.delete_objects(Bucket=bucket, Delete=objects)
def timeout(event, context):
logging.error('Execution is about to time out, sending failure response to CloudFormation')
cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_resource_id)
def handler(event, context):
physical_resource_id = None
if "PhysicalResourceId" in event.keys():
physical_resource_id = event["PhysicalResourceId"]
# make sure we send a failure to CloudFormation if the function is going to timeout
timer = threading.Timer((context.get_remaining_time_in_millis()
/ 1000.00) - 0.5, timeout, args=[event, context])
timer.start()
print('Received event: %s' % json.dumps(event))
status = cfnresponse.SUCCESS
try:
source_bucket = event['ResourceProperties']['SourceBucket']
dest_bucket = event['ResourceProperties']['DestBucket']
prefix = event['ResourceProperties']['Prefix']
objects = event['ResourceProperties']['Objects']
if event['RequestType'] == 'Delete':
delete_objects(dest_bucket, prefix, objects)
else:
copy_objects(source_bucket, dest_bucket, prefix, objects)
except Exception as e:
logging.error('Exception: %s' % e, exc_info=True)
status = cfnresponse.FAILED
finally:
timer.cancel()
cfnresponse.send(event, context, status, {}, physical_resource_id)
LambdaZipsBucket:
Type: AWS::S3::Bucket
Condition: CreateLambdaZipsBucket
LambdaCleanUpFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: ['sts:AssumeRole']
Effect: Allow
Principal:
Service: [lambda.amazonaws.com]
Version: '2012-10-17'
Path: /
Policies:
- PolicyName: LambdaRole
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 'logs:CreateLogGroup'
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Effect: Allow
Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"
- Action:
- 'ec2:*'
Effect: Allow
Resource: "*"
DeleteBucketContentsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: deletebucketcontents
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:*
Resource:
- !Sub 'arn:${AWS::Partition}:s3:::${LambdaZipsBucketName}/*'
- !Sub 'arn:${AWS::Partition}:s3:::${LambdaZipsBucketName}'
CopyZipsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- !Su 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
Policies:
- PolicyName: lambda-copier
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:GetObject
Resource: !Sub
- 'arn:${AWS::Partition}:s3:::${S3Bucket}/${QSS3KeyPrefix}*'
- S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
- Effect: Allow
Action:
- s3:PutObject
- s3:DeleteObject
Resource: !Sub 'arn:${AWS::Partition}:s3:::${LambdaZipsBucketName}/${QSS3KeyPrefix}*'
ManifestRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: eksStackPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: s3:GetObject
Resource: !Sub
- "arn:${AWS::Partition}:s3:::${BucketName}/*"
- S3Bucket: !If [UsingDefaultBucket, !Sub '${QSS3BucketName}-${AWS::Region}', !Ref QSS3BucketName]
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
Resource:
- "*"
- Action: "kms:decrypt"
Effect: Allow
Resource: "*"
- Action: "s3:GetObject"
Effect: Allow
Resource: "*"
EKSLambdaSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for lambda to communicate with cluster API
VpcId: !Ref VPCID
ClusterControlPlaneSecurityGroupIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Allow lambda to communicate with the cluster API Server
GroupId: !Ref ControlPlaneSecurityGroup
SourceSecurityGroupId: !Ref EKSLambdaSecurityGroup
IpProtocol: tcp
ToPort: 443
FromPort: 443
It works by creating a lambda function customer resource KubeManifestLambda and HelmLambda that has kubectl and helm installed respectively, both configured with a role that allows them to access the EKS k8s cluster.
Then these custom resources can be used to deploy k8s manifests and helm charts with custom values, like in this example.
KubeManifestExample:
Type: "Custom::KubeManifest"
Version: '1.0'
Properties:
# The lambda function that executes the manifest against the cluster. This is created in one of the parent stacks
ServiceToken: !Ref KubeManifestLambdaArn
# S3 path to the encrypted config file eg. s3://my-bucket/kube/config.encrypted
KubeConfigPath: !Ref KubeConfigPath
# context for KMS to use when decrypting the file
KubeConfigKmsContext: !Ref KubeConfigKmsContext
# Kubernetes manifest
Manifest:
apiVersion: v1
kind: ConfigMap
metadata:
# If name is not specified it will be automatically generated,
# and can be retrieved with !GetAtt LogicalID.name
#
# name: test
#
# if namespace is not specified, "default" namespace will be used
namespace: kube-system
data:
# examples of consuming outputs of the HelmExample resource below's output. Creates an implicit dependency, so
# this resource will only launch once the HelmExample resource has completed successfully
ServiceCatalogReleaseName: !Ref HelmExample
ServiceCatalogKubernetesServiceName: !GetAtt HelmExample.Service0
This even lets you reference other Cloud formation resources such as RDS instances that are created as part of a workload.
You can use CloudFormation to create EKS cluster and worker nodes but you have to use kubectl for any operation on cluster like creating service, pods, deployments etc.....you can’t use CloudFormation for that
If you use CDK, you can use cluster.add_helm_chart() or HelmChart class. It will create a lambda behind the scenes.
Or you can create a lambda directly with https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.lambda_layer_kubectl/README.html

Is it possible to reuse pre-built Text in CloudFormation?

I have a few parameters passed to one of my CloudFormation conf file like this (YAML):
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation Template for creating pipeline workflow.
Parameters:
Opt1:
Description: my opt1
Type: String
Default: opt1_default
Opt2:
Description: my opt2
Type: String
Default: opt2_default
Opt3:
Description: my opt3
Type: String
Default: opt3_default
...
In many other parts of my conf I keep on use:
...
Name: !Sub '${Opt1}-${Opt2}-${Opt3}'
...
Name: !Sub '${Opt1}-${Opt2}-${Opt3}'
...
Name: !Sub '${Opt1}-${Opt2}-${Opt3}'
...
Is it possible to create some reference like:
SomeRef: !Sub '${Opt1}-${Opt2}-${Opt3}'
in a way that I can do:
...
Name: !Ref SomeRef
...
Name: !Ref SomeRef
...
Name: !Ref SomeRef
...
?
Nested template with no resources and single output can do it:
NameCreator.yaml
---
AWSTemplateFormatVersion: 2010-09-09
Description: 'Empty nested template'
Parameters:
Opt1:
Type: String
Opt2:
Type: String
Conditions:
Never: !Equals [ a, b ]
Resources:
NullResource:
Type: Custom::Null
Condition: Never
Outputs:
Name:
Value: !Sub '${Opt1}-${Opt2}'
And then in main template call it:
NameCreator:
Type: AWS::CloudFormation::Stack
Properties:
Parameters:
Opt1: !Ref Opt1
Opt2: !Ref Opt2
TemplateURL: https://mybucket.s3.amazonaws.com/NameCreator.yaml
and use it:
Name: !GetAtt 'NameCreator.Outputs.Name'

Template validation error: Template error: unresolved condition dependency BackupSize in Fn::If

I am writing CF code to launch ec2 instance, this is what my code looks like:
I am facing these 2 issues:
1) I get this error "Template validation error: Template error: unresolved condition dependency BackupSize in Fn::If"
2) I want to join Parameter Name and from Mappings USERDATA. (The remaining userdata works fine, but this join is not working and just puts the same code in the userdata.
Can anyone help me out please?
AWSTemplateFormatVersion: "2010-09-09"
Description: "This template should be used to deploy ONLY test servers"
Mappings:
Regions:
us-east-1:
"AMI": "ami-x"
"VPC": "vpc-x"
"SUBNET": "subnet-x"
"USERDATA": ".example.com"
"SHARE": "server1:/share"
"SecurityGroups": "sg-x"
"SecurityGroups2": "sg-y"
Parameters:
ApplSize:
Description: "Please enter application vol. size"
Type: "String"
BackupSize:
Description: "Please enter backup vol. size"
Type: "String"
Resources:
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [Regions, !Ref "AWS::Region", AMI]
InstanceType: !Ref InstanceType
SubnetId: !FindInMap [Regions, !Ref "AWS::Region", SUBNET]
SecurityGroupIds:
- !FindInMap [Regions, !Ref "AWS::Region", SecurityGroups]
- !FindInMap [Regions, !Ref "AWS::Region", SecurityGroups2]
BlockDeviceMappings:
-
DeviceName : "/dev/sda1"
Ebs:
VolumeSize: "20"
VolumeType: gp2
-
DeviceName : "/dev/sde"
Ebs:
VolumeSize: !Ref ApplSize
VolumeType: gp2
-
DeviceName : "/dev/sdc"
Ebs:
VolumeSize: "5"
VolumeType: gp2
- Fn::If:
- BackupSize
-
DeviceName : "/dev/sdg"
Ebs:
VolumeSize: !Ref BackupSize
VolumeType: gp2
- !Ref "AWS::NoValue"
UserData:
Fn::Base64: !Sub |
#!/bin/bash
NEW_HOSTNAME=Fn::Join: [ " ", [ !Ref Name, Fn::FindInMap:
[Regions, !Ref "AWS::Region", USERDATA] ] ]
hostname $NEW_HOSTNAME
myshortname=`hostname -s`
I expect the template to create Backup volume if I put any value in the parameter, and if I leave backupsize value blank, it should not create this disk.
AWSTemplateFormatVersion: "2010-09-09"
Description: "This template should be used to deploy ONLY test servers"
Mappings:
Regions:
us-east-1:
"AMI": "ami-x"
"VPC": "vpc-x"
"SUBNET": "subnet-x"
"USERDATA": ".example.com"
"SHARE": "server1:/share"
"SecurityGroups": "sg-x"
"SecurityGroups2": "sg-y"
Parameters:
ApplSize:
Description: "Please enter application vol. size"
Type: "String"
BackupSize:
Description: "Please enter backup vol. size"
Type: "String"
VaultSize:
Description: "Please enter secret vol. size"
Type: "String"
InstanceType:
Description: "Please select the instance type"
Type: "String"
Name:
Description: "Please mention server name"
Type: "String"
CustomerName:
Description: "Please mention customer name"
Type: "String"
Url:
Description: "Please mention url without the domain name"
Type: "String"
Conditions:
BackupVol: !Equals [!Ref BackupSize, ""]
Resources:
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !FindInMap [Regions, !Ref "AWS::Region", AMI]
InstanceType: !Ref InstanceType
SubnetId: !FindInMap [Regions, !Ref "AWS::Region", SUBNET]
SecurityGroupIds:
- !FindInMap [Regions, !Ref "AWS::Region", SecurityGroups]
- !FindInMap [Regions, !Ref "AWS::Region", SecurityGroups2]
BlockDeviceMappings:
-
DeviceName : "/dev/sda1"
Ebs:
VolumeSize: "20"
VolumeType: gp2
-
DeviceName : "/dev/sde"
Ebs:
VolumeSize: !Ref ApplSize
VolumeType: gp2
-
DeviceName : "/dev/sdc"
Ebs:
VolumeSize: "5"
VolumeType: gp2
- Fn::If:
- BackupVol
- !Ref "AWS::NoValue"
- DeviceName : "/dev/sdg"
Ebs:
VolumeSize: !Ref BackupSize
VolumeType: gp2
UserData:
Fn::Base64: !Sub
- |+
#!/bin/bash -xe
NEW_HOSTNAME=${test}
- test:
Fn::FindInMap: [Regions, !Ref "AWS::Region", Name]
The various versions of the template presented all have basic formatting problems. The latest version (attached in a comment below this answer):
▶ aws cloudformation validate-template --template-body file://cloudformation.yml
An error occurred (ValidationError) when calling the ValidateTemplate operation: [/Mappings/Regions] 'null' values are not allowed in templates
The formatting problems include duplicate keys, incorrect indentation, etc. These problems can't be detected by simply checking if the file is valid YAML. It can be valid YAML and still be invalid for Cloudformation. You need to use the validate-template command as I showed above.
After fixing up the various issues in the provided template (including the new version), I was unable to reproduce an error about
unresolved condition dependency BackupSize in Fn::If
What you have in Fn::If looks ok to me.
As for how to interpolate Fn::Join in the UserData:
I would consider refactoring so that complex logic lies outside of Cloudformation. For instance, you could pass the hostname as a separate parameter.
If you really want to do it this way you can do it like this:
UserData:
Fn::Base64: !Sub
- |
#!/bin/bash
NEWHOSTNAME=${newhostname}
hostname $NEW_HOSTNAME
myshortname=`hostname -s`
- newhostname: !Join ["", ["foo", "bar"]]
Simple ans..first condition of IF should be a CONDITIONS..Create a conditions block and use that in IF... Example:
Conditions:
mycondition: blah blah
IF [mycondition: blah1, blah2]