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.ec2.exceptions import InvalidInstanceIdError
from moto.compat import OrderedDict 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.ec2 import ec2_backends
from moto.elb import elb_backends from moto.elb import elb_backends
from moto.elbv2 import elbv2_backends from moto.elbv2 import elbv2_backends
@ -74,7 +74,7 @@ class FakeScalingPolicy(BaseModel):
) )
class FakeLaunchConfiguration(BaseModel): class FakeLaunchConfiguration(CloudFormationModel):
def __init__( def __init__(
self, self,
name, name,
@ -127,6 +127,15 @@ class FakeLaunchConfiguration(BaseModel):
) )
return config 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -215,7 +224,7 @@ class FakeLaunchConfiguration(BaseModel):
return block_device_map return block_device_map
class FakeAutoScalingGroup(BaseModel): class FakeAutoScalingGroup(CloudFormationModel):
def __init__( def __init__(
self, self,
name, name,
@ -309,6 +318,15 @@ class FakeAutoScalingGroup(BaseModel):
tag["PropagateAtLaunch"] = bool_to_string[tag["PropagateAtLaunch"]] tag["PropagateAtLaunch"] = bool_to_string[tag["PropagateAtLaunch"]]
return tags 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -28,7 +28,7 @@ import requests.adapters
from boto3 import Session from boto3 import Session
from moto.awslambda.policy import Policy 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.core.exceptions import RESTError
from moto.iam.models import iam_backend from moto.iam.models import iam_backend
from moto.iam.exceptions import IAMNotFoundException from moto.iam.exceptions import IAMNotFoundException
@ -151,7 +151,7 @@ class _DockerDataVolumeContext:
raise # multiple processes trying to use same volume? raise # multiple processes trying to use same volume?
class LambdaFunction(BaseModel): class LambdaFunction(CloudFormationModel):
def __init__(self, spec, region, validate_s3=True, version=1): def __init__(self, spec, region, validate_s3=True, version=1):
# required # required
self.region = region self.region = region
@ -492,6 +492,15 @@ class LambdaFunction(BaseModel):
return result 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -556,7 +565,7 @@ class LambdaFunction(BaseModel):
lambda_backends[region].delete_function(self.function_name) lambda_backends[region].delete_function(self.function_name)
class EventSourceMapping(BaseModel): class EventSourceMapping(CloudFormationModel):
def __init__(self, spec): def __init__(self, spec):
# required # required
self.function_name = spec["FunctionName"] self.function_name = spec["FunctionName"]
@ -633,6 +642,15 @@ class EventSourceMapping(BaseModel):
lambda_backend = lambda_backends[region_name] lambda_backend = lambda_backends[region_name]
lambda_backend.delete_event_source_mapping(self.uuid) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -667,13 +685,22 @@ class EventSourceMapping(BaseModel):
esm.delete(region_name) esm.delete(region_name)
class LambdaVersion(BaseModel): class LambdaVersion(CloudFormationModel):
def __init__(self, spec): def __init__(self, spec):
self.version = spec["Version"] self.version = spec["Version"]
def __repr__(self): def __repr__(self):
return str(self.logical_resource_id) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -13,7 +13,7 @@ import threading
import dateutil.parser import dateutil.parser
from boto3 import Session 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.iam import iam_backends
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from moto.ecs import ecs_backends from moto.ecs import ecs_backends
@ -42,7 +42,7 @@ def datetime2int(date):
return int(time.mktime(date.timetuple())) return int(time.mktime(date.timetuple()))
class ComputeEnvironment(BaseModel): class ComputeEnvironment(CloudFormationModel):
def __init__( def __init__(
self, self,
compute_environment_name, compute_environment_name,
@ -76,6 +76,15 @@ class ComputeEnvironment(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.arn 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -95,7 +104,7 @@ class ComputeEnvironment(BaseModel):
return backend.get_compute_environment_by_arn(arn) return backend.get_compute_environment_by_arn(arn)
class JobQueue(BaseModel): class JobQueue(CloudFormationModel):
def __init__( def __init__(
self, name, priority, state, environments, env_order_json, region_name self, name, priority, state, environments, env_order_json, region_name
): ):
@ -139,6 +148,15 @@ class JobQueue(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.arn 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -164,7 +182,7 @@ class JobQueue(BaseModel):
return backend.get_job_queue_by_arn(arn) return backend.get_job_queue_by_arn(arn)
class JobDefinition(BaseModel): class JobDefinition(CloudFormationModel):
def __init__( def __init__(
self, self,
name, name,
@ -264,6 +282,15 @@ class JobDefinition(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.arn 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -6,31 +6,43 @@ import copy
import warnings import warnings
import re 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.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.ec2 import models as ec2_models
from moto.ecs import models as ecs_models from moto.s3 import models as _, s3_backend # noqa
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.utils import bucket_and_name_from_url from moto.s3.utils import bucket_and_name_from_url
from moto.sns import models as sns_models from moto.core import ACCOUNT_ID, CloudFormationModel
from moto.sqs import models as sqs_models
from moto.core import ACCOUNT_ID
from .utils import random_suffix from .utils import random_suffix
from .exceptions import ( from .exceptions import (
ExportNotFound, ExportNotFound,
@ -40,105 +52,13 @@ from .exceptions import (
) )
from boto.cloudformation.stack import Output from boto.cloudformation.stack import Output
MODEL_MAP = { # List of supported CloudFormation models
"AWS::AutoScaling::AutoScalingGroup": autoscaling_models.FakeAutoScalingGroup, MODEL_LIST = CloudFormationModel.__subclasses__()
"AWS::AutoScaling::LaunchConfiguration": autoscaling_models.FakeLaunchConfiguration, MODEL_MAP = {model.cloudformation_type(): model for model in MODEL_LIST}
"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
NAME_TYPE_MAP = { NAME_TYPE_MAP = {
"AWS::ApiGateway::ApiKey": "Name", model.cloudformation_type(): model.cloudformation_name_type()
"AWS::ApiGateway::Model": "Name", for model in MODEL_LIST
"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",
} }
NAME_TYPE_MAP.update(UNDOCUMENTED_NAME_TYPE_MAP)
# Just ignore these models types for now # Just ignore these models types for now
NULL_MODELS = [ NULL_MODELS = [
@ -292,9 +212,11 @@ def clean_json(resource_json, resources_map):
def resource_class_from_type(resource_type): def resource_class_from_type(resource_type):
if resource_type in NULL_MODELS: if resource_type in NULL_MODELS:
return None return None
if resource_type not in MODEL_MAP: if resource_type not in MODEL_MAP:
logger.warning("No Moto CloudFormation support for %s", resource_type) logger.warning("No Moto CloudFormation support for %s", resource_type)
return None return None
return MODEL_MAP.get(resource_type) return MODEL_MAP.get(resource_type)

View File

@ -3,7 +3,7 @@ import json
from boto3 import Session from boto3 import Session
from moto.core.utils import iso_8601_datetime_without_milliseconds 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.core.exceptions import RESTError
from moto.logs import logs_backends from moto.logs import logs_backends
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -490,13 +490,22 @@ class CloudWatchBackend(BaseBackend):
return None, metrics return None, metrics
class LogGroup(BaseModel): class LogGroup(CloudFormationModel):
def __init__(self, spec): def __init__(self, spec):
# required # required
self.name = spec["LogGroupName"] self.name = spec["LogGroupName"]
# optional # optional
self.tags = spec.get("Tags", []) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

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

View File

@ -8,6 +8,7 @@ import os
import re import re
import six import six
import types import types
from abc import abstractmethod
from io import BytesIO from io import BytesIO
from collections import defaultdict from collections import defaultdict
from botocore.config import Config from botocore.config import Config
@ -534,6 +535,47 @@ class BaseModel(object):
return instance 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): class BaseBackend(object):
def _reset_model_refs(self): def _reset_model_refs(self):
# Remove all references to the models stored # Remove all references to the models stored

View File

@ -4,7 +4,7 @@ import datetime
from boto3 import Session from boto3 import Session
from moto.compat import OrderedDict 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 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} return {"fields": self.fields, "id": self.object_id, "name": self.name}
class Pipeline(BaseModel): class Pipeline(CloudFormationModel):
def __init__(self, name, unique_id, **kwargs): def __init__(self, name, unique_id, **kwargs):
self.name = name self.name = name
self.unique_id = unique_id self.unique_id = unique_id
@ -74,6 +74,15 @@ class Pipeline(BaseModel):
def activate(self): def activate(self):
self.status = "SCHEDULED" 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -4,7 +4,7 @@ import datetime
import json import json
from moto.compat import OrderedDict 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.utils import unix_time
from moto.core import ACCOUNT_ID from moto.core import ACCOUNT_ID
from .comparisons import get_comparison_func from .comparisons import get_comparison_func
@ -82,7 +82,7 @@ class Item(BaseModel):
return {"Item": included} return {"Item": included}
class Table(BaseModel): class Table(CloudFormationModel):
def __init__( def __init__(
self, self,
name, name,
@ -135,6 +135,15 @@ class Table(BaseModel):
} }
return results 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -9,7 +9,7 @@ import uuid
from boto3 import Session from boto3 import Session
from moto.compat import OrderedDict 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.utils import unix_time
from moto.core.exceptions import JsonRESTError from moto.core.exceptions import JsonRESTError
from moto.dynamodb2.comparisons import get_filter_expression from moto.dynamodb2.comparisons import get_filter_expression
@ -359,7 +359,7 @@ class GlobalSecondaryIndex(SecondaryIndex):
self.throughput = u.get("ProvisionedThroughput", self.throughput) self.throughput = u.get("ProvisionedThroughput", self.throughput)
class Table(BaseModel): class Table(CloudFormationModel):
def __init__( def __init__(
self, self,
table_name, table_name,
@ -431,6 +431,15 @@ class Table(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.name 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name 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.compat import OrderedDict
from moto.core import BaseBackend 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 ( from moto.core.utils import (
iso_8601_datetime_with_milliseconds, iso_8601_datetime_with_milliseconds,
camelcase_to_underscores, camelcase_to_underscores,
@ -219,7 +219,7 @@ class TaggedEC2Resource(BaseModel):
raise FilterNotImplementedError(filter_name, method_name) raise FilterNotImplementedError(filter_name, method_name)
class NetworkInterface(TaggedEC2Resource): class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
def __init__( def __init__(
self, self,
ec2_backend, ec2_backend,
@ -268,6 +268,15 @@ class NetworkInterface(TaggedEC2Resource):
if group: if group:
self._group_set.append(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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -454,7 +463,7 @@ class NetworkInterfaceBackend(object):
return generic_filter(filters, enis) return generic_filter(filters, enis)
class Instance(TaggedEC2Resource, BotoInstance): class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel):
VALID_ATTRIBUTES = { VALID_ATTRIBUTES = {
"instanceType", "instanceType",
"kernel", "kernel",
@ -621,6 +630,15 @@ class Instance(TaggedEC2Resource, BotoInstance):
formatted_ip, self.region_name 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -1843,7 +1861,7 @@ class SecurityRule(object):
return True return True
class SecurityGroup(TaggedEC2Resource): class SecurityGroup(TaggedEC2Resource, CloudFormationModel):
def __init__(self, ec2_backend, group_id, name, description, vpc_id=None): def __init__(self, ec2_backend, group_id, name, description, vpc_id=None):
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
self.id = group_id self.id = group_id
@ -1861,6 +1879,15 @@ class SecurityGroup(TaggedEC2Resource):
if vpc and len(vpc.get_cidr_block_association_set(ipv6=True)) > 0: if vpc and len(vpc.get_cidr_block_association_set(ipv6=True)) > 0:
self.egress_rules.append(SecurityRule("-1", None, None, [], [])) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -2260,11 +2287,20 @@ class SecurityGroupBackend(object):
raise RulesPerSecurityGroupLimitExceededError raise RulesPerSecurityGroupLimitExceededError
class SecurityGroupIngress(object): class SecurityGroupIngress(CloudFormationModel):
def __init__(self, security_group, properties): def __init__(self, security_group, properties):
self.security_group = security_group self.security_group = security_group
self.properties = properties 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -2328,7 +2364,7 @@ class SecurityGroupIngress(object):
return cls(security_group, properties) return cls(security_group, properties)
class VolumeAttachment(object): class VolumeAttachment(CloudFormationModel):
def __init__(self, volume, instance, device, status): def __init__(self, volume, instance, device, status):
self.volume = volume self.volume = volume
self.attach_time = utc_date_and_time() self.attach_time = utc_date_and_time()
@ -2336,6 +2372,15 @@ class VolumeAttachment(object):
self.device = device self.device = device
self.status = status 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -2354,7 +2399,7 @@ class VolumeAttachment(object):
return attachment return attachment
class Volume(TaggedEC2Resource): class Volume(TaggedEC2Resource, CloudFormationModel):
def __init__( def __init__(
self, ec2_backend, volume_id, size, zone, snapshot_id=None, encrypted=False 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.ec2_backend = ec2_backend
self.encrypted = encrypted 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -2623,7 +2677,7 @@ class EBSBackend(object):
return True return True
class VPC(TaggedEC2Resource): class VPC(TaggedEC2Resource, CloudFormationModel):
def __init__( def __init__(
self, self,
ec2_backend, ec2_backend,
@ -2656,6 +2710,15 @@ class VPC(TaggedEC2Resource):
amazon_provided_ipv6_cidr_block=amazon_provided_ipv6_cidr_block, 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -3022,13 +3085,22 @@ class VPCPeeringConnectionStatus(object):
self.message = "Inactive" self.message = "Inactive"
class VPCPeeringConnection(TaggedEC2Resource): class VPCPeeringConnection(TaggedEC2Resource, CloudFormationModel):
def __init__(self, vpc_pcx_id, vpc, peer_vpc): def __init__(self, vpc_pcx_id, vpc, peer_vpc):
self.id = vpc_pcx_id self.id = vpc_pcx_id
self.vpc = vpc self.vpc = vpc
self.peer_vpc = peer_vpc self.peer_vpc = peer_vpc
self._status = VPCPeeringConnectionStatus() 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -3114,7 +3186,7 @@ class VPCPeeringConnectionBackend(object):
return vpc_pcx return vpc_pcx
class Subnet(TaggedEC2Resource): class Subnet(TaggedEC2Resource, CloudFormationModel):
def __init__( def __init__(
self, self,
ec2_backend, ec2_backend,
@ -3150,6 +3222,15 @@ class Subnet(TaggedEC2Resource):
self._unused_ips = set() # if instance is destroyed hold IP here for reuse self._unused_ips = set() # if instance is destroyed hold IP here for reuse
self._subnet_ips = {} # has IP: instance 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -3377,11 +3458,20 @@ class SubnetBackend(object):
raise InvalidParameterValueError(attr_name) raise InvalidParameterValueError(attr_name)
class SubnetRouteTableAssociation(object): class SubnetRouteTableAssociation(CloudFormationModel):
def __init__(self, route_table_id, subnet_id): def __init__(self, route_table_id, subnet_id):
self.route_table_id = route_table_id self.route_table_id = route_table_id
self.subnet_id = subnet_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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -3411,7 +3501,7 @@ class SubnetRouteTableAssociationBackend(object):
return subnet_association return subnet_association
class RouteTable(TaggedEC2Resource): class RouteTable(TaggedEC2Resource, CloudFormationModel):
def __init__(self, ec2_backend, route_table_id, vpc_id, main=False): def __init__(self, ec2_backend, route_table_id, vpc_id, main=False):
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
self.id = route_table_id self.id = route_table_id
@ -3420,6 +3510,15 @@ class RouteTable(TaggedEC2Resource):
self.associations = {} self.associations = {}
self.routes = {} 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -3555,7 +3654,7 @@ class RouteTableBackend(object):
return self.associate_route_table(route_table_id, subnet_id) return self.associate_route_table(route_table_id, subnet_id)
class Route(object): class Route(CloudFormationModel):
def __init__( def __init__(
self, self,
route_table, route_table,
@ -3581,6 +3680,15 @@ class Route(object):
self.interface = interface self.interface = interface
self.vpc_pcx = vpc_pcx 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -3748,12 +3856,21 @@ class RouteBackend(object):
return deleted return deleted
class InternetGateway(TaggedEC2Resource): class InternetGateway(TaggedEC2Resource, CloudFormationModel):
def __init__(self, ec2_backend): def __init__(self, ec2_backend):
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
self.id = random_internet_gateway_id() self.id = random_internet_gateway_id()
self.vpc = None 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name 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] return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0]
class VPCGatewayAttachment(BaseModel): class VPCGatewayAttachment(CloudFormationModel):
def __init__(self, gateway_id, vpc_id): def __init__(self, gateway_id, vpc_id):
self.gateway_id = gateway_id self.gateway_id = gateway_id
self.vpc_id = vpc_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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -4051,7 +4177,7 @@ class SpotFleetLaunchSpec(object):
self.weighted_capacity = float(weighted_capacity) self.weighted_capacity = float(weighted_capacity)
class SpotFleetRequest(TaggedEC2Resource): class SpotFleetRequest(TaggedEC2Resource, CloudFormationModel):
def __init__( def __init__(
self, self,
ec2_backend, ec2_backend,
@ -4100,6 +4226,15 @@ class SpotFleetRequest(TaggedEC2Resource):
def physical_resource_id(self): def physical_resource_id(self):
return self.id 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -4323,7 +4458,7 @@ class SpotFleetBackend(object):
return True return True
class ElasticAddress(object): class ElasticAddress(CloudFormationModel):
def __init__(self, domain, address=None): def __init__(self, domain, address=None):
if address: if address:
self.public_ip = address self.public_ip = address
@ -4335,6 +4470,15 @@ class ElasticAddress(object):
self.eni = None self.eni = None
self.association_id = 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -5095,7 +5239,7 @@ class CustomerGatewayBackend(object):
return deleted return deleted
class NatGateway(object): class NatGateway(CloudFormationModel):
def __init__(self, backend, subnet_id, allocation_id): def __init__(self, backend, subnet_id, allocation_id):
# public properties # public properties
self.id = random_nat_gateway_id() self.id = random_nat_gateway_id()
@ -5133,6 +5277,15 @@ class NatGateway(object):
eips = self._backend.address_by_allocation([self.allocation_id]) eips = self._backend.address_by_allocation([self.allocation_id])
return eips[0].public_ip 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -7,7 +7,7 @@ from random import random
from botocore.exceptions import ParamValidationError 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.ec2 import ec2_backends
from moto.ecr.exceptions import ImageNotFoundException, RepositoryNotFoundException from moto.ecr.exceptions import ImageNotFoundException, RepositoryNotFoundException
@ -38,7 +38,7 @@ class BaseObject(BaseModel):
return self.gen_response_object() return self.gen_response_object()
class Repository(BaseObject): class Repository(BaseObject, CloudFormationModel):
def __init__(self, repository_name): def __init__(self, repository_name):
self.registry_id = DEFAULT_REGISTRY_ID self.registry_id = DEFAULT_REGISTRY_ID
self.arn = "arn:aws:ecr:us-east-1:{0}:repository/{1}".format( 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"] del response_object["arn"], response_object["name"], response_object["images"]
return response_object 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -8,7 +8,7 @@ import pytz
from boto3 import Session from boto3 import Session
from moto.core.exceptions import JsonRESTError 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.core.utils import unix_time
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from copy import copy from copy import copy
@ -44,7 +44,7 @@ class BaseObject(BaseModel):
return self.gen_response_object() return self.gen_response_object()
class Cluster(BaseObject): class Cluster(BaseObject, CloudFormationModel):
def __init__(self, cluster_name, region_name): def __init__(self, cluster_name, region_name):
self.active_services_count = 0 self.active_services_count = 0
self.arn = "arn:aws:ecs:{0}:012345678910:cluster/{1}".format( self.arn = "arn:aws:ecs:{0}:012345678910:cluster/{1}".format(
@ -69,6 +69,15 @@ class Cluster(BaseObject):
del response_object["arn"], response_object["name"] del response_object["arn"], response_object["name"]
return response_object 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -116,7 +125,7 @@ class Cluster(BaseObject):
raise UnformattedGetAttTemplateException() raise UnformattedGetAttTemplateException()
class TaskDefinition(BaseObject): class TaskDefinition(BaseObject, CloudFormationModel):
def __init__( def __init__(
self, self,
family, family,
@ -159,6 +168,15 @@ class TaskDefinition(BaseObject):
def physical_resource_id(self): def physical_resource_id(self):
return self.arn 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -235,7 +253,7 @@ class Task(BaseObject):
return response_object return response_object
class Service(BaseObject): class Service(BaseObject, CloudFormationModel):
def __init__( def __init__(
self, self,
cluster, cluster,
@ -315,6 +333,15 @@ class Service(BaseObject):
return response_object 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name 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 boto.ec2.elb.policies import Policies, OtherPolicy
from moto.compat import OrderedDict 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 moto.ec2.models import ec2_backends
from .exceptions import ( from .exceptions import (
BadHealthCheckDefinition, BadHealthCheckDefinition,
@ -69,7 +69,7 @@ class FakeBackend(BaseModel):
) )
class FakeLoadBalancer(BaseModel): class FakeLoadBalancer(CloudFormationModel):
def __init__( def __init__(
self, self,
name, name,
@ -119,6 +119,15 @@ class FakeLoadBalancer(BaseModel):
) )
self.backends.append(backend) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -6,7 +6,7 @@ from jinja2 import Template
from botocore.exceptions import ParamValidationError from botocore.exceptions import ParamValidationError
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core.exceptions import RESTError 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.core.utils import camelcase_to_underscores, underscores_to_camelcase
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
from moto.acm.models import acm_backends from moto.acm.models import acm_backends
@ -50,7 +50,7 @@ class FakeHealthStatus(BaseModel):
self.description = description self.description = description
class FakeTargetGroup(BaseModel): class FakeTargetGroup(CloudFormationModel):
HTTP_CODE_REGEX = re.compile(r"(?:(?:\d+-\d+|\d+),?)+") HTTP_CODE_REGEX = re.compile(r"(?:(?:\d+-\d+|\d+),?)+")
def __init__( def __init__(
@ -143,6 +143,15 @@ class FakeTargetGroup(BaseModel):
) )
return FakeHealthStatus(t["id"], t["port"], self.healthcheck_port, "healthy") 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -183,7 +192,7 @@ class FakeTargetGroup(BaseModel):
return target_group return target_group
class FakeListener(BaseModel): class FakeListener(CloudFormationModel):
def __init__( def __init__(
self, self,
load_balancer_arn, load_balancer_arn,
@ -228,6 +237,15 @@ class FakeListener(BaseModel):
self._non_default_rules, key=lambda x: x.priority 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -343,7 +361,7 @@ class FakeBackend(BaseModel):
) )
class FakeLoadBalancer(BaseModel): class FakeLoadBalancer(CloudFormationModel):
VALID_ATTRS = { VALID_ATTRS = {
"access_logs.s3.enabled", "access_logs.s3.enabled",
"access_logs.s3.bucket", "access_logs.s3.bucket",
@ -402,6 +420,15 @@ class FakeLoadBalancer(BaseModel):
""" Not exposed as part of the ELB API - used for CloudFormation. """ """ Not exposed as part of the ELB API - used for CloudFormation. """
elbv2_backends[region].delete_load_balancer(self.arn) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -4,14 +4,14 @@ import json
from boto3 import Session from boto3 import Session
from moto.core.exceptions import JsonRESTError 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.sts.models import ACCOUNT_ID
from moto.utilities.tagging_service import TaggingService from moto.utilities.tagging_service import TaggingService
from uuid import uuid4 from uuid import uuid4
class Rule(BaseModel): class Rule(CloudFormationModel):
def _generate_arn(self, name): def _generate_arn(self, name):
return "arn:aws:events:{region_name}:111111111111:rule/{name}".format( return "arn:aws:events:{region_name}:111111111111:rule/{name}".format(
region_name=self.region_name, name=name region_name=self.region_name, name=name
@ -73,6 +73,15 @@ class Rule(BaseModel):
raise UnformattedGetAttTemplateException() 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -101,7 +110,7 @@ class Rule(BaseModel):
event_backend.delete_rule(name=event_name) event_backend.delete_rule(name=event_name)
class EventBus(BaseModel): class EventBus(CloudFormationModel):
def __init__(self, region_name, name): def __init__(self, region_name, name):
self.region = region_name self.region = region_name
self.name = name self.name = name
@ -152,6 +161,15 @@ class EventBus(BaseModel):
raise UnformattedGetAttTemplateException() 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name 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 uuid import uuid4
from moto.core.exceptions import RESTError 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 ( from moto.core.utils import (
iso_8601_datetime_without_milliseconds, iso_8601_datetime_without_milliseconds,
iso_8601_datetime_with_milliseconds, iso_8601_datetime_with_milliseconds,
@ -299,7 +299,7 @@ class InlinePolicy(Policy):
"""TODO: is this needed?""" """TODO: is this needed?"""
class Role(BaseModel): class Role(CloudFormationModel):
def __init__( def __init__(
self, self,
role_id, role_id,
@ -327,6 +327,15 @@ class Role(BaseModel):
def created_iso_8601(self): def created_iso_8601(self):
return iso_8601_datetime_with_milliseconds(self.create_date) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -384,7 +393,7 @@ class Role(BaseModel):
return [self.tags[tag] for tag in self.tags] return [self.tags[tag] for tag in self.tags]
class InstanceProfile(BaseModel): class InstanceProfile(CloudFormationModel):
def __init__(self, instance_profile_id, name, path, roles): def __init__(self, instance_profile_id, name, path, roles):
self.id = instance_profile_id self.id = instance_profile_id
self.name = name self.name = name
@ -396,6 +405,15 @@ class InstanceProfile(BaseModel):
def created_iso_8601(self): def created_iso_8601(self):
return iso_8601_datetime_with_milliseconds(self.create_date) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -12,7 +12,7 @@ from hashlib import md5
from boto3 import Session from boto3 import Session
from moto.compat import OrderedDict 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.utils import unix_time
from moto.core import ACCOUNT_ID from moto.core import ACCOUNT_ID
from .exceptions import ( from .exceptions import (
@ -129,7 +129,7 @@ class Shard(BaseModel):
} }
class Stream(BaseModel): class Stream(CloudFormationModel):
def __init__(self, stream_name, shard_count, region): def __init__(self, stream_name, shard_count, region):
self.stream_name = stream_name self.stream_name = stream_name
self.shard_count = shard_count 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -6,7 +6,7 @@ from datetime import datetime, timedelta
from boto3 import Session 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.core.utils import unix_time
from moto.utilities.tagging_service import TaggingService from moto.utilities.tagging_service import TaggingService
from moto.core.exceptions import JsonRESTError 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 from .utils import decrypt, encrypt, generate_key_id, generate_master_key
class Key(BaseModel): class Key(CloudFormationModel):
def __init__( def __init__(
self, policy, key_usage, customer_master_key_spec, description, region self, policy, key_usage, customer_master_key_spec, description, region
): ):
@ -99,6 +99,15 @@ class Key(BaseModel):
def delete(self, region_name): def delete(self, region_name):
kms_backends[region_name].delete_key(self.id) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
self, resource_name, cloudformation_json, region_name self, resource_name, cloudformation_json, region_name

View File

@ -3,14 +3,14 @@ from __future__ import unicode_literals
import boto.rds import boto.rds
from jinja2 import Template 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.core.utils import get_random_hex
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
from moto.rds.exceptions import UnformattedGetAttTemplateException from moto.rds.exceptions import UnformattedGetAttTemplateException
from moto.rds2.models import rds2_backends from moto.rds2.models import rds2_backends
class Database(BaseModel): class Database(CloudFormationModel):
def get_cfn_attribute(self, attribute_name): def get_cfn_attribute(self, attribute_name):
if attribute_name == "Endpoint.Address": if attribute_name == "Endpoint.Address":
return self.address return self.address
@ -18,13 +18,22 @@ class Database(BaseModel):
return self.port return self.port
raise UnformattedGetAttTemplateException() 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
): ):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
db_instance_identifier = properties.get("DBInstanceIdentifier") db_instance_identifier = properties.get(cls.cloudformation_name_type())
if not db_instance_identifier: if not db_instance_identifier:
db_instance_identifier = resource_name.lower() + get_random_hex(12) db_instance_identifier = resource_name.lower() + get_random_hex(12)
db_security_groups = properties.get("DBSecurityGroups") db_security_groups = properties.get("DBSecurityGroups")
@ -163,7 +172,7 @@ class Database(BaseModel):
backend.delete_database(self.db_instance_identifier) backend.delete_database(self.db_instance_identifier)
class SecurityGroup(BaseModel): class SecurityGroup(CloudFormationModel):
def __init__(self, group_name, description): def __init__(self, group_name, description):
self.group_name = group_name self.group_name = group_name
self.description = description self.description = description
@ -206,6 +215,15 @@ class SecurityGroup(BaseModel):
def authorize_security_group(self, security_group): def authorize_security_group(self, security_group):
self.ec2_security_groups.append(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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -239,7 +257,7 @@ class SecurityGroup(BaseModel):
backend.delete_security_group(self.group_name) backend.delete_security_group(self.group_name)
class SubnetGroup(BaseModel): class SubnetGroup(CloudFormationModel):
def __init__(self, subnet_name, description, subnets): def __init__(self, subnet_name, description, subnets):
self.subnet_name = subnet_name self.subnet_name = subnet_name
self.description = description self.description = description
@ -271,13 +289,23 @@ class SubnetGroup(BaseModel):
) )
return template.render(subnet_group=self) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
): ):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
subnet_name = properties.get(cls.cloudformation_name_type())
subnet_name = resource_name.lower() + get_random_hex(12) if not subnet_name:
subnet_name = resource_name.lower() + get_random_hex(12)
description = properties["DBSubnetGroupDescription"] description = properties["DBSubnetGroupDescription"]
subnet_ids = properties["SubnetIds"] subnet_ids = properties["SubnetIds"]
tags = properties.get("Tags") tags = properties.get("Tags")

View File

@ -9,7 +9,7 @@ from boto3 import Session
from jinja2 import Template from jinja2 import Template
from re import compile as re_compile from re import compile as re_compile
from moto.compat import OrderedDict 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 get_random_hex
from moto.core.utils import iso_8601_datetime_with_milliseconds from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
@ -28,7 +28,7 @@ from .exceptions import (
) )
class Database(BaseModel): class Database(CloudFormationModel):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.status = "available" self.status = "available"
self.is_replica = False self.is_replica = False
@ -356,13 +356,22 @@ class Database(BaseModel):
"sqlserver-web": {"gp2": 20, "io1": 100, "standard": 20}, "sqlserver-web": {"gp2": 20, "io1": 100, "standard": 20},
}[engine][storage_type] }[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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
): ):
properties = cloudformation_json["Properties"] properties = cloudformation_json["Properties"]
db_instance_identifier = properties.get("DBInstanceIdentifier") db_instance_identifier = properties.get(cls.cloudformation_name_type())
if not db_instance_identifier: if not db_instance_identifier:
db_instance_identifier = resource_name.lower() + get_random_hex(12) db_instance_identifier = resource_name.lower() + get_random_hex(12)
db_security_groups = properties.get("DBSecurityGroups") 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] 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): def __init__(self, group_name, description, tags):
self.group_name = group_name self.group_name = group_name
self.description = description self.description = description
@ -627,6 +636,15 @@ class SecurityGroup(BaseModel):
def authorize_security_group(self, security_group): def authorize_security_group(self, security_group):
self.ec2_security_groups.append(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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -671,7 +689,7 @@ class SecurityGroup(BaseModel):
backend.delete_security_group(self.group_name) backend.delete_security_group(self.group_name)
class SubnetGroup(BaseModel): class SubnetGroup(CloudFormationModel):
def __init__(self, subnet_name, description, subnets, tags): def __init__(self, subnet_name, description, subnets, tags):
self.subnet_name = subnet_name self.subnet_name = subnet_name
self.description = description self.description = description
@ -726,13 +744,24 @@ class SubnetGroup(BaseModel):
) )
return template.render(subnet_group=self) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
): ):
properties = cloudformation_json["Properties"] 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"] description = properties["DBSubnetGroupDescription"]
subnet_ids = properties["SubnetIds"] subnet_ids = properties["SubnetIds"]
tags = properties.get("Tags") tags = properties.get("Tags")
@ -1441,7 +1470,7 @@ class OptionGroupOptionSetting(object):
return template.render(option_group_option_setting=self) return template.render(option_group_option_setting=self)
class DBParameterGroup(object): class DBParameterGroup(CloudFormationModel):
def __init__(self, name, description, family, tags): def __init__(self, name, description, family, tags):
self.name = name self.name = name
self.description = description self.description = description
@ -1480,6 +1509,15 @@ class DBParameterGroup(object):
backend = rds2_backends[region_name] backend = rds2_backends[region_name]
backend.delete_db_parameter_group(self.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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -6,7 +6,7 @@ import datetime
from boto3 import Session from boto3 import Session
from botocore.exceptions import ClientError from botocore.exceptions import ClientError
from moto.compat import OrderedDict 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.core.utils import iso_8601_datetime_with_milliseconds
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from .exceptions import ( from .exceptions import (
@ -63,7 +63,7 @@ class TaggableResourceMixin(object):
return self.tags return self.tags
class Cluster(TaggableResourceMixin, BaseModel): class Cluster(TaggableResourceMixin, CloudFormationModel):
resource_type = "cluster" resource_type = "cluster"
@ -157,6 +157,15 @@ class Cluster(TaggableResourceMixin, BaseModel):
self.iam_roles_arn = iam_roles_arn or [] self.iam_roles_arn = iam_roles_arn or []
self.restored_from_snapshot = restored_from_snapshot 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -170,6 +179,7 @@ class Cluster(TaggableResourceMixin, BaseModel):
].cluster_subnet_group_name ].cluster_subnet_group_name
else: else:
subnet_group_name = None subnet_group_name = None
cluster = redshift_backend.create_cluster( cluster = redshift_backend.create_cluster(
cluster_identifier=resource_name, cluster_identifier=resource_name,
node_type=properties.get("NodeType"), node_type=properties.get("NodeType"),
@ -321,7 +331,7 @@ class SnapshotCopyGrant(TaggableResourceMixin, BaseModel):
} }
class SubnetGroup(TaggableResourceMixin, BaseModel): class SubnetGroup(TaggableResourceMixin, CloudFormationModel):
resource_type = "subnetgroup" resource_type = "subnetgroup"
@ -342,6 +352,15 @@ class SubnetGroup(TaggableResourceMixin, BaseModel):
if not self.subnets: if not self.subnets:
raise InvalidSubnetError(subnet_ids) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name 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" resource_type = "parametergroup"
@ -429,6 +448,15 @@ class ParameterGroup(TaggableResourceMixin, BaseModel):
self.group_family = group_family self.group_family = group_family
self.description = description 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -7,7 +7,7 @@ import random
import uuid import uuid
from jinja2 import Template from jinja2 import Template
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, CloudFormationModel
ROUTE53_ID_CHOICE = string.ascii_uppercase + string.digits 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)]) 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): def __init__(self, health_check_id, health_check_args):
self.id = health_check_id self.id = health_check_id
self.ip_address = health_check_args.get("ip_address") self.ip_address = health_check_args.get("ip_address")
@ -34,6 +34,15 @@ class HealthCheck(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.id 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -75,7 +84,7 @@ class HealthCheck(BaseModel):
return template.render(health_check=self) return template.render(health_check=self)
class RecordSet(BaseModel): class RecordSet(CloudFormationModel):
def __init__(self, kwargs): def __init__(self, kwargs):
self.name = kwargs.get("Name") self.name = kwargs.get("Name")
self.type_ = kwargs.get("Type") self.type_ = kwargs.get("Type")
@ -91,6 +100,15 @@ class RecordSet(BaseModel):
self.failover = kwargs.get("Failover") self.failover = kwargs.get("Failover")
self.geo_location = kwargs.get("GeoLocation") 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -202,7 +220,7 @@ def reverse_domain_name(domain_name):
return ".".join(reversed(domain_name.split("."))) return ".".join(reversed(domain_name.split(".")))
class FakeZone(BaseModel): class FakeZone(CloudFormationModel):
def __init__(self, name, id_, private_zone, comment=None): def __init__(self, name, id_, private_zone, comment=None):
self.name = name self.name = name
self.id = id_ self.id = id_
@ -267,6 +285,15 @@ class FakeZone(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.id 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -278,7 +305,7 @@ class FakeZone(BaseModel):
return hosted_zone return hosted_zone
class RecordSetGroup(BaseModel): class RecordSetGroup(CloudFormationModel):
def __init__(self, hosted_zone_id, record_sets): def __init__(self, hosted_zone_id, record_sets):
self.hosted_zone_id = hosted_zone_id self.hosted_zone_id = hosted_zone_id
self.record_sets = record_sets self.record_sets = record_sets
@ -287,6 +314,15 @@ class RecordSetGroup(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return "arn:aws:route53:::hostedzone/{0}".format(self.hosted_zone_id) 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -21,7 +21,7 @@ import uuid
import six import six
from bisect import insort 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.core.utils import iso_8601_datetime_without_milliseconds_s3, rfc_1123_datetime
from moto.cloudwatch.models import MetricDatum from moto.cloudwatch.models import MetricDatum
from moto.utilities.tagging_service import TaggingService 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): def __init__(self, name, region_name):
self.name = name self.name = name
self.region_name = region_name self.region_name = region_name
@ -1070,6 +1070,15 @@ class FakeBucket(BaseModel):
def physical_resource_id(self): def physical_resource_id(self):
return self.name 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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name

View File

@ -11,7 +11,7 @@ import re
from boto3 import Session from boto3 import Session
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import ( from moto.core.utils import (
iso_8601_datetime_with_milliseconds, iso_8601_datetime_with_milliseconds,
camelcase_to_underscores, camelcase_to_underscores,
@ -37,7 +37,7 @@ DEFAULT_PAGE_SIZE = 100
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
class Topic(BaseModel): class Topic(CloudFormationModel):
def __init__(self, name, sns_backend): def __init__(self, name, sns_backend):
self.name = name self.name = name
self.sns_backend = sns_backend self.sns_backend = sns_backend
@ -87,6 +87,15 @@ class Topic(BaseModel):
def policy(self, policy): def policy(self, policy):
self._policy_json = json.loads(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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name
@ -94,7 +103,7 @@ class Topic(BaseModel):
sns_backend = sns_backends[region_name] sns_backend = sns_backends[region_name]
properties = cloudformation_json["Properties"] 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", []): for subscription in properties.get("Subscription", []):
sns_backend.subscribe( sns_backend.subscribe(
topic.arn, subscription["Endpoint"], subscription["Protocol"] topic.arn, subscription["Endpoint"], subscription["Protocol"]

View File

@ -12,7 +12,7 @@ from xml.sax.saxutils import escape
from boto3 import Session from boto3 import Session
from moto.core.exceptions import RESTError from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import ( from moto.core.utils import (
camelcase_to_underscores, camelcase_to_underscores,
get_random_message_id, get_random_message_id,
@ -188,7 +188,7 @@ class Message(BaseModel):
return False return False
class Queue(BaseModel): class Queue(CloudFormationModel):
BASE_ATTRIBUTES = [ BASE_ATTRIBUTES = [
"ApproximateNumberOfMessages", "ApproximateNumberOfMessages",
"ApproximateNumberOfMessagesDelayed", "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 @classmethod
def create_from_cloudformation_json( def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name cls, resource_name, cloudformation_json, region_name