Add ECS CloudFormation support (#795)
* Add cloudformation support to AWS::ECS::Cluster * Add CloudFormation support to AWS::ECS::TaskDefinition * Add CloudFormation support to AWS::ECS::Service * Add support to update AWS::ECS::Cluster through CloudFormation * Fix Cluster.update_from_cloudformation_json to return original_resource if nothing changed * Implement TaskDefinition.update_from_cloudformation_json * Implement Service.update_from_cloudformation_json
This commit is contained in:
parent
a20906ff15
commit
0115267f2a
@ -3,11 +3,13 @@ import collections
|
|||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import copy
|
import copy
|
||||||
|
import warnings
|
||||||
|
|
||||||
from moto.autoscaling import models as autoscaling_models
|
from moto.autoscaling import models as autoscaling_models
|
||||||
from moto.awslambda import models as lambda_models
|
from moto.awslambda import models as lambda_models
|
||||||
from moto.datapipeline import models as datapipeline_models
|
from moto.datapipeline import models as datapipeline_models
|
||||||
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.elb import models as elb_models
|
from moto.elb import models as elb_models
|
||||||
from moto.iam import models as iam_models
|
from moto.iam import models as iam_models
|
||||||
from moto.kms import models as kms_models
|
from moto.kms import models as kms_models
|
||||||
@ -43,6 +45,9 @@ MODEL_MAP = {
|
|||||||
"AWS::EC2::VPC": ec2_models.VPC,
|
"AWS::EC2::VPC": ec2_models.VPC,
|
||||||
"AWS::EC2::VPCGatewayAttachment": ec2_models.VPCGatewayAttachment,
|
"AWS::EC2::VPCGatewayAttachment": ec2_models.VPCGatewayAttachment,
|
||||||
"AWS::EC2::VPCPeeringConnection": ec2_models.VPCPeeringConnection,
|
"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::ElasticLoadBalancing::LoadBalancer": elb_models.FakeLoadBalancer,
|
||||||
"AWS::DataPipeline::Pipeline": datapipeline_models.Pipeline,
|
"AWS::DataPipeline::Pipeline": datapipeline_models.Pipeline,
|
||||||
"AWS::IAM::InstanceProfile": iam_models.InstanceProfile,
|
"AWS::IAM::InstanceProfile": iam_models.InstanceProfile,
|
||||||
@ -175,6 +180,8 @@ def parse_resource(logical_id, resource_json, resources_map):
|
|||||||
resource_type = resource_json['Type']
|
resource_type = resource_json['Type']
|
||||||
resource_class = resource_class_from_type(resource_type)
|
resource_class = resource_class_from_type(resource_type)
|
||||||
if not resource_class:
|
if not resource_class:
|
||||||
|
warnings.warn(
|
||||||
|
"Tried to parse {0} but it's not supported by moto's CloudFormation implementation".format(resource_type))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
resource_json = clean_json(resource_json, resources_map)
|
resource_json = clean_json(resource_json, resources_map)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import uuid
|
import uuid
|
||||||
from random import randint
|
from random import randint, random
|
||||||
|
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
from moto.ec2 import ec2_backends
|
from moto.ec2 import ec2_backends
|
||||||
@ -48,13 +48,39 @@ class Cluster(BaseObject):
|
|||||||
del response_object['arn'], response_object['name']
|
del response_object['arn'], response_object['name']
|
||||||
return response_object
|
return response_object
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
|
||||||
|
ecs_backend = ecs_backends[region_name]
|
||||||
|
return ecs_backend.create_cluster(
|
||||||
|
# ClusterName is optional in CloudFormation, thus create a random name if necessary
|
||||||
|
cluster_name=properties.get('ClusterName', 'ecscluster{0}'.format(int(random() * 10 ** 6))),
|
||||||
|
)
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
|
||||||
|
if original_resource.name != properties['ClusterName']:
|
||||||
|
ecs_backend = ecs_backends[region_name]
|
||||||
|
ecs_backend.delete_cluster(original_resource.arn)
|
||||||
|
return ecs_backend.create_cluster(
|
||||||
|
# ClusterName is optional in CloudFormation, thus create a random name if necessary
|
||||||
|
cluster_name=properties.get('ClusterName', 'ecscluster{0}'.format(int(random() * 10 ** 6))),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# no-op when nothing changed between old and new resources
|
||||||
|
return original_resource
|
||||||
|
|
||||||
|
|
||||||
class TaskDefinition(BaseObject):
|
class TaskDefinition(BaseObject):
|
||||||
def __init__(self, family, revision, container_definitions, volumes=None):
|
def __init__(self, family, revision, container_definitions, volumes=None):
|
||||||
self.family = family
|
self.family = family
|
||||||
self.arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/{0}:{1}'.format(family, revision)
|
self.arn = 'arn:aws:ecs:us-east-1:012345678910:task-definition/{0}:{1}'.format(family, revision)
|
||||||
self.container_definitions = container_definitions
|
self.container_definitions = container_definitions
|
||||||
if volumes is not None:
|
if volumes is None:
|
||||||
|
self.volumes = []
|
||||||
|
else:
|
||||||
self.volumes = volumes
|
self.volumes = volumes
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -64,6 +90,37 @@ class TaskDefinition(BaseObject):
|
|||||||
del response_object['arn']
|
del response_object['arn']
|
||||||
return response_object
|
return response_object
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
|
||||||
|
family = properties.get('Family', 'task-definition-{0}'.format(int(random() * 10 ** 6)))
|
||||||
|
container_definitions = properties['ContainerDefinitions']
|
||||||
|
volumes = properties['Volumes']
|
||||||
|
|
||||||
|
ecs_backend = ecs_backends[region_name]
|
||||||
|
return ecs_backend.register_task_definition(
|
||||||
|
family=family, container_definitions=container_definitions, volumes=volumes)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
|
||||||
|
family = properties.get('Family', 'task-definition-{0}'.format(int(random() * 10 ** 6)))
|
||||||
|
container_definitions = properties['ContainerDefinitions']
|
||||||
|
volumes = properties['Volumes']
|
||||||
|
if (original_resource.family != family or
|
||||||
|
original_resource.container_definitions != container_definitions or
|
||||||
|
original_resource.volumes != volumes
|
||||||
|
# currently TaskRoleArn isn't stored at TaskDefinition instances
|
||||||
|
):
|
||||||
|
ecs_backend = ecs_backends[region_name]
|
||||||
|
ecs_backend.deregister_task_definition(original_resource.arn)
|
||||||
|
return ecs_backend.register_task_definition(
|
||||||
|
family=family, container_definitions=container_definitions, volumes=volumes)
|
||||||
|
else:
|
||||||
|
# no-op when nothing changed between old and new resources
|
||||||
|
return original_resource
|
||||||
|
|
||||||
class Task(BaseObject):
|
class Task(BaseObject):
|
||||||
def __init__(self, cluster, task_definition, container_instance_arn, overrides={}, started_by=''):
|
def __init__(self, cluster, task_definition, container_instance_arn, overrides={}, started_by=''):
|
||||||
@ -105,6 +162,51 @@ class Service(BaseObject):
|
|||||||
response_object['serviceArn'] = self.arn
|
response_object['serviceArn'] = self.arn
|
||||||
return response_object
|
return response_object
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
if isinstance(properties['Cluster'], Cluster):
|
||||||
|
cluster = properties['Cluster'].name
|
||||||
|
else:
|
||||||
|
cluster = properties['Cluster']
|
||||||
|
if isinstance(properties['TaskDefinition'], TaskDefinition):
|
||||||
|
task_definition = properties['TaskDefinition'].family
|
||||||
|
else:
|
||||||
|
task_definition = properties['TaskDefinition']
|
||||||
|
service_name = '{0}Service{1}'.format(cluster, int(random() * 10 ** 6))
|
||||||
|
desired_count = properties['DesiredCount']
|
||||||
|
# TODO: LoadBalancers
|
||||||
|
# TODO: Role
|
||||||
|
|
||||||
|
ecs_backend = ecs_backends[region_name]
|
||||||
|
return ecs_backend.create_service(
|
||||||
|
cluster, service_name, task_definition, desired_count)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_from_cloudformation_json(cls, original_resource, new_resource_name, cloudformation_json, region_name):
|
||||||
|
properties = cloudformation_json['Properties']
|
||||||
|
if isinstance(properties['Cluster'], Cluster):
|
||||||
|
cluster_name = properties['Cluster'].name
|
||||||
|
else:
|
||||||
|
cluster_name = properties['Cluster']
|
||||||
|
if isinstance(properties['TaskDefinition'], TaskDefinition):
|
||||||
|
task_definition = properties['TaskDefinition'].family
|
||||||
|
else:
|
||||||
|
task_definition = properties['TaskDefinition']
|
||||||
|
desired_count = properties['DesiredCount']
|
||||||
|
|
||||||
|
ecs_backend = ecs_backends[region_name]
|
||||||
|
service_name = original_resource.name
|
||||||
|
if original_resource.cluster_arn != Cluster(cluster_name).arn:
|
||||||
|
# TODO: LoadBalancers
|
||||||
|
# TODO: Role
|
||||||
|
ecs_backend.delete_service(cluster_name, service_name)
|
||||||
|
new_service_name = '{0}Service{1}'.format(cluster_name, int(random() * 10 ** 6))
|
||||||
|
return ecs_backend.create_service(
|
||||||
|
cluster_name, new_service_name, task_definition, desired_count)
|
||||||
|
else:
|
||||||
|
return ecs_backend.update_service(cluster_name, service_name, task_definition, desired_count)
|
||||||
|
|
||||||
|
|
||||||
class ContainerInstance(BaseObject):
|
class ContainerInstance(BaseObject):
|
||||||
def __init__(self, ec2_instance_id):
|
def __init__(self, ec2_instance_id):
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
import json
|
import json
|
||||||
from moto.ec2 import utils as ec2_utils
|
from moto.ec2 import utils as ec2_utils
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from moto import mock_cloudformation
|
||||||
from moto import mock_ecs
|
from moto import mock_ecs
|
||||||
from moto import mock_ec2
|
from moto import mock_ec2
|
||||||
|
|
||||||
@ -918,3 +922,254 @@ def test_stop_task():
|
|||||||
stop_response['task']['lastStatus'].should.equal('STOPPED')
|
stop_response['task']['lastStatus'].should.equal('STOPPED')
|
||||||
stop_response['task']['desiredStatus'].should.equal('STOPPED')
|
stop_response['task']['desiredStatus'].should.equal('STOPPED')
|
||||||
stop_response['task']['stoppedReason'].should.equal('moto testing')
|
stop_response['task']['stoppedReason'].should.equal('moto testing')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecs
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_create_cluster_through_cloudformation():
|
||||||
|
template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "ECS Cluster Test CloudFormation",
|
||||||
|
"Resources": {
|
||||||
|
"testCluster": {
|
||||||
|
"Type": "AWS::ECS::Cluster",
|
||||||
|
"Properties": {
|
||||||
|
"ClusterName": "testcluster"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template_json = json.dumps(template)
|
||||||
|
cfn_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||||
|
cfn_conn.create_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
ecs_conn = boto3.client('ecs', region_name='us-west-1')
|
||||||
|
resp = ecs_conn.list_clusters()
|
||||||
|
len(resp['clusterArns']).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecs
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_update_cluster_name_through_cloudformation_should_trigger_a_replacement():
|
||||||
|
template1 = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "ECS Cluster Test CloudFormation",
|
||||||
|
"Resources": {
|
||||||
|
"testCluster": {
|
||||||
|
"Type": "AWS::ECS::Cluster",
|
||||||
|
"Properties": {
|
||||||
|
"ClusterName": "testcluster1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template2 = deepcopy(template1)
|
||||||
|
template2['Resources']['testCluster']['Properties']['ClusterName'] = 'testcluster2'
|
||||||
|
template1_json = json.dumps(template1)
|
||||||
|
cfn_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||||
|
stack_resp = cfn_conn.create_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template1_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
template2_json = json.dumps(template2)
|
||||||
|
cfn_conn.update_stack(
|
||||||
|
StackName=stack_resp['StackId'],
|
||||||
|
TemplateBody=template2_json
|
||||||
|
)
|
||||||
|
ecs_conn = boto3.client('ecs', region_name='us-west-1')
|
||||||
|
resp = ecs_conn.list_clusters()
|
||||||
|
len(resp['clusterArns']).should.equal(1)
|
||||||
|
resp['clusterArns'][0].endswith('testcluster2').should.be.true
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecs
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_create_task_definition_through_cloudformation():
|
||||||
|
template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "ECS Cluster Test CloudFormation",
|
||||||
|
"Resources": {
|
||||||
|
"testTaskDefinition": {
|
||||||
|
"Type" : "AWS::ECS::TaskDefinition",
|
||||||
|
"Properties" : {
|
||||||
|
"ContainerDefinitions" : [
|
||||||
|
{
|
||||||
|
"Name": "ecs-sample",
|
||||||
|
"Image":"amazon/amazon-ecs-sample",
|
||||||
|
"Cpu": "200",
|
||||||
|
"Memory": "500",
|
||||||
|
"Essential": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Volumes" : [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template_json = json.dumps(template)
|
||||||
|
cfn_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||||
|
cfn_conn.create_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
ecs_conn = boto3.client('ecs', region_name='us-west-1')
|
||||||
|
resp = ecs_conn.list_task_definitions()
|
||||||
|
len(resp['taskDefinitionArns']).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecs
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_update_task_definition_family_through_cloudformation_should_trigger_a_replacement():
|
||||||
|
template1 = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "ECS Cluster Test CloudFormation",
|
||||||
|
"Resources": {
|
||||||
|
"testTaskDefinition": {
|
||||||
|
"Type" : "AWS::ECS::TaskDefinition",
|
||||||
|
"Properties" : {
|
||||||
|
"Family": "testTaskDefinition1",
|
||||||
|
"ContainerDefinitions" : [
|
||||||
|
{
|
||||||
|
"Name": "ecs-sample",
|
||||||
|
"Image":"amazon/amazon-ecs-sample",
|
||||||
|
"Cpu": "200",
|
||||||
|
"Memory": "500",
|
||||||
|
"Essential": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Volumes" : [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template1_json = json.dumps(template1)
|
||||||
|
cfn_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||||
|
cfn_conn.create_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template1_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
template2 = deepcopy(template1)
|
||||||
|
template2['Resources']['testTaskDefinition']['Properties']['Family'] = 'testTaskDefinition2'
|
||||||
|
template2_json = json.dumps(template2)
|
||||||
|
cfn_conn.update_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template2_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
ecs_conn = boto3.client('ecs', region_name='us-west-1')
|
||||||
|
resp = ecs_conn.list_task_definitions(familyPrefix='testTaskDefinition')
|
||||||
|
len(resp['taskDefinitionArns']).should.equal(1)
|
||||||
|
resp['taskDefinitionArns'][0].endswith('testTaskDefinition2:1').should.be.true
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecs
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_create_service_through_cloudformation():
|
||||||
|
template = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "ECS Cluster Test CloudFormation",
|
||||||
|
"Resources": {
|
||||||
|
"testCluster": {
|
||||||
|
"Type": "AWS::ECS::Cluster",
|
||||||
|
"Properties": {
|
||||||
|
"ClusterName": "testcluster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testTaskDefinition": {
|
||||||
|
"Type" : "AWS::ECS::TaskDefinition",
|
||||||
|
"Properties" : {
|
||||||
|
"ContainerDefinitions" : [
|
||||||
|
{
|
||||||
|
"Name": "ecs-sample",
|
||||||
|
"Image":"amazon/amazon-ecs-sample",
|
||||||
|
"Cpu": "200",
|
||||||
|
"Memory": "500",
|
||||||
|
"Essential": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Volumes" : [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testService": {
|
||||||
|
"Type": "AWS::ECS::Service",
|
||||||
|
"Properties": {
|
||||||
|
"Cluster": {"Ref": "testCluster"},
|
||||||
|
"DesiredCount": 10,
|
||||||
|
"TaskDefinition": {"Ref": "testTaskDefinition"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template_json = json.dumps(template)
|
||||||
|
cfn_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||||
|
cfn_conn.create_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
ecs_conn = boto3.client('ecs', region_name='us-west-1')
|
||||||
|
resp = ecs_conn.list_services(cluster='testcluster')
|
||||||
|
len(resp['serviceArns']).should.equal(1)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_ecs
|
||||||
|
@mock_cloudformation
|
||||||
|
def test_update_service_through_cloudformation_should_trigger_replacement():
|
||||||
|
template1 = {
|
||||||
|
"AWSTemplateFormatVersion": "2010-09-09",
|
||||||
|
"Description": "ECS Cluster Test CloudFormation",
|
||||||
|
"Resources": {
|
||||||
|
"testCluster": {
|
||||||
|
"Type": "AWS::ECS::Cluster",
|
||||||
|
"Properties": {
|
||||||
|
"ClusterName": "testcluster"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testTaskDefinition": {
|
||||||
|
"Type" : "AWS::ECS::TaskDefinition",
|
||||||
|
"Properties" : {
|
||||||
|
"ContainerDefinitions" : [
|
||||||
|
{
|
||||||
|
"Name": "ecs-sample",
|
||||||
|
"Image":"amazon/amazon-ecs-sample",
|
||||||
|
"Cpu": "200",
|
||||||
|
"Memory": "500",
|
||||||
|
"Essential": "true"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Volumes" : [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"testService": {
|
||||||
|
"Type": "AWS::ECS::Service",
|
||||||
|
"Properties": {
|
||||||
|
"Cluster": {"Ref": "testCluster"},
|
||||||
|
"TaskDefinition": {"Ref": "testTaskDefinition"},
|
||||||
|
"DesiredCount": 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template_json1 = json.dumps(template1)
|
||||||
|
cfn_conn = boto3.client('cloudformation', region_name='us-west-1')
|
||||||
|
cfn_conn.create_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template_json1,
|
||||||
|
)
|
||||||
|
template2 = deepcopy(template1)
|
||||||
|
template2['Resources']['testService']['Properties']['DesiredCount'] = 5
|
||||||
|
template2_json = json.dumps(template2)
|
||||||
|
cfn_conn.update_stack(
|
||||||
|
StackName="test_stack",
|
||||||
|
TemplateBody=template2_json,
|
||||||
|
)
|
||||||
|
|
||||||
|
ecs_conn = boto3.client('ecs', region_name='us-west-1')
|
||||||
|
resp = ecs_conn.list_services(cluster='testcluster')
|
||||||
|
len(resp['serviceArns']).should.equal(1)
|
||||||
|
Loading…
Reference in New Issue
Block a user