opsworks: impl create_layers; describe_layers
This commit is contained in:
parent
165bab0f97
commit
2fe5b77861
@ -6,9 +6,17 @@ from werkzeug.exceptions import BadRequest
|
|||||||
|
|
||||||
class ResourceNotFoundException(BadRequest):
|
class ResourceNotFoundException(BadRequest):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super(ResourceNotFoundError, self).__init__()
|
super(ResourceNotFoundException, self).__init__()
|
||||||
self.description = json.dumps({
|
self.description = json.dumps({
|
||||||
"message": message,
|
"message": message,
|
||||||
'__type': 'ResourceNotFoundException',
|
'__type': 'ResourceNotFoundException',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationException(BadRequest):
|
||||||
|
def __init__(self, message):
|
||||||
|
super(ValidationException, self).__init__()
|
||||||
|
self.description = json.dumps({
|
||||||
|
"message": message,
|
||||||
|
'__type': 'ResourceNotFoundException',
|
||||||
|
})
|
||||||
|
@ -5,34 +5,132 @@ from moto.elb import elb_backends
|
|||||||
import uuid
|
import uuid
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from .exceptions import ResourceNotFoundException
|
from .exceptions import ResourceNotFoundException, ValidationException
|
||||||
|
|
||||||
|
|
||||||
class Layer(object):
|
class Layer(object):
|
||||||
def __init__(self, stack_id, type, name, shortname, attributes,
|
def __init__(self, stack_id, type, name, shortname,
|
||||||
custom_instance_profile_arn, custom_json,
|
attributes=None,
|
||||||
custom_security_group_ids, packages, volume_configurations,
|
custom_instance_profile_arn=None,
|
||||||
enable_autohealing, auto_assign_elastic_ips,
|
custom_json=None,
|
||||||
auto_assign_public_ips, custom_recipes, install_updates_on_boot,
|
custom_security_group_ids=None,
|
||||||
use_ebs_optimized_instances, lifecycle_event_configuration):
|
packages=None,
|
||||||
|
volume_configurations=None,
|
||||||
|
enable_autohealing=None,
|
||||||
|
auto_assign_elastic_ips=None,
|
||||||
|
auto_assign_public_ips=None,
|
||||||
|
custom_recipes=None,
|
||||||
|
install_updates_on_boot=None,
|
||||||
|
use_ebs_optimized_instances=None,
|
||||||
|
lifecycle_event_configuration=None):
|
||||||
self.stack_id = stack_id
|
self.stack_id = stack_id
|
||||||
self.type = type
|
self.type = type
|
||||||
self.name = name
|
self.name = name
|
||||||
self.shortname = shortname
|
self.shortname = shortname
|
||||||
|
|
||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
|
if attributes is None:
|
||||||
|
self.attributes = {
|
||||||
|
'BundlerVersion': None,
|
||||||
|
'EcsClusterArn': None,
|
||||||
|
'EnableHaproxyStats': None,
|
||||||
|
'GangliaPassword': None,
|
||||||
|
'GangliaUrl': None,
|
||||||
|
'GangliaUser': None,
|
||||||
|
'HaproxyHealthCheckMethod': None,
|
||||||
|
'HaproxyHealthCheckUrl': None,
|
||||||
|
'HaproxyStatsPassword': None,
|
||||||
|
'HaproxyStatsUrl': None,
|
||||||
|
'HaproxyStatsUser': None,
|
||||||
|
'JavaAppServer': None,
|
||||||
|
'JavaAppServerVersion': None,
|
||||||
|
'Jvm': None,
|
||||||
|
'JvmOptions': None,
|
||||||
|
'JvmVersion': None,
|
||||||
|
'ManageBundler': None,
|
||||||
|
'MemcachedMemory': None,
|
||||||
|
'MysqlRootPassword': None,
|
||||||
|
'MysqlRootPasswordUbiquitous': None,
|
||||||
|
'NodejsVersion': None,
|
||||||
|
'PassengerVersion': None,
|
||||||
|
'RailsStack': None,
|
||||||
|
'RubyVersion': None,
|
||||||
|
'RubygemsVersion': None
|
||||||
|
} # May not be accurate
|
||||||
|
|
||||||
|
self.packages = packages
|
||||||
|
if packages is None:
|
||||||
|
self.packages = packages
|
||||||
|
|
||||||
|
self.custom_recipes = custom_recipes
|
||||||
|
if custom_recipes is None:
|
||||||
|
self.custom_recipes = {
|
||||||
|
'Configure': [],
|
||||||
|
'Deploy': [],
|
||||||
|
'Setup': [],
|
||||||
|
'Shutdown': [],
|
||||||
|
'Undeploy': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
self.custom_security_group_ids = custom_security_group_ids
|
||||||
|
if custom_security_group_ids is None:
|
||||||
|
self.custom_security_group_ids = []
|
||||||
|
|
||||||
|
self.lifecycle_event_configuration = lifecycle_event_configuration
|
||||||
|
if lifecycle_event_configuration is None:
|
||||||
|
self.lifecycle_event_configuration = {
|
||||||
|
"Shutdown": {"DelayUntilElbConnectionsDrained": False}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.volume_configurations = volume_configurations
|
||||||
|
if volume_configurations is None:
|
||||||
|
self.volume_configurations = []
|
||||||
|
|
||||||
self.custom_instance_profile_arn = custom_instance_profile_arn
|
self.custom_instance_profile_arn = custom_instance_profile_arn
|
||||||
self.custom_json = custom_json
|
self.custom_json = custom_json
|
||||||
self.custom_security_group_ids = custom_security_group_ids
|
|
||||||
self.packages = packages
|
|
||||||
self.volume_configurations = volume_configurations
|
|
||||||
self.enable_autohealing = enable_autohealing
|
self.enable_autohealing = enable_autohealing
|
||||||
self.auto_assign_elastic_ips = auto_assign_elastic_ips
|
self.auto_assign_elastic_ips = auto_assign_elastic_ips
|
||||||
self.auto_assign_public_ips = auto_assign_public_ips
|
self.auto_assign_public_ips = auto_assign_public_ips
|
||||||
self.custom_recipes = custom_recipes
|
|
||||||
self.install_updates_on_boot = install_updates_on_boot
|
self.install_updates_on_boot = install_updates_on_boot
|
||||||
self.use_ebs_optimized_instances = use_ebs_optimized_instances
|
self.use_ebs_optimized_instances = use_ebs_optimized_instances
|
||||||
self.lifecycle_event_configuration = lifecycle_event_configuration
|
|
||||||
self.instances = []
|
self.instances = []
|
||||||
|
self.id = "{}".format(uuid.uuid4())
|
||||||
|
self.created_at = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.id == other.id
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
d = {
|
||||||
|
"Attributes": self.attributes,
|
||||||
|
"AutoAssignElasticIps": self.auto_assign_elastic_ips,
|
||||||
|
"AutoAssignPublicIps": self.auto_assign_public_ips,
|
||||||
|
"CreatedAt": self.created_at.isoformat(),
|
||||||
|
"CustomRecipes": self.custom_recipes,
|
||||||
|
"CustomSecurityGroupIds": self.custom_security_group_ids,
|
||||||
|
"DefaultRecipes": {
|
||||||
|
"Configure": [],
|
||||||
|
"Setup": [],
|
||||||
|
"Shutdown": [],
|
||||||
|
"Undeploy": []
|
||||||
|
}, # May not be accurate
|
||||||
|
"DefaultSecurityGroupNames": ['AWS-OpsWorks-Custom-Server'],
|
||||||
|
"EnableAutoHealing": self.enable_autohealing,
|
||||||
|
"LayerId": self.id,
|
||||||
|
"LifecycleEventConfiguration": self.lifecycle_event_configuration,
|
||||||
|
"Name": self.name,
|
||||||
|
"Shortname": self.shortname,
|
||||||
|
"StackId": self.stack_id,
|
||||||
|
"Type": self.type,
|
||||||
|
"UseEbsOptimizedInstances": self.use_ebs_optimized_instances,
|
||||||
|
"VolumeConfigurations": self.volume_configurations,
|
||||||
|
}
|
||||||
|
if self.custom_json is not None:
|
||||||
|
d.update({"CustomJson": self.custom_json})
|
||||||
|
if self.custom_instance_profile_arn is not None:
|
||||||
|
d.update({"CustomInstanceProfileArn": self.custom_instance_profile_arn})
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
class Stack(object):
|
class Stack(object):
|
||||||
@ -154,15 +252,52 @@ class OpsWorksBackend(BaseBackend):
|
|||||||
self.stacks[stack.id] = stack
|
self.stacks[stack.id] = stack
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
def describe_stacks(self, stack_ids=None):
|
def create_layer(self, **kwargs):
|
||||||
|
name = kwargs['name']
|
||||||
|
shortname = kwargs['shortname']
|
||||||
|
stackid = kwargs['stack_id']
|
||||||
|
if stackid not in self.stacks:
|
||||||
|
raise ResourceNotFoundException(stackid)
|
||||||
|
if name in [l.name for l in self.layers.values()]:
|
||||||
|
raise ValidationException(
|
||||||
|
'There is already a layer named "{}" '
|
||||||
|
'for this stack'.format(name))
|
||||||
|
if shortname in [l.shortname for l in self.layers.values()]:
|
||||||
|
raise ValidationException(
|
||||||
|
'There is already a layer with shortname "{}" '
|
||||||
|
'for this stack'.format(shortname))
|
||||||
|
layer = Layer(**kwargs)
|
||||||
|
self.layers[layer.id] = layer
|
||||||
|
self.stacks[stackid].layers.append(layer)
|
||||||
|
return layer
|
||||||
|
|
||||||
|
def describe_stacks(self, stack_ids):
|
||||||
if stack_ids is None:
|
if stack_ids is None:
|
||||||
return [stack.to_dict() for stack in self.stacks.values()]
|
return [stack.to_dict() for stack in self.stacks.values()]
|
||||||
|
|
||||||
unknown_stacks = set(stack_ids) - set(self.stacks.keys())
|
unknown_stacks = set(stack_ids) - set(self.stacks.keys())
|
||||||
if unknown_stacks:
|
if unknown_stacks:
|
||||||
raise ResourceNotFoundException(unknown_stacks)
|
raise ResourceNotFoundException(", ".join(unknown_stacks))
|
||||||
return [self.stacks[id].to_dict() for id in stack_ids]
|
return [self.stacks[id].to_dict() for id in stack_ids]
|
||||||
|
|
||||||
|
def describe_layers(self, stack_id, layer_ids):
|
||||||
|
if stack_id is not None and layer_ids is not None:
|
||||||
|
raise ValidationException(
|
||||||
|
"Please provide one or more layer IDs or a stack ID"
|
||||||
|
)
|
||||||
|
if stack_id is not None:
|
||||||
|
if stack_id not in self.stacks:
|
||||||
|
raise ResourceNotFoundException(
|
||||||
|
"Unable to find stack with ID {}".format(stack_id))
|
||||||
|
return [layer.to_dict() for layer in self.stacks[stack_id].layers]
|
||||||
|
|
||||||
|
unknown_layers = set(layer_ids) - set(self.layers.keys())
|
||||||
|
if unknown_layers:
|
||||||
|
raise ResourceNotFoundException(", ".join(unknown_layers))
|
||||||
|
return [self.layers[id].to_dict() for id in layer_ids]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
opsworks_backends = {}
|
opsworks_backends = {}
|
||||||
for region, ec2_backend in ec2_backends.items():
|
for region, ec2_backend in ec2_backends.items():
|
||||||
opsworks_backends[region] = OpsWorksBackend(ec2_backend, elb_backends[region])
|
opsworks_backends[region] = OpsWorksBackend(ec2_backend, elb_backends[region])
|
||||||
|
@ -41,7 +41,36 @@ class OpsWorksResponse(BaseResponse):
|
|||||||
stack = self.opsworks_backend.create_stack(**kwargs)
|
stack = self.opsworks_backend.create_stack(**kwargs)
|
||||||
return json.dumps({"StackId": stack.id}, indent=1)
|
return json.dumps({"StackId": stack.id}, indent=1)
|
||||||
|
|
||||||
|
def create_layer(self):
|
||||||
|
kwargs = dict(
|
||||||
|
stack_id=self.parameters.get('StackId'),
|
||||||
|
type=self.parameters.get('Type'),
|
||||||
|
name=self.parameters.get('Name'),
|
||||||
|
shortname=self.parameters.get('Shortname'),
|
||||||
|
attributes=self.parameters.get('Attributes'),
|
||||||
|
custom_instance_profile_arn=self.parameters.get("CustomInstanceProfileArn"),
|
||||||
|
custom_json=self.parameters.get("CustomJson"),
|
||||||
|
custom_security_group_ids=self.parameters.get('CustomSecurityGroupIds'),
|
||||||
|
packages=self.parameters.get('Packages'),
|
||||||
|
volume_configurations=self.parameters.get("VolumeConfigurations"),
|
||||||
|
enable_autohealing=self.parameters.get("EnableAutoHealing"),
|
||||||
|
auto_assign_elastic_ips=self.parameters.get("AutoAssignElasticIps"),
|
||||||
|
auto_assign_public_ips=self.parameters.get("AutoAssignPublicIps"),
|
||||||
|
custom_recipes=self.parameters.get("CustomRecipes"),
|
||||||
|
install_updates_on_boot=self.parameters.get("InstallUpdatesOnBoot"),
|
||||||
|
use_ebs_optimized_instances=self.parameters.get("UseEbsOptimizedInstances"),
|
||||||
|
lifecycle_event_configuration=self.parameters.get("LifecycleEventConfiguration")
|
||||||
|
)
|
||||||
|
layer = self.opsworks_backend.create_layer(**kwargs)
|
||||||
|
return json.dumps({"LayerId": layer.id}, indent=1)
|
||||||
|
|
||||||
def describe_stacks(self):
|
def describe_stacks(self):
|
||||||
stack_ids = self.parameters.get("StackIds")
|
stack_ids = self.parameters.get("StackIds")
|
||||||
stacks = self.opsworks_backend.describe_stacks(stack_ids)
|
stacks = self.opsworks_backend.describe_stacks(stack_ids)
|
||||||
return json.dumps({"Stacks": stacks}, indent=1)
|
return json.dumps({"Stacks": stacks}, indent=1)
|
||||||
|
|
||||||
|
def describe_layers(self):
|
||||||
|
stack_id = self.parameters.get("StackId")
|
||||||
|
layer_ids = self.parameters.get("LayerIds")
|
||||||
|
layers = self.opsworks_backend.describe_layers(stack_id, layer_ids)
|
||||||
|
return json.dumps({"Layers": layers}, indent=1)
|
||||||
|
69
tests/test_opsworks/test_layers.py
Normal file
69
tests/test_opsworks/test_layers.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
import boto3
|
||||||
|
import sure # noqa
|
||||||
|
import re
|
||||||
|
|
||||||
|
from moto import mock_opsworks
|
||||||
|
|
||||||
|
|
||||||
|
@mock_opsworks
|
||||||
|
def test_create_layer_response():
|
||||||
|
client = boto3.client('opsworks')
|
||||||
|
stack_id = client.create_stack(
|
||||||
|
Name="test_stack_1",
|
||||||
|
Region="us-east-1",
|
||||||
|
ServiceRoleArn="service_arn",
|
||||||
|
DefaultInstanceProfileArn="profile_arn"
|
||||||
|
)['StackId']
|
||||||
|
|
||||||
|
response = client.create_layer(
|
||||||
|
StackId=stack_id,
|
||||||
|
Type="custom",
|
||||||
|
Name="TestLayer",
|
||||||
|
Shortname="TestLayerShortName"
|
||||||
|
)
|
||||||
|
|
||||||
|
response.should.contain("LayerId")
|
||||||
|
|
||||||
|
# ClientError
|
||||||
|
client.create_layer.when.called_with(
|
||||||
|
StackId=stack_id,
|
||||||
|
Type="custom",
|
||||||
|
Name="TestLayer",
|
||||||
|
Shortname="_"
|
||||||
|
).should.throw(
|
||||||
|
Exception, re.compile(r'already a layer named "TestLayer"')
|
||||||
|
)
|
||||||
|
# ClientError
|
||||||
|
client.create_layer.when.called_with(
|
||||||
|
StackId=stack_id,
|
||||||
|
Type="custom",
|
||||||
|
Name="_",
|
||||||
|
Shortname="TestLayerShortName"
|
||||||
|
).should.throw(
|
||||||
|
Exception, re.compile(r'already a layer with shortname "TestLayerShortName"')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_opsworks
|
||||||
|
def test_describe_layers():
|
||||||
|
client = boto3.client('opsworks')
|
||||||
|
stack_id = client.create_stack(
|
||||||
|
Name="test_stack_1",
|
||||||
|
Region="us-east-1",
|
||||||
|
ServiceRoleArn="service_arn",
|
||||||
|
DefaultInstanceProfileArn="profile_arn"
|
||||||
|
)['StackId']
|
||||||
|
layer_id = client.create_layer(
|
||||||
|
StackId=stack_id,
|
||||||
|
Type="custom",
|
||||||
|
Name="TestLayer",
|
||||||
|
Shortname="TestLayerShortName"
|
||||||
|
)['LayerId']
|
||||||
|
|
||||||
|
rv1 = client.describe_layers(StackId=stack_id)
|
||||||
|
rv2 = client.describe_layers(LayerIds=[layer_id])
|
||||||
|
rv1.should.equal(rv2)
|
||||||
|
|
||||||
|
rv1['Layers'][0]['Name'].should.equal("TestLayer")
|
||||||
|
|
@ -1,8 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import boto3
|
import boto3
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
import re
|
||||||
|
|
||||||
from moto import mock_opsworks, mock_ec2, mock_elb
|
from moto import mock_opsworks
|
||||||
|
|
||||||
|
|
||||||
@mock_opsworks
|
@mock_opsworks
|
||||||
@ -30,12 +31,18 @@ def test_describe_stacks():
|
|||||||
|
|
||||||
response = client.describe_stacks()
|
response = client.describe_stacks()
|
||||||
response['Stacks'].should.have.length_of(3)
|
response['Stacks'].should.have.length_of(3)
|
||||||
response['Stacks'][0]['ServiceRoleArn'].should.equal("service_arn")
|
for stack in response['Stacks']:
|
||||||
response['Stacks'][0]['DefaultInstanceProfileArn'].should.equal("profile_arn")
|
stack['ServiceRoleArn'].should.equal("service_arn")
|
||||||
|
stack['DefaultInstanceProfileArn'].should.equal("profile_arn")
|
||||||
|
|
||||||
_id = response['Stacks'][0]['StackId']
|
_id = response['Stacks'][0]['StackId']
|
||||||
response = client.describe_stacks(StackIds=[_id])
|
response = client.describe_stacks(StackIds=[_id])
|
||||||
response['Stacks'].should.have.length_of(1)
|
response['Stacks'].should.have.length_of(1)
|
||||||
response['Stacks'][0]['Arn'].should.contain(_id)
|
response['Stacks'][0]['Arn'].should.contain(_id)
|
||||||
|
|
||||||
|
# ClientError/ResourceNotFoundException
|
||||||
|
client.describe_stacks.when.called_with(StackIds=["foo"]).should.throw(
|
||||||
|
Exception, re.compile(r'foo')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user