Decentralize cloudformation naming responsibilities (#3201)

* #3127 - Decentralize CF naming responsibilities

* Decentralize CloudFormation naming responsibilities

* Update URLs in cloudformation_resource_type functions

* Fix flake8 errors

* Black formatting

* Add a bunch of imports to populate CloudFormationModel.__subclasses__

* Add noqa to s3 models import statement in cloudformation/parsing.py

* Black formatting

* Remove debugging print statement

Co-authored-by: Bert Blommers <info@bertblommers.nl>
This commit is contained in:
Adam Richie-Halford 2020-08-01 07:23:36 -07:00 committed by GitHub
parent 88a1134657
commit 9a9a1d8413
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 717 additions and 208 deletions

View File

@ -6,7 +6,7 @@ from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
from moto.ec2.exceptions import InvalidInstanceIdError
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.ec2 import ec2_backends
from moto.elb import elb_backends
from moto.elbv2 import elbv2_backends
@ -74,7 +74,7 @@ class FakeScalingPolicy(BaseModel):
)
class FakeLaunchConfiguration(BaseModel):
class FakeLaunchConfiguration(CloudFormationModel):
def __init__(
self,
name,
@ -127,6 +127,15 @@ class FakeLaunchConfiguration(BaseModel):
)
return config
@staticmethod
def cloudformation_name_type():
return "LaunchConfigurationName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-launchconfiguration.html
return "AWS::AutoScaling::LaunchConfiguration"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -215,7 +224,7 @@ class FakeLaunchConfiguration(BaseModel):
return block_device_map
class FakeAutoScalingGroup(BaseModel):
class FakeAutoScalingGroup(CloudFormationModel):
def __init__(
self,
name,
@ -309,6 +318,15 @@ class FakeAutoScalingGroup(BaseModel):
tag["PropagateAtLaunch"] = bool_to_string[tag["PropagateAtLaunch"]]
return tags
@staticmethod
def cloudformation_name_type():
return "AutoScalingGroupName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-autoscalinggroup.html
return "AWS::AutoScaling::AutoScalingGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -28,7 +28,7 @@ import requests.adapters
from boto3 import Session
from moto.awslambda.policy import Policy
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, CloudFormationModel
from moto.core.exceptions import RESTError
from moto.iam.models import iam_backend
from moto.iam.exceptions import IAMNotFoundException
@ -151,7 +151,7 @@ class _DockerDataVolumeContext:
raise # multiple processes trying to use same volume?
class LambdaFunction(BaseModel):
class LambdaFunction(CloudFormationModel):
def __init__(self, spec, region, validate_s3=True, version=1):
# required
self.region = region
@ -492,6 +492,15 @@ class LambdaFunction(BaseModel):
return result
@staticmethod
def cloudformation_name_type():
return "FunctionName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html
return "AWS::Lambda::Function"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -556,7 +565,7 @@ class LambdaFunction(BaseModel):
lambda_backends[region].delete_function(self.function_name)
class EventSourceMapping(BaseModel):
class EventSourceMapping(CloudFormationModel):
def __init__(self, spec):
# required
self.function_name = spec["FunctionName"]
@ -633,6 +642,15 @@ class EventSourceMapping(BaseModel):
lambda_backend = lambda_backends[region_name]
lambda_backend.delete_event_source_mapping(self.uuid)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html
return "AWS::Lambda::EventSourceMapping"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -667,13 +685,22 @@ class EventSourceMapping(BaseModel):
esm.delete(region_name)
class LambdaVersion(BaseModel):
class LambdaVersion(CloudFormationModel):
def __init__(self, spec):
self.version = spec["Version"]
def __repr__(self):
return str(self.logical_resource_id)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-version.html
return "AWS::Lambda::Version"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -13,7 +13,7 @@ import threading
import dateutil.parser
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.iam import iam_backends
from moto.ec2 import ec2_backends
from moto.ecs import ecs_backends
@ -42,7 +42,7 @@ def datetime2int(date):
return int(time.mktime(date.timetuple()))
class ComputeEnvironment(BaseModel):
class ComputeEnvironment(CloudFormationModel):
def __init__(
self,
compute_environment_name,
@ -76,6 +76,15 @@ class ComputeEnvironment(BaseModel):
def physical_resource_id(self):
return self.arn
@staticmethod
def cloudformation_name_type():
return "ComputeEnvironmentName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-computeenvironment.html
return "AWS::Batch::ComputeEnvironment"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -95,7 +104,7 @@ class ComputeEnvironment(BaseModel):
return backend.get_compute_environment_by_arn(arn)
class JobQueue(BaseModel):
class JobQueue(CloudFormationModel):
def __init__(
self, name, priority, state, environments, env_order_json, region_name
):
@ -139,6 +148,15 @@ class JobQueue(BaseModel):
def physical_resource_id(self):
return self.arn
@staticmethod
def cloudformation_name_type():
return "JobQueueName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobqueue.html
return "AWS::Batch::JobQueue"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -164,7 +182,7 @@ class JobQueue(BaseModel):
return backend.get_job_queue_by_arn(arn)
class JobDefinition(BaseModel):
class JobDefinition(CloudFormationModel):
def __init__(
self,
name,
@ -264,6 +282,15 @@ class JobDefinition(BaseModel):
def physical_resource_id(self):
return self.arn
@staticmethod
def cloudformation_name_type():
return "JobDefinitionName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobdefinition.html
return "AWS::Batch::JobDefinition"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -6,31 +6,43 @@ import copy
import warnings
import re
from moto.autoscaling import models as autoscaling_models
from moto.awslambda import models as lambda_models
from moto.batch import models as batch_models
from moto.cloudwatch import models as cloudwatch_models
from moto.cognitoidentity import models as cognitoidentity_models
from moto.compat import collections_abc
from moto.datapipeline import models as datapipeline_models
from moto.dynamodb2 import models as dynamodb2_models
# This ugly section of imports is necessary because we
# build the list of CloudFormationModel subclasses using
# CloudFormationModel.__subclasses__(). However, if the class
# definition of a subclass hasn't been executed yet - for example, if
# the subclass's module hasn't been imported yet - then that subclass
# doesn't exist yet, and __subclasses__ won't find it.
# So we import here to populate the list of subclasses.
from moto.autoscaling import models as autoscaling_models # noqa
from moto.awslambda import models as awslambda_models # noqa
from moto.batch import models as batch_models # noqa
from moto.cloudwatch import models as cloudwatch_models # noqa
from moto.datapipeline import models as datapipeline_models # noqa
from moto.dynamodb2 import models as dynamodb2_models # noqa
from moto.ecr import models as ecr_models # noqa
from moto.ecs import models as ecs_models # noqa
from moto.elb import models as elb_models # noqa
from moto.elbv2 import models as elbv2_models # noqa
from moto.events import models as events_models # noqa
from moto.iam import models as iam_models # noqa
from moto.kinesis import models as kinesis_models # noqa
from moto.kms import models as kms_models # noqa
from moto.rds import models as rds_models # noqa
from moto.rds2 import models as rds2_models # noqa
from moto.redshift import models as redshift_models # noqa
from moto.route53 import models as route53_models # noqa
from moto.s3 import models as s3_models # noqa
from moto.sns import models as sns_models # noqa
from moto.sqs import models as sqs_models # noqa
# End ugly list of imports
from moto.ec2 import models as ec2_models
from moto.ecs import models as ecs_models
from moto.elb import models as elb_models
from moto.elbv2 import models as elbv2_models
from moto.events import models as events_models
from moto.iam import models as iam_models
from moto.kinesis import models as kinesis_models
from moto.kms import models as kms_models
from moto.rds import models as rds_models
from moto.rds2 import models as rds2_models
from moto.redshift import models as redshift_models
from moto.route53 import models as route53_models
from moto.s3 import models as s3_models, s3_backend
from moto.s3 import models as _, s3_backend # noqa
from moto.s3.utils import bucket_and_name_from_url
from moto.sns import models as sns_models
from moto.sqs import models as sqs_models
from moto.core import ACCOUNT_ID
from moto.core import ACCOUNT_ID, CloudFormationModel
from .utils import random_suffix
from .exceptions import (
ExportNotFound,
@ -40,105 +52,13 @@ from .exceptions import (
)
from boto.cloudformation.stack import Output
MODEL_MAP = {
"AWS::AutoScaling::AutoScalingGroup": autoscaling_models.FakeAutoScalingGroup,
"AWS::AutoScaling::LaunchConfiguration": autoscaling_models.FakeLaunchConfiguration,
"AWS::Batch::JobDefinition": batch_models.JobDefinition,
"AWS::Batch::JobQueue": batch_models.JobQueue,
"AWS::Batch::ComputeEnvironment": batch_models.ComputeEnvironment,
"AWS::DynamoDB::Table": dynamodb2_models.Table,
"AWS::Kinesis::Stream": kinesis_models.Stream,
"AWS::Lambda::EventSourceMapping": lambda_models.EventSourceMapping,
"AWS::Lambda::Function": lambda_models.LambdaFunction,
"AWS::Lambda::Version": lambda_models.LambdaVersion,
"AWS::EC2::EIP": ec2_models.ElasticAddress,
"AWS::EC2::Instance": ec2_models.Instance,
"AWS::EC2::InternetGateway": ec2_models.InternetGateway,
"AWS::EC2::NatGateway": ec2_models.NatGateway,
"AWS::EC2::NetworkInterface": ec2_models.NetworkInterface,
"AWS::EC2::Route": ec2_models.Route,
"AWS::EC2::RouteTable": ec2_models.RouteTable,
"AWS::EC2::SecurityGroup": ec2_models.SecurityGroup,
"AWS::EC2::SecurityGroupIngress": ec2_models.SecurityGroupIngress,
"AWS::EC2::SpotFleet": ec2_models.SpotFleetRequest,
"AWS::EC2::Subnet": ec2_models.Subnet,
"AWS::EC2::SubnetRouteTableAssociation": ec2_models.SubnetRouteTableAssociation,
"AWS::EC2::Volume": ec2_models.Volume,
"AWS::EC2::VolumeAttachment": ec2_models.VolumeAttachment,
"AWS::EC2::VPC": ec2_models.VPC,
"AWS::EC2::VPCGatewayAttachment": ec2_models.VPCGatewayAttachment,
"AWS::EC2::VPCPeeringConnection": ec2_models.VPCPeeringConnection,
"AWS::ECS::Cluster": ecs_models.Cluster,
"AWS::ECS::TaskDefinition": ecs_models.TaskDefinition,
"AWS::ECS::Service": ecs_models.Service,
"AWS::ElasticLoadBalancing::LoadBalancer": elb_models.FakeLoadBalancer,
"AWS::ElasticLoadBalancingV2::LoadBalancer": elbv2_models.FakeLoadBalancer,
"AWS::ElasticLoadBalancingV2::TargetGroup": elbv2_models.FakeTargetGroup,
"AWS::ElasticLoadBalancingV2::Listener": elbv2_models.FakeListener,
"AWS::Cognito::IdentityPool": cognitoidentity_models.CognitoIdentity,
"AWS::DataPipeline::Pipeline": datapipeline_models.Pipeline,
"AWS::IAM::InstanceProfile": iam_models.InstanceProfile,
"AWS::IAM::Role": iam_models.Role,
"AWS::KMS::Key": kms_models.Key,
"AWS::Logs::LogGroup": cloudwatch_models.LogGroup,
"AWS::RDS::DBInstance": rds_models.Database,
"AWS::RDS::DBSecurityGroup": rds_models.SecurityGroup,
"AWS::RDS::DBSubnetGroup": rds_models.SubnetGroup,
"AWS::RDS::DBParameterGroup": rds2_models.DBParameterGroup,
"AWS::Redshift::Cluster": redshift_models.Cluster,
"AWS::Redshift::ClusterParameterGroup": redshift_models.ParameterGroup,
"AWS::Redshift::ClusterSubnetGroup": redshift_models.SubnetGroup,
"AWS::Route53::HealthCheck": route53_models.HealthCheck,
"AWS::Route53::HostedZone": route53_models.FakeZone,
"AWS::Route53::RecordSet": route53_models.RecordSet,
"AWS::Route53::RecordSetGroup": route53_models.RecordSetGroup,
"AWS::SNS::Topic": sns_models.Topic,
"AWS::S3::Bucket": s3_models.FakeBucket,
"AWS::SQS::Queue": sqs_models.Queue,
"AWS::Events::Rule": events_models.Rule,
"AWS::Events::EventBus": events_models.EventBus,
}
UNDOCUMENTED_NAME_TYPE_MAP = {
"AWS::AutoScaling::AutoScalingGroup": "AutoScalingGroupName",
"AWS::AutoScaling::LaunchConfiguration": "LaunchConfigurationName",
"AWS::IAM::InstanceProfile": "InstanceProfileName",
}
# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html
# List of supported CloudFormation models
MODEL_LIST = CloudFormationModel.__subclasses__()
MODEL_MAP = {model.cloudformation_type(): model for model in MODEL_LIST}
NAME_TYPE_MAP = {
"AWS::ApiGateway::ApiKey": "Name",
"AWS::ApiGateway::Model": "Name",
"AWS::CloudWatch::Alarm": "AlarmName",
"AWS::DynamoDB::Table": "TableName",
"AWS::ElasticBeanstalk::Application": "ApplicationName",
"AWS::ElasticBeanstalk::Environment": "EnvironmentName",
"AWS::CodeDeploy::Application": "ApplicationName",
"AWS::CodeDeploy::DeploymentConfig": "DeploymentConfigName",
"AWS::CodeDeploy::DeploymentGroup": "DeploymentGroupName",
"AWS::Config::ConfigRule": "ConfigRuleName",
"AWS::Config::DeliveryChannel": "Name",
"AWS::Config::ConfigurationRecorder": "Name",
"AWS::ElasticLoadBalancing::LoadBalancer": "LoadBalancerName",
"AWS::ElasticLoadBalancingV2::LoadBalancer": "Name",
"AWS::ElasticLoadBalancingV2::TargetGroup": "Name",
"AWS::EC2::SecurityGroup": "GroupName",
"AWS::ElastiCache::CacheCluster": "ClusterName",
"AWS::ECR::Repository": "RepositoryName",
"AWS::ECS::Cluster": "ClusterName",
"AWS::Elasticsearch::Domain": "DomainName",
"AWS::Events::Rule": "Name",
"AWS::IAM::Group": "GroupName",
"AWS::IAM::ManagedPolicy": "ManagedPolicyName",
"AWS::IAM::Role": "RoleName",
"AWS::IAM::User": "UserName",
"AWS::Lambda::Function": "FunctionName",
"AWS::RDS::DBInstance": "DBInstanceIdentifier",
"AWS::S3::Bucket": "BucketName",
"AWS::SNS::Topic": "TopicName",
"AWS::SQS::Queue": "QueueName",
model.cloudformation_type(): model.cloudformation_name_type()
for model in MODEL_LIST
}
NAME_TYPE_MAP.update(UNDOCUMENTED_NAME_TYPE_MAP)
# Just ignore these models types for now
NULL_MODELS = [
@ -292,9 +212,11 @@ def clean_json(resource_json, resources_map):
def resource_class_from_type(resource_type):
if resource_type in NULL_MODELS:
return None
if resource_type not in MODEL_MAP:
logger.warning("No Moto CloudFormation support for %s", resource_type)
return None
return MODEL_MAP.get(resource_type)

View File

@ -3,7 +3,7 @@ import json
from boto3 import Session
from moto.core.utils import iso_8601_datetime_without_milliseconds
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.exceptions import RESTError
from moto.logs import logs_backends
from datetime import datetime, timedelta
@ -490,13 +490,22 @@ class CloudWatchBackend(BaseBackend):
return None, metrics
class LogGroup(BaseModel):
class LogGroup(CloudFormationModel):
def __init__(self, spec):
# required
self.name = spec["LogGroupName"]
# optional
self.tags = spec.get("Tags", [])
@staticmethod
def cloudformation_name_type():
return "LogGroupName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html
return "AWS::Logs::LogGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from .models import BaseModel, BaseBackend, moto_api_backend, ACCOUNT_ID # noqa
from .models import CloudFormationModel # noqa
from .responses import ActionAuthenticatorMixin
moto_api_backends = {"global": moto_api_backend}

View File

@ -8,6 +8,7 @@ import os
import re
import six
import types
from abc import abstractmethod
from io import BytesIO
from collections import defaultdict
from botocore.config import Config
@ -534,6 +535,47 @@ class BaseModel(object):
return instance
# Parent class for every Model that can be instantiated by CloudFormation
# On subclasses, implement the two methods as @staticmethod to ensure correct behaviour of the CF parser
class CloudFormationModel(BaseModel):
@abstractmethod
def cloudformation_name_type(self):
# This must be implemented as a staticmethod with no parameters
# Return None for resources that do not have a name property
pass
@abstractmethod
def cloudformation_type(self):
# This must be implemented as a staticmethod with no parameters
# See for example https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
return "AWS::SERVICE::RESOURCE"
@abstractmethod
def create_from_cloudformation_json(self):
# This must be implemented as a classmethod with parameters:
# cls, resource_name, cloudformation_json, region_name
# Extract the resource parameters from the cloudformation json
# and return an instance of the resource class
pass
@abstractmethod
def update_from_cloudformation_json(self):
# This must be implemented as a classmethod with parameters:
# cls, original_resource, new_resource_name, cloudformation_json, region_name
# Extract the resource parameters from the cloudformation json,
# delete the old resource and return the new one. Optionally inspect
# the change in parameters and no-op when nothing has changed.
pass
@abstractmethod
def delete_from_cloudformation_json(self):
# This must be implemented as a classmethod with parameters:
# cls, resource_name, cloudformation_json, region_name
# Extract the resource parameters from the cloudformation json
# and delete the resource. Do not include a return statement.
pass
class BaseBackend(object):
def _reset_model_refs(self):
# Remove all references to the models stored

View File

@ -4,7 +4,7 @@ import datetime
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys
@ -18,7 +18,7 @@ class PipelineObject(BaseModel):
return {"fields": self.fields, "id": self.object_id, "name": self.name}
class Pipeline(BaseModel):
class Pipeline(CloudFormationModel):
def __init__(self, name, unique_id, **kwargs):
self.name = name
self.unique_id = unique_id
@ -74,6 +74,15 @@ class Pipeline(BaseModel):
def activate(self):
self.status = "SCHEDULED"
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datapipeline-pipeline.html
return "AWS::DataPipeline::Pipeline"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -4,7 +4,7 @@ import datetime
import json
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import unix_time
from moto.core import ACCOUNT_ID
from .comparisons import get_comparison_func
@ -82,7 +82,7 @@ class Item(BaseModel):
return {"Item": included}
class Table(BaseModel):
class Table(CloudFormationModel):
def __init__(
self,
name,
@ -135,6 +135,15 @@ class Table(BaseModel):
}
return results
@staticmethod
def cloudformation_name_type():
return "TableName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
return "AWS::DynamoDB::Table"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -9,7 +9,7 @@ import uuid
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import unix_time
from moto.core.exceptions import JsonRESTError
from moto.dynamodb2.comparisons import get_filter_expression
@ -359,7 +359,7 @@ class GlobalSecondaryIndex(SecondaryIndex):
self.throughput = u.get("ProvisionedThroughput", self.throughput)
class Table(BaseModel):
class Table(CloudFormationModel):
def __init__(
self,
table_name,
@ -431,6 +431,15 @@ class Table(BaseModel):
def physical_resource_id(self):
return self.name
@staticmethod
def cloudformation_name_type():
return "TableName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
return "AWS::DynamoDB::Table"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -22,7 +22,7 @@ from boto.ec2.launchspecification import LaunchSpecification
from moto.compat import OrderedDict
from moto.core import BaseBackend
from moto.core.models import Model, BaseModel
from moto.core.models import Model, BaseModel, CloudFormationModel
from moto.core.utils import (
iso_8601_datetime_with_milliseconds,
camelcase_to_underscores,
@ -219,7 +219,7 @@ class TaggedEC2Resource(BaseModel):
raise FilterNotImplementedError(filter_name, method_name)
class NetworkInterface(TaggedEC2Resource):
class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
def __init__(
self,
ec2_backend,
@ -268,6 +268,15 @@ class NetworkInterface(TaggedEC2Resource):
if group:
self._group_set.append(group)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html
return "AWS::EC2::NetworkInterface"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -454,7 +463,7 @@ class NetworkInterfaceBackend(object):
return generic_filter(filters, enis)
class Instance(TaggedEC2Resource, BotoInstance):
class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
VALID_ATTRIBUTES = {
"instanceType",
"kernel",
@ -621,6 +630,15 @@ class Instance(TaggedEC2Resource, BotoInstance):
formatted_ip, self.region_name
)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-instance.html
return "AWS::EC2::Instance"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -1843,7 +1861,7 @@ class SecurityRule(object):
return True
class SecurityGroup(TaggedEC2Resource):
class SecurityGroup(TaggedEC2Resource, CloudFormationModel):
def __init__(self, ec2_backend, group_id, name, description, vpc_id=None):
self.ec2_backend = ec2_backend
self.id = group_id
@ -1861,6 +1879,15 @@ class SecurityGroup(TaggedEC2Resource):
if vpc and len(vpc.get_cidr_block_association_set(ipv6=True)) > 0:
self.egress_rules.append(SecurityRule("-1", None, None, [], []))
@staticmethod
def cloudformation_name_type():
return "GroupName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-securitygroup.html
return "AWS::EC2::SecurityGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -2260,11 +2287,20 @@ class SecurityGroupBackend(object):
raise RulesPerSecurityGroupLimitExceededError
class SecurityGroupIngress(object):
class SecurityGroupIngress(CloudFormationModel):
def __init__(self, security_group, properties):
self.security_group = security_group
self.properties = properties
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-securitygroupingress.html
return "AWS::EC2::SecurityGroupIngress"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -2328,7 +2364,7 @@ class SecurityGroupIngress(object):
return cls(security_group, properties)
class VolumeAttachment(object):
class VolumeAttachment(CloudFormationModel):
def __init__(self, volume, instance, device, status):
self.volume = volume
self.attach_time = utc_date_and_time()
@ -2336,6 +2372,15 @@ class VolumeAttachment(object):
self.device = device
self.status = status
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volumeattachment.html
return "AWS::EC2::VolumeAttachment"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -2354,7 +2399,7 @@ class VolumeAttachment(object):
return attachment
class Volume(TaggedEC2Resource):
class Volume(TaggedEC2Resource, CloudFormationModel):
def __init__(
self, ec2_backend, volume_id, size, zone, snapshot_id=None, encrypted=False
):
@ -2367,6 +2412,15 @@ class Volume(TaggedEC2Resource):
self.ec2_backend = ec2_backend
self.encrypted = encrypted
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html
return "AWS::EC2::Volume"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -2623,7 +2677,7 @@ class EBSBackend(object):
return True
class VPC(TaggedEC2Resource):
class VPC(TaggedEC2Resource, CloudFormationModel):
def __init__(
self,
ec2_backend,
@ -2656,6 +2710,15 @@ class VPC(TaggedEC2Resource):
amazon_provided_ipv6_cidr_block=amazon_provided_ipv6_cidr_block,
)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html
return "AWS::EC2::VPC"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3022,13 +3085,22 @@ class VPCPeeringConnectionStatus(object):
self.message = "Inactive"
class VPCPeeringConnection(TaggedEC2Resource):
class VPCPeeringConnection(TaggedEC2Resource, CloudFormationModel):
def __init__(self, vpc_pcx_id, vpc, peer_vpc):
self.id = vpc_pcx_id
self.vpc = vpc
self.peer_vpc = peer_vpc
self._status = VPCPeeringConnectionStatus()
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcpeeringconnection.html
return "AWS::EC2::VPCPeeringConnection"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3114,7 +3186,7 @@ class VPCPeeringConnectionBackend(object):
return vpc_pcx
class Subnet(TaggedEC2Resource):
class Subnet(TaggedEC2Resource, CloudFormationModel):
def __init__(
self,
ec2_backend,
@ -3150,6 +3222,15 @@ class Subnet(TaggedEC2Resource):
self._unused_ips = set() # if instance is destroyed hold IP here for reuse
self._subnet_ips = {} # has IP: instance
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html
return "AWS::EC2::Subnet"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3377,11 +3458,20 @@ class SubnetBackend(object):
raise InvalidParameterValueError(attr_name)
class SubnetRouteTableAssociation(object):
class SubnetRouteTableAssociation(CloudFormationModel):
def __init__(self, route_table_id, subnet_id):
self.route_table_id = route_table_id
self.subnet_id = subnet_id
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnetroutetableassociation.html
return "AWS::EC2::SubnetRouteTableAssociation"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3411,7 +3501,7 @@ class SubnetRouteTableAssociationBackend(object):
return subnet_association
class RouteTable(TaggedEC2Resource):
class RouteTable(TaggedEC2Resource, CloudFormationModel):
def __init__(self, ec2_backend, route_table_id, vpc_id, main=False):
self.ec2_backend = ec2_backend
self.id = route_table_id
@ -3420,6 +3510,15 @@ class RouteTable(TaggedEC2Resource):
self.associations = {}
self.routes = {}
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html
return "AWS::EC2::RouteTable"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3555,7 +3654,7 @@ class RouteTableBackend(object):
return self.associate_route_table(route_table_id, subnet_id)
class Route(object):
class Route(CloudFormationModel):
def __init__(
self,
route_table,
@ -3581,6 +3680,15 @@ class Route(object):
self.interface = interface
self.vpc_pcx = vpc_pcx
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html
return "AWS::EC2::Route"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3748,12 +3856,21 @@ class RouteBackend(object):
return deleted
class InternetGateway(TaggedEC2Resource):
class InternetGateway(TaggedEC2Resource, CloudFormationModel):
def __init__(self, ec2_backend):
self.ec2_backend = ec2_backend
self.id = random_internet_gateway_id()
self.vpc = None
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html
return "AWS::EC2::InternetGateway"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -3826,11 +3943,20 @@ class InternetGatewayBackend(object):
return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0]
class VPCGatewayAttachment(BaseModel):
class VPCGatewayAttachment(CloudFormationModel):
def __init__(self, gateway_id, vpc_id):
self.gateway_id = gateway_id
self.vpc_id = vpc_id
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcgatewayattachment.html
return "AWS::EC2::VPCGatewayAttachment"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -4051,7 +4177,7 @@ class SpotFleetLaunchSpec(object):
self.weighted_capacity = float(weighted_capacity)
class SpotFleetRequest(TaggedEC2Resource):
class SpotFleetRequest(TaggedEC2Resource, CloudFormationModel):
def __init__(
self,
ec2_backend,
@ -4100,6 +4226,15 @@ class SpotFleetRequest(TaggedEC2Resource):
def physical_resource_id(self):
return self.id
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-spotfleet.html
return "AWS::EC2::SpotFleet"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -4323,7 +4458,7 @@ class SpotFleetBackend(object):
return True
class ElasticAddress(object):
class ElasticAddress(CloudFormationModel):
def __init__(self, domain, address=None):
if address:
self.public_ip = address
@ -4335,6 +4470,15 @@ class ElasticAddress(object):
self.eni = None
self.association_id = None
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-eip.html
return "AWS::EC2::EIP"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -5095,7 +5239,7 @@ class CustomerGatewayBackend(object):
return deleted
class NatGateway(object):
class NatGateway(CloudFormationModel):
def __init__(self, backend, subnet_id, allocation_id):
# public properties
self.id = random_nat_gateway_id()
@ -5133,6 +5277,15 @@ class NatGateway(object):
eips = self._backend.address_by_allocation([self.allocation_id])
return eips[0].public_ip
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html
return "AWS::EC2::NatGateway"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -7,7 +7,7 @@ from random import random
from botocore.exceptions import ParamValidationError
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.ec2 import ec2_backends
from moto.ecr.exceptions import ImageNotFoundException, RepositoryNotFoundException
@ -38,7 +38,7 @@ class BaseObject(BaseModel):
return self.gen_response_object()
class Repository(BaseObject):
class Repository(BaseObject, CloudFormationModel):
def __init__(self, repository_name):
self.registry_id = DEFAULT_REGISTRY_ID
self.arn = "arn:aws:ecr:us-east-1:{0}:repository/{1}".format(
@ -67,6 +67,15 @@ class Repository(BaseObject):
del response_object["arn"], response_object["name"], response_object["images"]
return response_object
@staticmethod
def cloudformation_name_type():
return "RepositoryName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html
return "AWS::ECR::Repository"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -8,7 +8,7 @@ import pytz
from boto3 import Session
from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import unix_time
from moto.ec2 import ec2_backends
from copy import copy
@ -44,7 +44,7 @@ class BaseObject(BaseModel):
return self.gen_response_object()
class Cluster(BaseObject):
class Cluster(BaseObject, CloudFormationModel):
def __init__(self, cluster_name, region_name):
self.active_services_count = 0
self.arn = "arn:aws:ecs:{0}:012345678910:cluster/{1}".format(
@ -69,6 +69,15 @@ class Cluster(BaseObject):
del response_object["arn"], response_object["name"]
return response_object
@staticmethod
def cloudformation_name_type():
return "ClusterName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html
return "AWS::ECS::Cluster"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -116,7 +125,7 @@ class Cluster(BaseObject):
raise UnformattedGetAttTemplateException()
class TaskDefinition(BaseObject):
class TaskDefinition(BaseObject, CloudFormationModel):
def __init__(
self,
family,
@ -159,6 +168,15 @@ class TaskDefinition(BaseObject):
def physical_resource_id(self):
return self.arn
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html
return "AWS::ECS::TaskDefinition"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -235,7 +253,7 @@ class Task(BaseObject):
return response_object
class Service(BaseObject):
class Service(BaseObject, CloudFormationModel):
def __init__(
self,
cluster,
@ -315,6 +333,15 @@ class Service(BaseObject):
return response_object
@staticmethod
def cloudformation_name_type():
return "ServiceName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html
return "AWS::ECS::Service"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -13,7 +13,7 @@ from boto.ec2.elb.attributes import (
)
from boto.ec2.elb.policies import Policies, OtherPolicy
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.ec2.models import ec2_backends
from .exceptions import (
BadHealthCheckDefinition,
@ -69,7 +69,7 @@ class FakeBackend(BaseModel):
)
class FakeLoadBalancer(BaseModel):
class FakeLoadBalancer(CloudFormationModel):
def __init__(
self,
name,
@ -119,6 +119,15 @@ class FakeLoadBalancer(BaseModel):
)
self.backends.append(backend)
@staticmethod
def cloudformation_name_type():
return "LoadBalancerName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancing-loadbalancer.html
return "AWS::ElasticLoadBalancing::LoadBalancer"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -6,7 +6,7 @@ from jinja2 import Template
from botocore.exceptions import ParamValidationError
from moto.compat import OrderedDict
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import camelcase_to_underscores, underscores_to_camelcase
from moto.ec2.models import ec2_backends
from moto.acm.models import acm_backends
@ -50,7 +50,7 @@ class FakeHealthStatus(BaseModel):
self.description = description
class FakeTargetGroup(BaseModel):
class FakeTargetGroup(CloudFormationModel):
HTTP_CODE_REGEX = re.compile(r"(?:(?:\d+-\d+|\d+),?)+")
def __init__(
@ -143,6 +143,15 @@ class FakeTargetGroup(BaseModel):
)
return FakeHealthStatus(t["id"], t["port"], self.healthcheck_port, "healthy")
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-targetgroup.html
return "AWS::ElasticLoadBalancingV2::TargetGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -183,7 +192,7 @@ class FakeTargetGroup(BaseModel):
return target_group
class FakeListener(BaseModel):
class FakeListener(CloudFormationModel):
def __init__(
self,
load_balancer_arn,
@ -228,6 +237,15 @@ class FakeListener(BaseModel):
self._non_default_rules, key=lambda x: x.priority
)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listener.html
return "AWS::ElasticLoadBalancingV2::Listener"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -343,7 +361,7 @@ class FakeBackend(BaseModel):
)
class FakeLoadBalancer(BaseModel):
class FakeLoadBalancer(CloudFormationModel):
VALID_ATTRS = {
"access_logs.s3.enabled",
"access_logs.s3.bucket",
@ -402,6 +420,15 @@ class FakeLoadBalancer(BaseModel):
""" Not exposed as part of the ELB API - used for CloudFormation. """
elbv2_backends[region].delete_load_balancer(self.arn)
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-loadbalancer.html
return "AWS::ElasticLoadBalancingV2::LoadBalancer"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -4,14 +4,14 @@ import json
from boto3 import Session
from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, CloudFormationModel
from moto.sts.models import ACCOUNT_ID
from moto.utilities.tagging_service import TaggingService
from uuid import uuid4
class Rule(BaseModel):
class Rule(CloudFormationModel):
def _generate_arn(self, name):
return "arn:aws:events:{region_name}:111111111111:rule/{name}".format(
region_name=self.region_name, name=name
@ -73,6 +73,15 @@ class Rule(BaseModel):
raise UnformattedGetAttTemplateException()
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html
return "AWS::Events::Rule"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -101,7 +110,7 @@ class Rule(BaseModel):
event_backend.delete_rule(name=event_name)
class EventBus(BaseModel):
class EventBus(CloudFormationModel):
def __init__(self, region_name, name):
self.region = region_name
self.name = name
@ -152,6 +161,15 @@ class EventBus(BaseModel):
raise UnformattedGetAttTemplateException()
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-eventbus.html
return "AWS::Events::EventBus"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -15,7 +15,7 @@ from six.moves.urllib.parse import urlparse
from uuid import uuid4
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID, CloudFormationModel
from moto.core.utils import (
iso_8601_datetime_without_milliseconds,
iso_8601_datetime_with_milliseconds,
@ -299,7 +299,7 @@ class InlinePolicy(Policy):
"""TODO: is this needed?"""
class Role(BaseModel):
class Role(CloudFormationModel):
def __init__(
self,
role_id,
@ -327,6 +327,15 @@ class Role(BaseModel):
def created_iso_8601(self):
return iso_8601_datetime_with_milliseconds(self.create_date)
@staticmethod
def cloudformation_name_type():
return "RoleName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html
return "AWS::IAM::Role"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -384,7 +393,7 @@ class Role(BaseModel):
return [self.tags[tag] for tag in self.tags]
class InstanceProfile(BaseModel):
class InstanceProfile(CloudFormationModel):
def __init__(self, instance_profile_id, name, path, roles):
self.id = instance_profile_id
self.name = name
@ -396,6 +405,15 @@ class InstanceProfile(BaseModel):
def created_iso_8601(self):
return iso_8601_datetime_with_milliseconds(self.create_date)
@staticmethod
def cloudformation_name_type():
return "InstanceProfileName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html
return "AWS::IAM::InstanceProfile"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -12,7 +12,7 @@ from hashlib import md5
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import unix_time
from moto.core import ACCOUNT_ID
from .exceptions import (
@ -129,7 +129,7 @@ class Shard(BaseModel):
}
class Stream(BaseModel):
class Stream(CloudFormationModel):
def __init__(self, stream_name, shard_count, region):
self.stream_name = stream_name
self.shard_count = shard_count
@ -216,6 +216,15 @@ class Stream(BaseModel):
}
}
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesis-stream.html
return "AWS::Kinesis::Stream"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -6,7 +6,7 @@ from datetime import datetime, timedelta
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, CloudFormationModel
from moto.core.utils import unix_time
from moto.utilities.tagging_service import TaggingService
from moto.core.exceptions import JsonRESTError
@ -15,7 +15,7 @@ from moto.iam.models import ACCOUNT_ID
from .utils import decrypt, encrypt, generate_key_id, generate_master_key
class Key(BaseModel):
class Key(CloudFormationModel):
def __init__(
self, policy, key_usage, customer_master_key_spec, description, region
):
@ -99,6 +99,15 @@ class Key(BaseModel):
def delete(self, region_name):
kms_backends[region_name].delete_key(self.id)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kms-key.html
return "AWS::KMS::Key"
@classmethod
def create_from_cloudformation_json(
self, resource_name, cloudformation_json, region_name

View File

@ -3,14 +3,14 @@ from __future__ import unicode_literals
import boto.rds
from jinja2 import Template
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, CloudFormationModel
from moto.core.utils import get_random_hex
from moto.ec2.models import ec2_backends
from moto.rds.exceptions import UnformattedGetAttTemplateException
from moto.rds2.models import rds2_backends
class Database(BaseModel):
class Database(CloudFormationModel):
def get_cfn_attribute(self, attribute_name):
if attribute_name == "Endpoint.Address":
return self.address
@ -18,13 +18,22 @@ class Database(BaseModel):
return self.port
raise UnformattedGetAttTemplateException()
@staticmethod
def cloudformation_name_type():
return "DBInstanceIdentifier"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbinstance.html
return "AWS::RDS::DBInstance"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
):
properties = cloudformation_json["Properties"]
db_instance_identifier = properties.get("DBInstanceIdentifier")
db_instance_identifier = properties.get(cls.cloudformation_name_type())
if not db_instance_identifier:
db_instance_identifier = resource_name.lower() + get_random_hex(12)
db_security_groups = properties.get("DBSecurityGroups")
@ -163,7 +172,7 @@ class Database(BaseModel):
backend.delete_database(self.db_instance_identifier)
class SecurityGroup(BaseModel):
class SecurityGroup(CloudFormationModel):
def __init__(self, group_name, description):
self.group_name = group_name
self.description = description
@ -206,6 +215,15 @@ class SecurityGroup(BaseModel):
def authorize_security_group(self, security_group):
self.ec2_security_groups.append(security_group)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbsecuritygroup.html
return "AWS::RDS::DBSecurityGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -239,7 +257,7 @@ class SecurityGroup(BaseModel):
backend.delete_security_group(self.group_name)
class SubnetGroup(BaseModel):
class SubnetGroup(CloudFormationModel):
def __init__(self, subnet_name, description, subnets):
self.subnet_name = subnet_name
self.description = description
@ -271,13 +289,23 @@ class SubnetGroup(BaseModel):
)
return template.render(subnet_group=self)
@staticmethod
def cloudformation_name_type():
return "DBSubnetGroupName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbsubnetgroup.html
return "AWS::RDS::DBSubnetGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
):
properties = cloudformation_json["Properties"]
subnet_name = resource_name.lower() + get_random_hex(12)
subnet_name = properties.get(cls.cloudformation_name_type())
if not subnet_name:
subnet_name = resource_name.lower() + get_random_hex(12)
description = properties["DBSubnetGroupDescription"]
subnet_ids = properties["SubnetIds"]
tags = properties.get("Tags")

View File

@ -9,7 +9,7 @@ from boto3 import Session
from jinja2 import Template
from re import compile as re_compile
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import get_random_hex
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.ec2.models import ec2_backends
@ -28,7 +28,7 @@ from .exceptions import (
)
class Database(BaseModel):
class Database(CloudFormationModel):
def __init__(self, **kwargs):
self.status = "available"
self.is_replica = False
@ -356,13 +356,22 @@ class Database(BaseModel):
"sqlserver-web": {"gp2": 20, "io1": 100, "standard": 20},
}[engine][storage_type]
@staticmethod
def cloudformation_name_type():
return "DBInstanceIdentifier"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbinstance.html
return "AWS::RDS::DBInstance"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
):
properties = cloudformation_json["Properties"]
db_instance_identifier = properties.get("DBInstanceIdentifier")
db_instance_identifier = properties.get(cls.cloudformation_name_type())
if not db_instance_identifier:
db_instance_identifier = resource_name.lower() + get_random_hex(12)
db_security_groups = properties.get("DBSecurityGroups")
@ -564,7 +573,7 @@ class Snapshot(BaseModel):
self.tags = [tag_set for tag_set in self.tags if tag_set["Key"] not in tag_keys]
class SecurityGroup(BaseModel):
class SecurityGroup(CloudFormationModel):
def __init__(self, group_name, description, tags):
self.group_name = group_name
self.description = description
@ -627,6 +636,15 @@ class SecurityGroup(BaseModel):
def authorize_security_group(self, security_group):
self.ec2_security_groups.append(security_group)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbsecuritygroup.html
return "AWS::RDS::DBSecurityGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -671,7 +689,7 @@ class SecurityGroup(BaseModel):
backend.delete_security_group(self.group_name)
class SubnetGroup(BaseModel):
class SubnetGroup(CloudFormationModel):
def __init__(self, subnet_name, description, subnets, tags):
self.subnet_name = subnet_name
self.description = description
@ -726,13 +744,24 @@ class SubnetGroup(BaseModel):
)
return template.render(subnet_group=self)
@staticmethod
def cloudformation_name_type():
return "DBSubnetGroupName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbsubnetgroup.html
return "AWS::RDS::DBSubnetGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
):
properties = cloudformation_json["Properties"]
subnet_name = resource_name.lower() + get_random_hex(12)
subnet_name = properties.get(cls.cloudformation_name_type())
if not subnet_name:
subnet_name = resource_name.lower() + get_random_hex(12)
description = properties["DBSubnetGroupDescription"]
subnet_ids = properties["SubnetIds"]
tags = properties.get("Tags")
@ -1441,7 +1470,7 @@ class OptionGroupOptionSetting(object):
return template.render(option_group_option_setting=self)
class DBParameterGroup(object):
class DBParameterGroup(CloudFormationModel):
def __init__(self, name, description, family, tags):
self.name = name
self.description = description
@ -1480,6 +1509,15 @@ class DBParameterGroup(object):
backend = rds2_backends[region_name]
backend.delete_db_parameter_group(self.name)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbparametergroup.html
return "AWS::RDS::DBParameterGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -6,7 +6,7 @@ import datetime
from boto3 import Session
from botocore.exceptions import ClientError
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.ec2 import ec2_backends
from .exceptions import (
@ -63,7 +63,7 @@ class TaggableResourceMixin(object):
return self.tags
class Cluster(TaggableResourceMixin, BaseModel):
class Cluster(TaggableResourceMixin, CloudFormationModel):
resource_type = "cluster"
@ -157,6 +157,15 @@ class Cluster(TaggableResourceMixin, BaseModel):
self.iam_roles_arn = iam_roles_arn or []
self.restored_from_snapshot = restored_from_snapshot
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-cluster.html
return "AWS::Redshift::Cluster"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -170,6 +179,7 @@ class Cluster(TaggableResourceMixin, BaseModel):
].cluster_subnet_group_name
else:
subnet_group_name = None
cluster = redshift_backend.create_cluster(
cluster_identifier=resource_name,
node_type=properties.get("NodeType"),
@ -321,7 +331,7 @@ class SnapshotCopyGrant(TaggableResourceMixin, BaseModel):
}
class SubnetGroup(TaggableResourceMixin, BaseModel):
class SubnetGroup(TaggableResourceMixin, CloudFormationModel):
resource_type = "subnetgroup"
@ -342,6 +352,15 @@ class SubnetGroup(TaggableResourceMixin, BaseModel):
if not self.subnets:
raise InvalidSubnetError(subnet_ids)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clustersubnetgroup.html
return "AWS::Redshift::ClusterSubnetGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -412,7 +431,7 @@ class SecurityGroup(TaggableResourceMixin, BaseModel):
}
class ParameterGroup(TaggableResourceMixin, BaseModel):
class ParameterGroup(TaggableResourceMixin, CloudFormationModel):
resource_type = "parametergroup"
@ -429,6 +448,15 @@ class ParameterGroup(TaggableResourceMixin, BaseModel):
self.group_family = group_family
self.description = description
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clusterparametergroup.html
return "AWS::Redshift::ClusterParameterGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -7,7 +7,7 @@ import random
import uuid
from jinja2 import Template
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, CloudFormationModel
ROUTE53_ID_CHOICE = string.ascii_uppercase + string.digits
@ -18,7 +18,7 @@ def create_route53_zone_id():
return "".join([random.choice(ROUTE53_ID_CHOICE) for _ in range(0, 15)])
class HealthCheck(BaseModel):
class HealthCheck(CloudFormationModel):
def __init__(self, health_check_id, health_check_args):
self.id = health_check_id
self.ip_address = health_check_args.get("ip_address")
@ -34,6 +34,15 @@ class HealthCheck(BaseModel):
def physical_resource_id(self):
return self.id
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-healthcheck.html
return "AWS::Route53::HealthCheck"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -75,7 +84,7 @@ class HealthCheck(BaseModel):
return template.render(health_check=self)
class RecordSet(BaseModel):
class RecordSet(CloudFormationModel):
def __init__(self, kwargs):
self.name = kwargs.get("Name")
self.type_ = kwargs.get("Type")
@ -91,6 +100,15 @@ class RecordSet(BaseModel):
self.failover = kwargs.get("Failover")
self.geo_location = kwargs.get("GeoLocation")
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-recordset.html
return "AWS::Route53::RecordSet"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -202,7 +220,7 @@ def reverse_domain_name(domain_name):
return ".".join(reversed(domain_name.split(".")))
class FakeZone(BaseModel):
class FakeZone(CloudFormationModel):
def __init__(self, name, id_, private_zone, comment=None):
self.name = name
self.id = id_
@ -267,6 +285,15 @@ class FakeZone(BaseModel):
def physical_resource_id(self):
return self.id
@staticmethod
def cloudformation_name_type():
return "Name"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html
return "AWS::Route53::HostedZone"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -278,7 +305,7 @@ class FakeZone(BaseModel):
return hosted_zone
class RecordSetGroup(BaseModel):
class RecordSetGroup(CloudFormationModel):
def __init__(self, hosted_zone_id, record_sets):
self.hosted_zone_id = hosted_zone_id
self.record_sets = record_sets
@ -287,6 +314,15 @@ class RecordSetGroup(BaseModel):
def physical_resource_id(self):
return "arn:aws:route53:::hostedzone/{0}".format(self.hosted_zone_id)
@staticmethod
def cloudformation_name_type():
return None
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-recordsetgroup.html
return "AWS::Route53::RecordSetGroup"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -21,7 +21,7 @@ import uuid
import six
from bisect import insort
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import iso_8601_datetime_without_milliseconds_s3, rfc_1123_datetime
from moto.cloudwatch.models import MetricDatum
from moto.utilities.tagging_service import TaggingService
@ -763,7 +763,7 @@ class PublicAccessBlock(BaseModel):
}
class FakeBucket(BaseModel):
class FakeBucket(CloudFormationModel):
def __init__(self, name, region_name):
self.name = name
self.region_name = region_name
@ -1070,6 +1070,15 @@ class FakeBucket(BaseModel):
def physical_resource_id(self):
return self.name
@staticmethod
def cloudformation_name_type():
return "BucketName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucket.html
return "AWS::S3::Bucket"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name

View File

@ -11,7 +11,7 @@ import re
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import (
iso_8601_datetime_with_milliseconds,
camelcase_to_underscores,
@ -37,7 +37,7 @@ DEFAULT_PAGE_SIZE = 100
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
class Topic(BaseModel):
class Topic(CloudFormationModel):
def __init__(self, name, sns_backend):
self.name = name
self.sns_backend = sns_backend
@ -87,6 +87,15 @@ class Topic(BaseModel):
def policy(self, policy):
self._policy_json = json.loads(policy)
@staticmethod
def cloudformation_name_type():
return "TopicName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-topic.html
return "AWS::SNS::Topic"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
@ -94,7 +103,7 @@ class Topic(BaseModel):
sns_backend = sns_backends[region_name]
properties = cloudformation_json["Properties"]
topic = sns_backend.create_topic(properties.get("TopicName"))
topic = sns_backend.create_topic(properties.get(cls.cloudformation_name_type()))
for subscription in properties.get("Subscription", []):
sns_backend.subscribe(
topic.arn, subscription["Endpoint"], subscription["Protocol"]

View File

@ -12,7 +12,7 @@ from xml.sax.saxutils import escape
from boto3 import Session
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import (
camelcase_to_underscores,
get_random_message_id,
@ -188,7 +188,7 @@ class Message(BaseModel):
return False
class Queue(BaseModel):
class Queue(CloudFormationModel):
BASE_ATTRIBUTES = [
"ApproximateNumberOfMessages",
"ApproximateNumberOfMessagesDelayed",
@ -354,6 +354,15 @@ class Queue(BaseModel):
),
)
@staticmethod
def cloudformation_name_type():
return "QueueName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sqs-queue.html
return "AWS::SQS::Queue"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name