Moderniser.repo
  • 日本語
  • English✔️
  • 日本語
  • English✔️
[AWS]
What is a Design Guideline?
AWS Organizations AWS Control Tower
AWS IAM / AWS IAM Identity Center
AWS CloudTrail AWS Config Amazon GuardDuty
aws:executeScript action for SSM Automation
How to create custom checks with Prowler How to scrape the reference of Security Hub control and convert it to data CIS AWS Foundations Benchmark v3.0.0 How to use the latest Boto3 with SSM Automation (or Lambda) How to write a CommaDelimitedList in List (array) format in samconfig.yml How to specify a local file using the Script property of AWS::SSM::Document How to get information on multiple AWS resources in Terraforms Data Source
CloudFormation template / SAM template coding rules
Amazon S3
Amazon Athena (Glue Database)
[Visual Studio Code]
How to use the latest AWS icon in Draw.io of Visual Studio Code
Recommended plug-in summary of Visual Studio Code for cloud engineers
[iPhone]
[Others]
How to switch accounts for GitHub CLI commands using commands
Privacy Policy
Profile
...

Kanji

・ Cloud engineer / freelance
・ Born in 1993
・ Born in Ehime Prefecture / Lives in Shibuya-ku, Tokyo
・ AWS history 5 years

Profile details

Contact
Twitter(@kanji_aws_fl) Instagram(kanji_aws_freelance) Mail(kanji@cont-aid.com)


【Python】How to specify a local file using the Script property of AWS::SSM::Document


Created date: 2025/04/13, Update date: 2025/04/13


aws cloudformation package command cannot upload the file specified in the Script property of the aws:executeScript step, which is used to define the runbook for SSM Automation in AWS::SSM::Document, to S3.
The reason why this process has not been supported by AWS feature updates for a while is presumably because, in order to specify a script placed in an S3 bucket for AWS::SSM::Document, it is necessary to specify not only the name of the S3 bucket and file name where it is stored but also the checksum as a hash value in AWS::SSM::Document.
In this article, we have implemented a conversion process in Python so that local files can be specified by path in the Script property of AWS::SSM::Document and deployed. The method is described here.

Table of Contents


  1. Notes
  2. Coding Rules and Prerequisites
  3. Process to Write the File Content Specified in the Script Property of AWS::SSM::Document into a CloudFormation Template

Notes

  • By executing the Python script described in this article, you can write the contents of the file specified in the Script property of the AWS::SSM::Document resource in a CloudFormation template.
  • For Python scripts, the modules available for use are equivalent to the standard modules of Lambda. If you need to use modules other than the standard Lambda modules, this Python script cannot handle them.
    • Standard modules such as os , argparse , boto3 , json , logging , time , and datetime are available.
  • If you need to use modules other than the standard Lambda modules, you must manually upload a ZIP file containing the Python script and packages to an S3 bucket and then deploy the CloudFormation template.

Coding Rules and Prerequisites

  • The Python script described in this article adheres to PEP8.
    • Syntax checks are performed using flake8 , and automatic formatting is done using black .
    • For code that needs to ignore checks due to processing requirements, # noqa is added.
    • The maximum number of characters per line ( max-line-length ) is set to 200.
  • The runtime version of Python used for verification is 3.12.1.

Process to Write the File Content Specified in the Script Property of AWS::SSM::Document into a CloudFormation Template

  • The required modules for execution are as follows. Since argparse and os are part of the standard library, no additional installation is required.
ruamel.yaml

  • The following Python script writes the contents of the file specified in the Script property of the AWS::SSM::Document into a CloudFormation template. Save it with an appropriate Python file name (e.g., package-ssm-document.py ).
    • Similar to the aws cloudformation package command, it must be executed before deploying the CloudFormation template. Use it as part of your execution steps or integrate it into your CI/CD pipeline.
import argparse
import os
from ruamel.yaml import YAML
from ruamel.yaml.scalarstring import LiteralScalarString

yaml = YAML()
yaml.default_flow_style = False
yaml.sort_keys = False

def replace_script_with_inline_code(input_file, output_file):
    base_dir = os.path.dirname(input_file)

    with open(input_file, "r") as file:
        yaml_content = yaml.load(file)

    def process_main_steps(main_steps):
        for step in main_steps:
            if step.get("action") == "aws:executeScript" and "Script" in step["inputs"]:
                script_path = step["inputs"]["Script"]
                full_script_path = os.path.normpath(os.path.join(base_dir, script_path))
                if os.path.exists(full_script_path):
                    with open(full_script_path, "r") as script_file:
                        script_content = script_file.read()
                    step["inputs"]["Script"] = apply_literal_block_style(script_content)
                else:
                    print(f"Warning: Script file not found at {full_script_path}")
            elif step.get("action") == "aws:loop" and "Steps" in step["inputs"]:
                process_main_steps(step["inputs"]["Steps"])
            if "nextStep" in step:
                process_main_steps(step.get("nextStep", []))

    def apply_literal_block_style(script_content):
        return LiteralScalarString(script_content)

    for resource in yaml_content.get("Resources", {}).values():
        if resource.get("Type") == "AWS::SSM::Document":
            content = resource.get("Properties", {}).get("Content", {})
            if "mainSteps" in content:
                process_main_steps(content["mainSteps"])

    output_dir = os.path.dirname(output_file)
    if output_dir and not os.path.exists(output_dir):
        os.makedirs(output_dir)

    with open(output_file, "w") as file:
        yaml.dump(yaml_content, file)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Transform parameter overrides in a YAML file.")
    parser.add_argument("--input-file", help="Path to the input YAML file.", default="template.original.yml")
    parser.add_argument("--output-file", help="Path to the output YAML file.", default="template.yml")
    args = parser.parse_args()
    replace_script_with_inline_code(args.input_file, args.output_file)

  • Below is an example of execution.
    • The options specify converting template.original.yml to template.yml .
    • Specify the file paths as arguments according to your requirements.
python package-ssm-document.py \
--input-file tests/template1.original.yml \
--output-file .alcache/template.yml

  • The contents of template.original.yml are as follows. The file specified in the Script property is src/sample1.py .
AWSTemplateFormatVersion: 2010-09-09
Description: template.yml
Resources:
  TestRunbook:
    Type: AWS::SSM::Document
    Properties:
      Name: Sample-TestRunbook
      DocumentType: Automation
      DocumentFormat: YAML
      UpdateMethod: NewVersion
      Content:
        schemaVersion: '0.3'
        mainSteps:
          - name: SampleStep
            action: aws:executeScript
            isEnd: true
            inputs:
              Runtime: python3.11
              Handler: main
              Script: src/sample1.py

  • Below is the content of the transformed template.yml . You can confirm that the content of the file specified in the Script property has been replaced with the value of the Script property.
AWSTemplateFormatVersion: 2010-09-09
Description: template.yml
Resources:
  TestRunbook:
    Type: AWS::SSM::Document
    Properties:
      Name: Sample-TestRunbook
      DocumentType: Automation
      DocumentFormat: YAML
      UpdateMethod: NewVersion
      Content:
        schemaVersion: '0.3'
        mainSteps:
        - name: SampleStep
          action: aws:executeScript
          isEnd: true
          inputs:
            Runtime: python3.11
            Handler: main
            Script: |
              def main(event, context):
                  print("[sample1] Hello, World!")

              if __name__ == "__main__":
                  main(None, None)



©2025 ContAID