Merge pull request #591 from vsudilov/feature/opsworks
Feature/opsworks
This commit is contained in:
commit
1ff0aa4a65
@ -18,6 +18,7 @@ from .ecs import mock_ecs # flake8: noqa
|
||||
from .elb import mock_elb # flake8: noqa
|
||||
from .emr import mock_emr # flake8: noqa
|
||||
from .glacier import mock_glacier # flake8: noqa
|
||||
from .opsworks import mock_opsworks # flake8: noqa
|
||||
from .iam import mock_iam # flake8: noqa
|
||||
from .kinesis import mock_kinesis # flake8: noqa
|
||||
from .kms import mock_kms # flake8: noqa
|
||||
|
@ -12,6 +12,7 @@ from moto.elb import elb_backend
|
||||
from moto.emr import emr_backend
|
||||
from moto.glacier import glacier_backend
|
||||
from moto.iam import iam_backend
|
||||
from moto.opsworks import opsworks_backend
|
||||
from moto.kinesis import kinesis_backend
|
||||
from moto.kms import kms_backend
|
||||
from moto.rds import rds_backend
|
||||
@ -36,6 +37,7 @@ BACKENDS = {
|
||||
'emr': emr_backend,
|
||||
'glacier': glacier_backend,
|
||||
'iam': iam_backend,
|
||||
'opsworks': opsworks_backend,
|
||||
'kinesis': kinesis_backend,
|
||||
'kms': kms_backend,
|
||||
'redshift': redshift_backend,
|
||||
|
13
moto/opsworks/__init__.py
Normal file
13
moto/opsworks/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
from .models import opsworks_backends
|
||||
from ..core.models import MockAWS
|
||||
|
||||
opsworks_backend = opsworks_backends['us-east-1']
|
||||
|
||||
|
||||
def mock_opsworks(func=None):
|
||||
if func:
|
||||
return MockAWS(opsworks_backends)(func)
|
||||
else:
|
||||
return MockAWS(opsworks_backends)
|
||||
|
22
moto/opsworks/exceptions.py
Normal file
22
moto/opsworks/exceptions.py
Normal file
@ -0,0 +1,22 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
|
||||
class ResourceNotFoundException(BadRequest):
|
||||
def __init__(self, message):
|
||||
super(ResourceNotFoundException, self).__init__()
|
||||
self.description = json.dumps({
|
||||
"message": message,
|
||||
'__type': 'ResourceNotFoundException',
|
||||
})
|
||||
|
||||
|
||||
class ValidationException(BadRequest):
|
||||
def __init__(self, message):
|
||||
super(ValidationException, self).__init__()
|
||||
self.description = json.dumps({
|
||||
"message": message,
|
||||
'__type': 'ResourceNotFoundException',
|
||||
})
|
529
moto/opsworks/models.py
Normal file
529
moto/opsworks/models.py
Normal file
@ -0,0 +1,529 @@
|
||||
from __future__ import unicode_literals
|
||||
from moto.core import BaseBackend
|
||||
from moto.ec2 import ec2_backends
|
||||
import uuid
|
||||
import datetime
|
||||
from random import choice
|
||||
|
||||
from .exceptions import ResourceNotFoundException, ValidationException
|
||||
|
||||
|
||||
class OpsworkInstance(object):
|
||||
"""
|
||||
opsworks maintains its own set of ec2 instance metadata.
|
||||
This metadata exists before any instance reservations are made, and is
|
||||
used to populate a reservation request when "start" is called
|
||||
"""
|
||||
def __init__(self, stack_id, layer_ids, instance_type, ec2_backend,
|
||||
auto_scale_type=None,
|
||||
hostname=None,
|
||||
os=None,
|
||||
ami_id="ami-08111162",
|
||||
ssh_keyname=None,
|
||||
availability_zone=None,
|
||||
virtualization_type="hvm",
|
||||
subnet_id=None,
|
||||
architecture="x86_64",
|
||||
root_device_type="ebs",
|
||||
block_device_mappings=None,
|
||||
install_updates_on_boot=True,
|
||||
ebs_optimized=False,
|
||||
agent_version="INHERIT",
|
||||
instance_profile_arn=None,
|
||||
associate_public_ip=None,
|
||||
security_group_ids=None):
|
||||
|
||||
self.ec2_backend = ec2_backend
|
||||
|
||||
self.instance_profile_arn = instance_profile_arn
|
||||
self.agent_version = agent_version
|
||||
self.ebs_optimized = ebs_optimized
|
||||
self.install_updates_on_boot = install_updates_on_boot
|
||||
self.architecture = architecture
|
||||
self.virtualization_type = virtualization_type
|
||||
self.ami_id = ami_id
|
||||
self.auto_scale_type = auto_scale_type
|
||||
self.instance_type = instance_type
|
||||
self.layer_ids = layer_ids
|
||||
self.stack_id = stack_id
|
||||
|
||||
# may not be totally accurate defaults; instance-type dependent
|
||||
self.root_device_type = root_device_type
|
||||
# todo: refactor how we track block_device_mappings to use
|
||||
# boto.ec2.blockdevicemapping.BlockDeviceType and standardize
|
||||
# formatting in to_dict()
|
||||
self.block_device_mappings = block_device_mappings
|
||||
if self.block_device_mappings is None:
|
||||
self.block_device_mappings = [{
|
||||
'DeviceName': 'ROOT_DEVICE',
|
||||
'Ebs': {
|
||||
'VolumeSize': 8,
|
||||
'VolumeType': 'gp2'
|
||||
}
|
||||
}]
|
||||
self.security_group_ids = security_group_ids
|
||||
if self.security_group_ids is None:
|
||||
self.security_group_ids = []
|
||||
|
||||
self.os = os
|
||||
self.hostname = hostname
|
||||
self.ssh_keyname = ssh_keyname
|
||||
self.availability_zone = availability_zone
|
||||
self.subnet_id = subnet_id
|
||||
self.associate_public_ip = associate_public_ip
|
||||
|
||||
self.instance = None
|
||||
self.reported_os = {}
|
||||
self.infrastructure_class = "ec2 (fixed)"
|
||||
self.platform = "linux (fixed)"
|
||||
|
||||
self.id = "{0}".format(uuid.uuid4())
|
||||
self.created_at = datetime.datetime.utcnow()
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
create an ec2 reservation if one doesn't already exist and call
|
||||
start_instance. Update instance attributes to the newly created instance
|
||||
attributes
|
||||
"""
|
||||
if self.instance is None:
|
||||
reservation = self.ec2_backend.add_instances(
|
||||
image_id=self.ami_id,
|
||||
count=1,
|
||||
user_data="",
|
||||
security_group_names=[],
|
||||
security_group_ids=self.security_group_ids,
|
||||
instance_type=self.instance_type,
|
||||
key_name=self.ssh_keyname,
|
||||
ebs_optimized=self.ebs_optimized,
|
||||
subnet_id=self.subnet_id,
|
||||
associate_public_ip=self.associate_public_ip,
|
||||
)
|
||||
self.instance = reservation.instances[0]
|
||||
self.reported_os = {
|
||||
'Family': 'rhel (fixed)',
|
||||
'Name': 'amazon (fixed)',
|
||||
'Version': '2016.03 (fixed)'
|
||||
}
|
||||
self.platform = self.instance.platform
|
||||
self.security_group_ids = self.instance.security_groups
|
||||
self.architecture = self.instance.architecture
|
||||
self.virtualization_type = self.instance.virtualization_type
|
||||
self.subnet_id = self.instance.subnet_id
|
||||
self.root_device_type = self.instance.root_device_type
|
||||
|
||||
self.ec2_backend.start_instances([self.instance.id])
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
if self.instance is None:
|
||||
return "stopped"
|
||||
return self.instance._state.name
|
||||
|
||||
def to_dict(self):
|
||||
d = {
|
||||
"AgentVersion": self.agent_version,
|
||||
"Architecture": self.architecture,
|
||||
"AvailabilityZone": self.availability_zone,
|
||||
"BlockDeviceMappings": self.block_device_mappings,
|
||||
"CreatedAt": self.created_at.isoformat(),
|
||||
"EbsOptimized": self.ebs_optimized,
|
||||
"InstanceId": self.id,
|
||||
"Hostname": self.hostname,
|
||||
"InfrastructureClass": self.infrastructure_class,
|
||||
"InstallUpdatesOnBoot": self.install_updates_on_boot,
|
||||
"InstanceProfileArn": self.instance_profile_arn,
|
||||
"InstanceType": self.instance_type,
|
||||
"LayerIds": self.layer_ids,
|
||||
"Os": self.os,
|
||||
"Platform": self.platform,
|
||||
"ReportedOs": self.reported_os,
|
||||
"RootDeviceType": self.root_device_type,
|
||||
"SecurityGroupIds": self.security_group_ids,
|
||||
"AmiId": self.ami_id,
|
||||
"Status": self.status,
|
||||
}
|
||||
if self.ssh_keyname is not None:
|
||||
d.update({"SshKeyName": self.ssh_keyname})
|
||||
|
||||
if self.auto_scale_type is not None:
|
||||
d.update({"AutoScaleType": self.auto_scale_type})
|
||||
|
||||
if self.instance is not None:
|
||||
d.update({"Ec2InstanceId": self.instance.id})
|
||||
d.update({"ReportedAgentVersion": "2425-20160406102508 (fixed)"})
|
||||
d.update({"RootDeviceVolumeId": "vol-a20e450a (fixed)"})
|
||||
if self.ssh_keyname is not None:
|
||||
d.update({"SshHostDsaKeyFingerprint": "24:36:32:fe:d8:5f:9c:18:b1:ad:37:e9:eb:e8:69:58 (fixed)"})
|
||||
d.update({"SshHostRsaKeyFingerprint": "3c:bd:37:52:d7:ca:67:e1:6e:4b:ac:31:86:79:f5:6c (fixed)"})
|
||||
d.update({"PrivateDns": self.instance.private_dns})
|
||||
d.update({"PrivateIp": self.instance.private_ip})
|
||||
d.update({"PublicDns": getattr(self.instance, 'public_dns', None)})
|
||||
d.update({"PublicIp": getattr(self.instance, 'public_ip', None)})
|
||||
return d
|
||||
|
||||
|
||||
class Layer(object):
|
||||
def __init__(self, stack_id, type, name, shortname,
|
||||
attributes=None,
|
||||
custom_instance_profile_arn=None,
|
||||
custom_json=None,
|
||||
custom_security_group_ids=None,
|
||||
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.type = type
|
||||
self.name = name
|
||||
self.shortname = shortname
|
||||
|
||||
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_json = custom_json
|
||||
self.enable_autohealing = enable_autohealing
|
||||
self.auto_assign_elastic_ips = auto_assign_elastic_ips
|
||||
self.auto_assign_public_ips = auto_assign_public_ips
|
||||
self.install_updates_on_boot = install_updates_on_boot
|
||||
self.use_ebs_optimized_instances = use_ebs_optimized_instances
|
||||
|
||||
self.id = "{0}".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):
|
||||
def __init__(self, name, region, service_role_arn, default_instance_profile_arn,
|
||||
vpcid="vpc-1f99bf7a",
|
||||
attributes=None,
|
||||
default_os='Ubuntu 12.04 LTS',
|
||||
hostname_theme='Layer_Dependent',
|
||||
default_availability_zone='us-east-1a',
|
||||
default_subnet_id='subnet-73981004',
|
||||
custom_json=None,
|
||||
configuration_manager=None,
|
||||
chef_configuration=None,
|
||||
use_custom_cookbooks=False,
|
||||
use_opsworks_security_groups=True,
|
||||
custom_cookbooks_source=None,
|
||||
default_ssh_keyname=None,
|
||||
default_root_device_type='instance-store',
|
||||
agent_version='LATEST'):
|
||||
|
||||
self.name = name
|
||||
self.region = region
|
||||
self.service_role_arn = service_role_arn
|
||||
self.default_instance_profile_arn = default_instance_profile_arn
|
||||
|
||||
self.vpcid = vpcid
|
||||
self.attributes = attributes
|
||||
if attributes is None:
|
||||
self.attributes = {'Color': None}
|
||||
|
||||
self.configuration_manager = configuration_manager
|
||||
if configuration_manager is None:
|
||||
self.configuration_manager = {'Name': 'Chef', 'Version': '11.4'}
|
||||
|
||||
self.chef_configuration = chef_configuration
|
||||
if chef_configuration is None:
|
||||
self.chef_configuration = {}
|
||||
|
||||
self.custom_cookbooks_source = custom_cookbooks_source
|
||||
if custom_cookbooks_source is None:
|
||||
self.custom_cookbooks_source = {}
|
||||
|
||||
self.custom_json = custom_json
|
||||
self.default_ssh_keyname = default_ssh_keyname
|
||||
self.default_os = default_os
|
||||
self.hostname_theme = hostname_theme
|
||||
self.default_availability_zone = default_availability_zone
|
||||
self.default_subnet_id = default_subnet_id
|
||||
self.use_custom_cookbooks = use_custom_cookbooks
|
||||
self.use_opsworks_security_groups = use_opsworks_security_groups
|
||||
self.default_root_device_type = default_root_device_type
|
||||
self.agent_version = agent_version
|
||||
|
||||
self.id = "{0}".format(uuid.uuid4())
|
||||
self.layers = []
|
||||
self.apps = []
|
||||
self.account_number = "123456789012"
|
||||
self.created_at = datetime.datetime.utcnow()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
||||
def generate_hostname(self):
|
||||
# this doesn't match amazon's implementation
|
||||
return "{theme}-{rand}-(moto)".format(
|
||||
theme=self.hostname_theme,
|
||||
rand=[choice("abcdefghijhk") for _ in range(4)])
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
return "arn:aws:opsworks:{region}:{account_number}:stack/{id}".format(
|
||||
region=self.region,
|
||||
account_number=self.account_number,
|
||||
id=self.id
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
response = {
|
||||
"AgentVersion": self.agent_version,
|
||||
"Arn": self.arn,
|
||||
"Attributes": self.attributes,
|
||||
"ChefConfiguration": self.chef_configuration,
|
||||
"ConfigurationManager": self.configuration_manager,
|
||||
"CreatedAt": self.created_at.isoformat(),
|
||||
"CustomCookbooksSource": self.custom_cookbooks_source,
|
||||
"DefaultAvailabilityZone": self.default_availability_zone,
|
||||
"DefaultInstanceProfileArn": self.default_instance_profile_arn,
|
||||
"DefaultOs": self.default_os,
|
||||
"DefaultRootDeviceType": self.default_root_device_type,
|
||||
"DefaultSshKeyName": self.default_ssh_keyname,
|
||||
"DefaultSubnetId": self.default_subnet_id,
|
||||
"HostnameTheme": self.hostname_theme,
|
||||
"Name": self.name,
|
||||
"Region": self.region,
|
||||
"ServiceRoleArn": self.service_role_arn,
|
||||
"StackId": self.id,
|
||||
"UseCustomCookbooks": self.use_custom_cookbooks,
|
||||
"UseOpsworksSecurityGroups": self.use_opsworks_security_groups,
|
||||
"VpcId": self.vpcid
|
||||
}
|
||||
if self.custom_json is not None:
|
||||
response.update({"CustomJson": self.custom_json})
|
||||
if self.default_ssh_keyname is not None:
|
||||
response.update({"DefaultSshKeyName": self.default_ssh_keyname})
|
||||
return response
|
||||
|
||||
|
||||
class OpsWorksBackend(BaseBackend):
|
||||
def __init__(self, ec2_backend):
|
||||
self.stacks = {}
|
||||
self.layers = {}
|
||||
self.instances = {}
|
||||
self.ec2_backend = ec2_backend
|
||||
|
||||
def reset(self):
|
||||
ec2_backend = self.ec2_backend
|
||||
self.__dict__ = {}
|
||||
self.__init__(ec2_backend)
|
||||
|
||||
def create_stack(self, **kwargs):
|
||||
stack = Stack(**kwargs)
|
||||
self.stacks[stack.id] = stack
|
||||
return stack
|
||||
|
||||
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 "{0}" '
|
||||
'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 "{0}" '
|
||||
'for this stack'.format(shortname))
|
||||
layer = Layer(**kwargs)
|
||||
self.layers[layer.id] = layer
|
||||
self.stacks[stackid].layers.append(layer)
|
||||
return layer
|
||||
|
||||
def create_instance(self, **kwargs):
|
||||
stack_id = kwargs['stack_id']
|
||||
layer_ids = kwargs['layer_ids']
|
||||
|
||||
if stack_id not in self.stacks:
|
||||
raise ResourceNotFoundException(
|
||||
"Unable to find stack with ID {0}".format(stack_id))
|
||||
|
||||
unknown_layers = set(layer_ids) - set(self.layers.keys())
|
||||
if unknown_layers:
|
||||
raise ResourceNotFoundException(", ".join(unknown_layers))
|
||||
|
||||
layers = [self.layers[id] for id in layer_ids]
|
||||
if len(set([layer.stack_id for layer in layers])) != 1 or \
|
||||
any([layer.stack_id != stack_id for layer in layers]):
|
||||
raise ValidationException(
|
||||
"Please only provide layer IDs from the same stack")
|
||||
|
||||
stack = self.stacks[stack_id]
|
||||
# pick the first to set default instance_profile_arn and
|
||||
# security_group_ids on the instance.
|
||||
layer = layers[0]
|
||||
|
||||
kwargs.setdefault("hostname", stack.generate_hostname())
|
||||
kwargs.setdefault("ssh_keyname", stack.default_ssh_keyname)
|
||||
kwargs.setdefault("availability_zone", stack.default_availability_zone)
|
||||
kwargs.setdefault("subnet_id", stack.default_subnet_id)
|
||||
kwargs.setdefault("root_device_type", stack.default_root_device_type)
|
||||
if layer.custom_instance_profile_arn:
|
||||
kwargs.setdefault("instance_profile_arn", layer.custom_instance_profile_arn)
|
||||
kwargs.setdefault("instance_profile_arn", stack.default_instance_profile_arn)
|
||||
kwargs.setdefault("security_group_ids", layer.custom_security_group_ids)
|
||||
kwargs.setdefault("associate_public_ip", layer.auto_assign_public_ips)
|
||||
kwargs.setdefault("ebs_optimized", layer.use_ebs_optimized_instances)
|
||||
kwargs.update({"ec2_backend": self.ec2_backend})
|
||||
opsworks_instance = OpsworkInstance(**kwargs)
|
||||
self.instances[opsworks_instance.id] = opsworks_instance
|
||||
return opsworks_instance
|
||||
|
||||
def describe_stacks(self, stack_ids):
|
||||
if stack_ids is None:
|
||||
return [stack.to_dict() for stack in self.stacks.values()]
|
||||
|
||||
unknown_stacks = set(stack_ids) - set(self.stacks.keys())
|
||||
if unknown_stacks:
|
||||
raise ResourceNotFoundException(", ".join(unknown_stacks))
|
||||
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 {0}".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]
|
||||
|
||||
def describe_instances(self, instance_ids, layer_id, stack_id):
|
||||
if len(list(filter(None, (instance_ids, layer_id, stack_id)))) != 1:
|
||||
raise ValidationException("Please provide either one or more "
|
||||
"instance IDs or one stack ID or one "
|
||||
"layer ID")
|
||||
if instance_ids:
|
||||
unknown_instances = set(instance_ids) - set(self.instances.keys())
|
||||
if unknown_instances:
|
||||
raise ResourceNotFoundException(", ".join(unknown_instances))
|
||||
return [self.instances[id].to_dict() for id in instance_ids]
|
||||
|
||||
if layer_id:
|
||||
if layer_id not in self.layers:
|
||||
raise ResourceNotFoundException(
|
||||
"Unable to find layer with ID {0}".format(layer_id))
|
||||
instances = [i.to_dict() for i in self.instances.values() if layer_id in i.layer_ids]
|
||||
return instances
|
||||
|
||||
if stack_id:
|
||||
if stack_id not in self.stacks:
|
||||
raise ResourceNotFoundException(
|
||||
"Unable to find stack with ID {0}".format(stack_id))
|
||||
instances = [i.to_dict() for i in self.instances.values() if stack_id==i.stack_id]
|
||||
return instances
|
||||
|
||||
def start_instance(self, instance_id):
|
||||
if instance_id not in self.instances:
|
||||
raise ResourceNotFoundException(
|
||||
"Unable to find instance with ID {0}".format(instance_id))
|
||||
self.instances[instance_id].start()
|
||||
|
||||
|
||||
opsworks_backends = {}
|
||||
for region, ec2_backend in ec2_backends.items():
|
||||
opsworks_backends[region] = OpsWorksBackend(ec2_backend)
|
112
moto/opsworks/responses.py
Normal file
112
moto/opsworks/responses.py
Normal file
@ -0,0 +1,112 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import opsworks_backends
|
||||
|
||||
|
||||
class OpsWorksResponse(BaseResponse):
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
return json.loads(self.body.decode("utf-8"))
|
||||
|
||||
@property
|
||||
def opsworks_backend(self):
|
||||
return opsworks_backends[self.region]
|
||||
|
||||
def create_stack(self):
|
||||
kwargs = dict(
|
||||
name=self.parameters.get("Name"),
|
||||
region=self.parameters.get("Region"),
|
||||
vpcid=self.parameters.get("VpcId"),
|
||||
attributes=self.parameters.get("Attributes"),
|
||||
default_instance_profile_arn=self.parameters.get("DefaultInstanceProfileArn"),
|
||||
default_os=self.parameters.get("DefaultOs"),
|
||||
hostname_theme=self.parameters.get("HostnameTheme"),
|
||||
default_availability_zone=self.parameters.get("DefaultAvailabilityZone"),
|
||||
default_subnet_id=self.parameters.get("DefaultInstanceProfileArn"),
|
||||
custom_json=self.parameters.get("CustomJson"),
|
||||
configuration_manager=self.parameters.get("ConfigurationManager"),
|
||||
chef_configuration=self.parameters.get("ChefConfiguration"),
|
||||
use_custom_cookbooks=self.parameters.get("UseCustomCookbooks"),
|
||||
use_opsworks_security_groups=self.parameters.get("UseOpsworksSecurityGroups"),
|
||||
custom_cookbooks_source=self.parameters.get("CustomCookbooksSource"),
|
||||
default_ssh_keyname=self.parameters.get("DefaultSshKeyName"),
|
||||
default_root_device_type=self.parameters.get("DefaultRootDeviceType"),
|
||||
service_role_arn=self.parameters.get("ServiceRoleArn"),
|
||||
agent_version=self.parameters.get("AgentVersion"),
|
||||
)
|
||||
stack = self.opsworks_backend.create_stack(**kwargs)
|
||||
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 create_instance(self):
|
||||
kwargs = dict(
|
||||
stack_id=self.parameters.get("StackId"),
|
||||
layer_ids=self.parameters.get("LayerIds"),
|
||||
instance_type=self.parameters.get("InstanceType"),
|
||||
auto_scale_type=self.parameters.get("AutoScalingType"),
|
||||
hostname=self.parameters.get("Hostname"),
|
||||
os=self.parameters.get("Os"),
|
||||
ami_id=self.parameters.get("AmiId"),
|
||||
ssh_keyname=self.parameters.get("SshKeyName"),
|
||||
availability_zone=self.parameters.get("AvailabilityZone"),
|
||||
virtualization_type=self.parameters.get("VirtualizationType"),
|
||||
subnet_id=self.parameters.get("SubnetId"),
|
||||
architecture=self.parameters.get("Architecture"),
|
||||
root_device_type=self.parameters.get("RootDeviceType"),
|
||||
block_device_mappings=self.parameters.get("BlockDeviceMappings"),
|
||||
install_updates_on_boot=self.parameters.get("InstallUpdatesOnBoot"),
|
||||
ebs_optimized=self.parameters.get("EbsOptimized"),
|
||||
agent_version=self.parameters.get("AgentVersion"),
|
||||
)
|
||||
opsworks_instance = self.opsworks_backend.create_instance(**kwargs)
|
||||
return json.dumps({"InstanceId": opsworks_instance.id}, indent=1)
|
||||
|
||||
def describe_stacks(self):
|
||||
stack_ids = self.parameters.get("StackIds")
|
||||
stacks = self.opsworks_backend.describe_stacks(stack_ids)
|
||||
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)
|
||||
|
||||
def describe_instances(self):
|
||||
instance_ids = self.parameters.get("InstanceIds")
|
||||
layer_id = self.parameters.get("LayerId")
|
||||
stack_id = self.parameters.get("StackId")
|
||||
instances = self.opsworks_backend.describe_instances(
|
||||
instance_ids, layer_id, stack_id)
|
||||
return json.dumps({"Instances": instances}, indent=1)
|
||||
|
||||
def start_instance(self):
|
||||
instance_id = self.parameters.get("InstanceId")
|
||||
self.opsworks_backend.start_instance(instance_id)
|
||||
return ""
|
12
moto/opsworks/urls.py
Normal file
12
moto/opsworks/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
from .responses import OpsWorksResponse
|
||||
|
||||
# AWS OpsWorks has a single endpoint: opsworks.us-east-1.amazonaws.com
|
||||
# and only supports HTTPS requests.
|
||||
url_bases = [
|
||||
"opsworks.us-east-1.amazonaws.com"
|
||||
]
|
||||
|
||||
url_paths = {
|
||||
'{0}/$': OpsWorksResponse.dispatch,
|
||||
}
|
176
tests/test_opsworks/test_instances.py
Normal file
176
tests/test_opsworks/test_instances.py
Normal file
@ -0,0 +1,176 @@
|
||||
from __future__ import unicode_literals
|
||||
import boto3
|
||||
import sure # noqa
|
||||
|
||||
from moto import mock_opsworks
|
||||
from moto import mock_ec2
|
||||
|
||||
|
||||
@mock_opsworks
|
||||
def test_create_instance():
|
||||
client = boto3.client('opsworks', region_name='us-east-1')
|
||||
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']
|
||||
|
||||
response = client.create_instance(
|
||||
StackId=stack_id, LayerIds=[layer_id], InstanceType="t2.micro"
|
||||
)
|
||||
|
||||
response.should.contain("InstanceId")
|
||||
|
||||
client.create_instance.when.called_with(
|
||||
StackId="nothere", LayerIds=[layer_id], InstanceType="t2.micro"
|
||||
).should.throw(Exception, "Unable to find stack with ID nothere")
|
||||
|
||||
client.create_instance.when.called_with(
|
||||
StackId=stack_id, LayerIds=["nothere"], InstanceType="t2.micro"
|
||||
).should.throw(Exception, "nothere")
|
||||
|
||||
|
||||
@mock_opsworks
|
||||
def test_describe_instances():
|
||||
"""
|
||||
create two stacks, with 1 layer and 2 layers (S1L1, S2L1, S2L2)
|
||||
|
||||
populate S1L1 with 2 instances (S1L1_i1, S1L1_i2)
|
||||
populate S2L1 with 1 instance (S2L1_i1)
|
||||
populate S2L2 with 3 instances (S2L2_i1..2)
|
||||
"""
|
||||
|
||||
client = boto3.client('opsworks', region_name='us-east-1')
|
||||
S1 = client.create_stack(
|
||||
Name="S1",
|
||||
Region="us-east-1",
|
||||
ServiceRoleArn="service_arn",
|
||||
DefaultInstanceProfileArn="profile_arn"
|
||||
)['StackId']
|
||||
S1L1 = client.create_layer(
|
||||
StackId=S1,
|
||||
Type="custom",
|
||||
Name="S1L1",
|
||||
Shortname="S1L1"
|
||||
)['LayerId']
|
||||
S2 = client.create_stack(
|
||||
Name="S2",
|
||||
Region="us-east-1",
|
||||
ServiceRoleArn="service_arn",
|
||||
DefaultInstanceProfileArn="profile_arn"
|
||||
)['StackId']
|
||||
S2L1 = client.create_layer(
|
||||
StackId=S2,
|
||||
Type="custom",
|
||||
Name="S2L1",
|
||||
Shortname="S2L1"
|
||||
)['LayerId']
|
||||
S2L2 = client.create_layer(
|
||||
StackId=S2,
|
||||
Type="custom",
|
||||
Name="S2L2",
|
||||
Shortname="S2L2"
|
||||
)['LayerId']
|
||||
|
||||
S1L1_i1 = client.create_instance(
|
||||
StackId=S1, LayerIds=[S1L1], InstanceType="t2.micro"
|
||||
)['InstanceId']
|
||||
S1L1_i2 = client.create_instance(
|
||||
StackId=S1, LayerIds=[S1L1], InstanceType="t2.micro"
|
||||
)['InstanceId']
|
||||
S2L1_i1 = client.create_instance(
|
||||
StackId=S2, LayerIds=[S2L1], InstanceType="t2.micro"
|
||||
)['InstanceId']
|
||||
S2L2_i1 = client.create_instance(
|
||||
StackId=S2, LayerIds=[S2L2], InstanceType="t2.micro"
|
||||
)['InstanceId']
|
||||
S2L2_i2 = client.create_instance(
|
||||
StackId=S2, LayerIds=[S2L2], InstanceType="t2.micro"
|
||||
)['InstanceId']
|
||||
|
||||
# instances in Stack 1
|
||||
response = client.describe_instances(StackId=S1)['Instances']
|
||||
response.should.have.length_of(2)
|
||||
S1L1_i1.should.be.within([i["InstanceId"] for i in response])
|
||||
S1L1_i2.should.be.within([i["InstanceId"] for i in response])
|
||||
|
||||
response2 = client.describe_instances(InstanceIds=[S1L1_i1, S1L1_i2])['Instances']
|
||||
sorted(response2, key=lambda d: d['InstanceId']).should.equal(
|
||||
sorted(response, key=lambda d: d['InstanceId']))
|
||||
|
||||
response3 = client.describe_instances(LayerId=S1L1)['Instances']
|
||||
sorted(response3, key=lambda d: d['InstanceId']).should.equal(
|
||||
sorted(response, key=lambda d: d['InstanceId']))
|
||||
|
||||
response = client.describe_instances(StackId=S1)['Instances']
|
||||
response.should.have.length_of(2)
|
||||
S1L1_i1.should.be.within([i["InstanceId"] for i in response])
|
||||
S1L1_i2.should.be.within([i["InstanceId"] for i in response])
|
||||
|
||||
# instances in Stack 2
|
||||
response = client.describe_instances(StackId=S2)['Instances']
|
||||
response.should.have.length_of(3)
|
||||
S2L1_i1.should.be.within([i["InstanceId"] for i in response])
|
||||
S2L2_i1.should.be.within([i["InstanceId"] for i in response])
|
||||
S2L2_i2.should.be.within([i["InstanceId"] for i in response])
|
||||
|
||||
response = client.describe_instances(LayerId=S2L1)['Instances']
|
||||
response.should.have.length_of(1)
|
||||
S2L1_i1.should.be.within([i["InstanceId"] for i in response])
|
||||
|
||||
response = client.describe_instances(LayerId=S2L2)['Instances']
|
||||
response.should.have.length_of(2)
|
||||
S2L1_i1.should_not.be.within([i["InstanceId"] for i in response])
|
||||
|
||||
|
||||
@mock_opsworks
|
||||
@mock_ec2
|
||||
def test_ec2_integration():
|
||||
"""
|
||||
instances created via OpsWorks should be discoverable via ec2
|
||||
"""
|
||||
|
||||
opsworks = boto3.client('opsworks', region_name='us-east-1')
|
||||
stack_id = opsworks.create_stack(
|
||||
Name="S1",
|
||||
Region="us-east-1",
|
||||
ServiceRoleArn="service_arn",
|
||||
DefaultInstanceProfileArn="profile_arn"
|
||||
)['StackId']
|
||||
|
||||
layer_id = opsworks.create_layer(
|
||||
StackId=stack_id,
|
||||
Type="custom",
|
||||
Name="S1L1",
|
||||
Shortname="S1L1"
|
||||
)['LayerId']
|
||||
|
||||
instance_id = opsworks.create_instance(
|
||||
StackId=stack_id, LayerIds=[layer_id], InstanceType="t2.micro"
|
||||
)['InstanceId']
|
||||
|
||||
ec2 = boto3.client('ec2', region_name='us-east-1')
|
||||
|
||||
# Before starting the instance, it shouldn't be discoverable via ec2
|
||||
reservations = ec2.describe_instances()['Reservations']
|
||||
assert reservations.should.be.empty
|
||||
|
||||
# After starting the instance, it should be discoverable via ec2
|
||||
opsworks.start_instance(InstanceId=instance_id)
|
||||
reservations = ec2.describe_instances()['Reservations']
|
||||
reservations[0]['Instances'].should.have.length_of(1)
|
||||
instance = reservations[0]['Instances'][0]
|
||||
opsworks_instance = opsworks.describe_instances(StackId=stack_id)['Instances'][0]
|
||||
|
||||
instance['InstanceId'].should.equal(opsworks_instance['Ec2InstanceId'])
|
||||
instance['PrivateIpAddress'].should.equal(opsworks_instance['PrivateIp'])
|
||||
|
||||
|
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', region_name='us-east-1')
|
||||
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', region_name='us-east-1')
|
||||
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")
|
||||
|
48
tests/test_opsworks/test_stack.py
Normal file
48
tests/test_opsworks/test_stack.py
Normal file
@ -0,0 +1,48 @@
|
||||
from __future__ import unicode_literals
|
||||
import boto3
|
||||
import sure # noqa
|
||||
import re
|
||||
|
||||
from moto import mock_opsworks
|
||||
|
||||
|
||||
@mock_opsworks
|
||||
def test_create_stack_response():
|
||||
client = boto3.client('opsworks', region_name='us-east-1')
|
||||
response = client.create_stack(
|
||||
Name="test_stack_1",
|
||||
Region="us-east-1",
|
||||
ServiceRoleArn="service_arn",
|
||||
DefaultInstanceProfileArn="profile_arn"
|
||||
)
|
||||
response.should.contain("StackId")
|
||||
|
||||
|
||||
@mock_opsworks
|
||||
def test_describe_stacks():
|
||||
client = boto3.client('opsworks', region_name='us-east-1')
|
||||
for i in range(1, 4):
|
||||
client.create_stack(
|
||||
Name="test_stack_{0}".format(i),
|
||||
Region="us-east-1",
|
||||
ServiceRoleArn="service_arn",
|
||||
DefaultInstanceProfileArn="profile_arn"
|
||||
)
|
||||
|
||||
response = client.describe_stacks()
|
||||
response['Stacks'].should.have.length_of(3)
|
||||
for stack in response['Stacks']:
|
||||
stack['ServiceRoleArn'].should.equal("service_arn")
|
||||
stack['DefaultInstanceProfileArn'].should.equal("profile_arn")
|
||||
|
||||
_id = response['Stacks'][0]['StackId']
|
||||
response = client.describe_stacks(StackIds=[_id])
|
||||
response['Stacks'].should.have.length_of(1)
|
||||
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