如何从CloudFormation实例读取文件内容并在Outputs中展示
Hey there, let's break down why your current approach isn't working and walk through a solution to get that file content into your CloudFormation Outputs.
The Problem with Your Current Setup
CloudFormation's Fn::Sub and Fn::Join are template-level functions—they run when CloudFormation parses your template during deployment, not after your EC2 instance is up and running. So when you write !Sub "cat /opt/tmp/data", all CloudFormation does is output the string itself, not execute the command on the instance. The Outputs are finalized before your instance even has a chance to create or populate that /opt/tmp/data file.
The Solution: Combine User Data, SSM Parameter Store, and a Custom Resource
We need a way to:
- Get the EC2 instance to read the file and send its content to a place CloudFormation can access
- Fetch that content from CloudFormation and display it in Outputs
Here's a step-by-step implementation:
1. Configure EC2 Instance to Upload File Content to SSM
First, we'll give the EC2 instance permissions to write to AWS Systems Manager Parameter Store, then use User Data to upload the file content once it's available.
Resources: # IAM Role for EC2: Allows writing to SSM Parameter Store FileDataInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SSMPutParameterAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: ssm:PutParameter Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/cfn/instance-file-data' # Instance Profile to attach the role to EC2 FileDataInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: [!Ref FileDataInstanceRole] # Your EC2 Instance MyEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-0c55b159cbfafe1f0 # Replace with your region's Amazon Linux 2 AMI InstanceType: t2.micro IamInstanceProfile: !Ref FileDataInstanceProfile UserData: Fn::Base64: !Sub | #!/bin/bash # Wait for the /opt/tmp/data file to exist (adjust sleep time if needed) while [ ! -f /opt/tmp/data ]; do echo "Waiting for /opt/tmp/data to be created..." sleep 10 done # Read the file content and upload to SSM Parameter Store FILE_CONTENT=$(cat /opt/tmp/data) aws ssm put-parameter --name "/cfn/instance-file-data" --type String --value "$FILE_CONTENT" --overwrite # Send success signal to CloudFormation to mark instance initialization as done /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource MyEC2Instance --region ${AWS::Region} # Wait for the instance to finish uploading the file before proceeding CreationPolicy: ResourceSignal: Timeout: PT15M # 15-minute timeout (adjust based on your file generation time)
2. Create a Lambda-Backed Custom Resource to Fetch the SSM Parameter
CloudFormation can't directly read SSM parameters into Outputs, so we'll use a Lambda function to pull the value and return it as a custom resource.
# IAM Role for Lambda: Allows reading SSM and interacting with CloudFormation FileDataLambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SSMGetParameterAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: ssm:GetParameter Resource: !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/cfn/instance-file-data' - PolicyName: LambdaBasicLogs PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: 'arn:aws:logs:*:*:*' # Lambda Function to read the SSM parameter FileDataLambdaFunction: Type: AWS::Lambda::Function Properties: Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt FileDataLambdaRole.Arn Code: ZipFile: | import boto3 import cfnresponse ssm_client = boto3.client('ssm') def lambda_handler(event, context): try: # Only fetch the value on create/update events if event['RequestType'] in ['Create', 'Update']: param_response = ssm_client.get_parameter( Name='/cfn/instance-file-data', WithDecryption=False ) file_content = param_response['Parameter']['Value'] # Send success response with the file content cfnresponse.send(event, context, cfnresponse.SUCCESS, {'FileContent': file_content}) else: # For delete events, just send success cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception as e: print(f"Error fetching parameter: {str(e)}") cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) # Custom Resource to trigger the Lambda function FileDataCustomResource: Type: Custom::FileData Properties: ServiceToken: !GetAtt FileDataLambdaFunction.Arn DependsOn: MyEC2Instance # Ensure the instance has uploaded the file first
3. Add the File Content to Outputs
Finally, reference the custom resource's returned value in your Outputs:
Outputs: FileData: Description: "Content of /opt/tmp/data from the EC2 instance" Value: !GetAtt FileDataCustomResource.FileContent
Key Notes
- Make sure your EC2 AMI has the AWS CLI installed (Amazon Linux 2 and Ubuntu Server images usually do by default)
- Adjust the wait loop in User Data if your
/opt/tmp/datafile takes longer to generate - The
CreationPolicytimeout should be long enough to cover your file generation and upload time - If your file has special characters (like newlines or quotes), you may need to escape them when uploading to SSM (e.g., using
jqto format the content properly)
内容的提问来源于stack exchange,提问作者aihsts

