Skip to content

Add dynamic resource generation from AWS CloudFormation schemas#199

Closed
Jordi Soucheiron (jsoucheiron) wants to merge 7 commits intomasterfrom
dynamic-resource-generation
Closed

Add dynamic resource generation from AWS CloudFormation schemas#199
Jordi Soucheiron (jsoucheiron) wants to merge 7 commits intomasterfrom
dynamic-resource-generation

Conversation

@jsoucheiron
Copy link
Copy Markdown
Member

@jsoucheiron Jordi Soucheiron (jsoucheiron) commented Feb 2, 2026

Summary

  • Adds a schema_generator module that dynamically creates pycfmodel Resource classes from AWS CloudFormation resource provider schemas
  • Downloads and caches schemas from AWS (https://schema.cloudformation.{region}.amazonaws.com/CloudformationSchema.zip)
  • Generates Pydantic models with proper type hints from JSON schemas
  • Filters out read-only properties automatically
  • Adds strict validation for CloudFormation intrinsic functions to detect malformed templates
  • Adds automatic dynamic resource generation during template parsing for unmodeled resource types

Features Supported

Dynamic Resource Generation During Parsing

When enabled, pycfmodel will automatically generate typed models for resource types that are not explicitly modeled:

from pycfmodel import parse, enable_dynamic_generation

# Enable dynamic generation (disabled by default)
enable_dynamic_generation()

template = {
    "Resources": {
        "MyFunction": {
            "Type": "AWS::Lambda::Function",  # Not explicitly modeled
            "Properties": {
                "FunctionName": "my-function",
                "Runtime": "python3.12",
                "Role": "arn:aws:iam::123456789012:role/lambda-role",
                "Code": {"ZipFile": "def handler(event, context): pass"},
            },
        }
    }
}

model = parse(template)
# MyFunction is now a typed Resource, not GenericResource!
assert model.Resources["MyFunction"].Properties.FunctionName == "my-function"

Available functions:

  • enable_dynamic_generation() - Enable dynamic generation
  • disable_dynamic_generation() - Disable dynamic generation (default)
  • is_dynamic_generation_enabled() - Check current state
  • clear_dynamic_model_cache() - Clear cached dynamically generated models

Intrinsic Function Validation

All CloudFormation intrinsic functions are now validated at parse time:

Function Validation
Ref Must be a non-empty string
Fn::Sub String or [string, dict] format
Fn::GetAtt [resource, attr] list or "resource.attr" string
Fn::Join [delimiter, list] with string delimiter
Fn::Select [index, list] format
Fn::Split [delimiter, string] with string delimiter
Fn::If [condition, true_value, false_value] - exactly 3 elements
Fn::And / Fn::Or List of 2-10 conditions
Fn::Not List with exactly 1 condition
Fn::Equals List with exactly 2 values
Fn::FindInMap [map, key1, key2] - exactly 3 elements
Fn::Base64 String or function
Fn::GetAZs String or function
Fn::ImportValue String or function
Condition Non-empty string

Manual Dynamic Resource Generation

  • Resource attributes: DependsOn, Condition, DeletionPolicy, UpdateReplacePolicy, Metadata
  • Optional properties, arrays, and nested objects
from pycfmodel.schema_generator import generate_resource_from_schema

DynamicLambdaFunction = generate_resource_from_schema("AWS::Lambda::Function")
function = DynamicLambdaFunction(
    Type="AWS::Lambda::Function",
    Properties={"FunctionName": "my-function", "Runtime": "python3.12", ...}
)

Malformed Template Detection

Invalid intrinsic functions are now rejected at parse time:

# This will raise a ValidationError
bucket = S3Bucket(
    Type="AWS::S3::Bucket",
    Properties={"BucketName": {"Ref": ["invalid", "list"]}}  # Ref must be a string
)
# ValidationError: Ref value must be a string, got list

Test plan

  • Unit tests for schema retrieval
  • Tests for all CloudFormation intrinsic functions validation (71 tests)
  • Tests for CloudFormation conditions
  • Tests for resource metadata attributes
  • Integration tests for multiple resource types (S3, Lambda, DynamoDB, SNS, SQS, EC2)
  • Full ECS+ALB CloudFormation template test with 11 resource types
  • Dynamic resource generation during parsing tests (16 tests)
    • Toggle enable/disable functionality
    • Unmodeled resources become typed resources when enabled
    • Explicitly modeled resources still use explicit models
    • Mixed templates with both explicit and dynamic resources
    • Intrinsic functions work with dynamic resources
    • Conditions and DependsOn work with dynamic resources
    • Unknown resource types fall back to GenericResource
    • Model caching tests

Total: 780 tests

🤖 Generated with Claude Code

This adds a schema_generator module that can dynamically create pycfmodel
Resource classes from AWS CloudFormation resource provider schemas.

Features:
- Downloads and caches CloudFormation schemas from AWS
- Generates Pydantic models with proper type hints from JSON schemas
- Supports all CloudFormation intrinsic functions (Ref, Fn::Sub, Fn::GetAtt,
  Fn::Join, Fn::Select, Fn::Split, Fn::If, Fn::ImportValue, Fn::FindInMap,
  Fn::Base64, Fn::GetAZs)
- Supports CloudFormation conditions (Fn::And, Fn::Or, Fn::Not, Fn::Equals)
- Handles all resource attributes (DependsOn, Condition, DeletionPolicy,
  UpdateReplacePolicy, Metadata)
- Handles optional properties, arrays, and nested objects
- Filters out read-only properties automatically

Usage:
  from pycfmodel.schema_generator import generate_resource_from_schema

  DynamicLambdaFunction = generate_resource_from_schema("AWS::Lambda::Function")
  function = DynamicLambdaFunction(
      Type="AWS::Lambda::Function",
      Properties={"FunctionName": "my-function", ...}
  )

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 2, 2026 17:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces dynamic resource generation from AWS CloudFormation schemas, enabling automatic creation of pycfmodel Resource classes without manual definition. The implementation downloads and caches CloudFormation schemas from AWS, generates Pydantic models with proper type hints, and automatically filters read-only properties.

Changes:

  • Adds schema download and caching functionality from AWS CloudFormation schema registry
  • Implements dynamic Pydantic model generation with type mapping from JSON schemas
  • Provides comprehensive test coverage for CloudFormation intrinsic functions, conditions, and resource attributes

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
pycfmodel/schema_generator/init.py Exports the public API for schema-based resource generation
pycfmodel/schema_generator/generator.py Core implementation for schema downloading, parsing, and dynamic model creation
tests/schema_generator/test_generator.py Comprehensive test suite covering intrinsic functions, conditions, metadata, and resource generation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jsoucheiron Jordi Soucheiron (jsoucheiron) marked this pull request as draft February 2, 2026 17:33
Adds a test suite that validates parsing a realistic CloudFormation
template containing:
- ECS Cluster with container insights
- ECS Task Definition with Fargate configuration
- ECS Service with load balancer integration
- Application Load Balancer with listener and target group
- Security groups for ALB and ECS tasks
- IAM roles for task execution and task
- CloudWatch Log Group
- Application Auto Scaling (conditional on environment)

The tests verify:
- All 11 resource types can be dynamically generated and parsed
- Complex nested properties (NetworkConfiguration, ContainerDefinitions)
- All CloudFormation intrinsic functions (Ref, Fn::Sub, Fn::GetAtt,
  Fn::Join, Fn::If)
- Conditional resources and properties
- Resource attributes (DependsOn, Condition)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This adds comprehensive validation for all CloudFormation intrinsic
functions when parsing templates. The FunctionDict class now validates
the format of each intrinsic function according to AWS documentation.

Validated functions:
- Ref: must be a non-empty string
- Fn::Sub: string or [string, dict] format
- Fn::GetAtt: [resource, attr] list or "resource.attr" string
- Fn::Join: [delimiter, list] with string delimiter
- Fn::Select: [index, list] format
- Fn::Split: [delimiter, string] with string delimiter
- Fn::If: [condition, true_value, false_value] - exactly 3 elements
- Fn::And/Fn::Or: list of 2-10 conditions
- Fn::Not: list with exactly 1 condition
- Fn::Equals: list with exactly 2 values
- Fn::FindInMap: [map, key1, key2] - exactly 3 elements
- Fn::Base64: string or function
- Fn::GetAZs: string or function
- Fn::ImportValue: string or function
- Condition: non-empty string

This helps detect malformed CloudFormation templates at parse time
rather than at deployment time.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Caching is already handled by @lru_cache decorator on _download_schemas.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When enabled via enable_dynamic_generation(), pycfmodel will now dynamically
generate typed Pydantic models for CloudFormation resource types that are not
explicitly modeled in the library. This uses the AWS CloudFormation schema
registry to create proper models instead of falling back to GenericResource.

Features:
- enable_dynamic_generation() / disable_dynamic_generation() to toggle the feature
- is_dynamic_generation_enabled() to check current state
- clear_dynamic_model_cache() to clear cached dynamically generated models
- All functions exported from pycfmodel package

The feature is opt-in and disabled by default to maintain backward compatibility.
When disabled, unmodeled resources continue to use GenericResource as before.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds 4 realistic CloudFormation YAML templates as test fixtures:
- s3_static_website.yaml: S3 bucket with website hosting and bucket policy
- lambda_api_gateway.yaml: Lambda function with API Gateway, DynamoDB, IAM roles
- ecs_fargate_service.yaml: ECS Fargate service with ALB, auto-scaling
- vpc_network.yaml: VPC with subnets, NAT Gateway, VPC endpoints, flow logs

Tests verify:
- Template metadata (version, description) is correctly parsed
- Parameters with defaults, allowed values, constraints
- Conditions (Fn::Equals, Fn::And, Fn::Not) are preserved
- Mappings are correctly parsed
- Resources are parsed (with and without dynamic generation)
- Intrinsic functions (Ref, Fn::Sub, Fn::GetAtt, Fn::Join, etc.) are preserved
- Resource attributes (DependsOn, Condition) work correctly
- Outputs with exports are parsed
- Template resolution with parameter substitution

Includes custom YAML loader to handle CloudFormation intrinsic function tags.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
… schemas

This script creates permanent Python resource files that can be integrated
into the pycfmodel codebase, as an alternative to dynamic generation at runtime.

Usage:
    python scripts/generate_resource_from_schema.py AWS::Lambda::Function
    python scripts/generate_resource_from_schema.py AWS::Lambda::Function --output-dir pycfmodel/model/resources
    python scripts/generate_resource_from_schema.py --list-types

Features:
- Downloads CloudFormation schemas from AWS (cached during execution)
- Generates Properties class with proper type annotations (ResolvableStr, etc.)
- Generates Resource class inheriting from Resource base class
- Follows existing pycfmodel conventions (naming, structure, docstrings)
- Handles optional vs required properties correctly
- Supports multiple resource types in a single invocation
- --dry-run option to preview generated code
- --list-types option to list all available AWS resource types

The generated code is compatible with existing pycfmodel patterns and can be
used directly in templates, supporting intrinsic functions like Ref, Fn::Sub, etc.

Tests verify:
- Generated code compiles without errors
- Generated classes inherit from Resource correctly
- Generated classes can be instantiated and work with intrinsic functions
- Generated code matches pycfmodel conventions
- CLI options work correctly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
if type_name:
schemas[type_name] = schema

_schemas_cache = schemas

Check notice

Code scanning / CodeQL

Unused global variable Note

The global variable '_schemas_cache' is not used.

Copilot Autofix

AI 2 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants