diff --git a/moto/opsworks/models.py b/moto/opsworks/models.py index fe8c882a7..4fe428c65 100644 --- a/moto/opsworks/models.py +++ b/moto/opsworks/models.py @@ -398,11 +398,82 @@ class Stack(BaseModel): return response +class App(BaseModel): + + def __init__(self, stack_id, name, type, + shortname=None, + description=None, + datasources=None, + app_source=None, + domains=None, + enable_ssl=False, + ssl_configuration=None, + attributes=None, + environment=None): + self.stack_id = stack_id + self.name = name + self.type = type + self.shortname = shortname + self.description = description + + self.datasources = datasources + if datasources is None: + self.datasources = [] + + self.app_source = app_source + if app_source is None: + self.app_source = {} + + self.domains = domains + if domains is None: + self.domains = [] + + self.enable_ssl = enable_ssl + + self.ssl_configuration = ssl_configuration + if ssl_configuration is None: + self.ssl_configuration = {} + + self.attributes = attributes + if attributes is None: + self.attributes = {} + + self.environment = environment + if environment is None: + self.environment = {} + + 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 = { + "AppId": self.id, + "AppSource": self.app_source, + "Attributes": self.attributes, + "CreatedAt": self.created_at.isoformat(), + "Datasources": self.datasources, + "Description": self.description, + "Domains": self.domains, + "EnableSsl": self.enable_ssl, + "Environment": self.environment, + "Name": self.name, + "Shortname": self.shortname, + "SslConfiguration": self.ssl_configuration, + "StackId": self.stack_id, + "Type": self.type + } + return d + + class OpsWorksBackend(BaseBackend): def __init__(self, ec2_backend): self.stacks = {} self.layers = {} + self.apps = {} self.instances = {} self.ec2_backend = ec2_backend @@ -435,6 +506,20 @@ class OpsWorksBackend(BaseBackend): self.stacks[stackid].layers.append(layer) return layer + def create_app(self, **kwargs): + name = kwargs['name'] + stackid = kwargs['stack_id'] + if stackid not in self.stacks: + raise ResourceNotFoundException(stackid) + if name in [a.name for a in self.stacks[stackid].apps]: + raise ValidationException( + 'There is already an app named "{0}" ' + 'for this stack'.format(name)) + app = App(**kwargs) + self.apps[app.id] = app + self.stacks[stackid].apps.append(app) + return app + def create_instance(self, **kwargs): stack_id = kwargs['stack_id'] layer_ids = kwargs['layer_ids'] @@ -502,6 +587,22 @@ class OpsWorksBackend(BaseBackend): raise ResourceNotFoundException(", ".join(unknown_layers)) return [self.layers[id].to_dict() for id in layer_ids] + def describe_apps(self, stack_id, app_ids): + if stack_id is not None and app_ids is not None: + raise ValidationException( + "Please provide one or more app 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 [app.to_dict() for app in self.stacks[stack_id].apps] + + unknown_apps = set(app_ids) - set(self.apps.keys()) + if unknown_apps: + raise ResourceNotFoundException(", ".join(unknown_apps)) + return [self.apps[id].to_dict() for id in app_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 " diff --git a/moto/opsworks/responses.py b/moto/opsworks/responses.py index 42e0f2c5c..c9f8fe125 100644 --- a/moto/opsworks/responses.py +++ b/moto/opsworks/responses.py @@ -75,6 +75,24 @@ class OpsWorksResponse(BaseResponse): layer = self.opsworks_backend.create_layer(**kwargs) return json.dumps({"LayerId": layer.id}, indent=1) + def create_app(self): + kwargs = dict( + stack_id=self.parameters.get('StackId'), + name=self.parameters.get('Name'), + type=self.parameters.get('Type'), + shortname=self.parameters.get('Shortname'), + description=self.parameters.get('Description'), + datasources=self.parameters.get('DataSources'), + app_source=self.parameters.get('AppSource'), + domains=self.parameters.get('Domains'), + enable_ssl=self.parameters.get('EnableSsl'), + ssl_configuration=self.parameters.get('SslConfiguration'), + attributes=self.parameters.get('Attributes'), + environment=self.parameters.get('Environment') + ) + app = self.opsworks_backend.create_app(**kwargs) + return json.dumps({"AppId": app.id}, indent=1) + def create_instance(self): kwargs = dict( stack_id=self.parameters.get("StackId"), @@ -110,6 +128,12 @@ class OpsWorksResponse(BaseResponse): layers = self.opsworks_backend.describe_layers(stack_id, layer_ids) return json.dumps({"Layers": layers}, indent=1) + def describe_apps(self): + stack_id = self.parameters.get("StackId") + app_ids = self.parameters.get("AppIds") + apps = self.opsworks_backend.describe_apps(stack_id, app_ids) + return json.dumps({"Apps": apps}, indent=1) + def describe_instances(self): instance_ids = self.parameters.get("InstanceIds") layer_id = self.parameters.get("LayerId") diff --git a/tests/test_opsworks/test_apps.py b/tests/test_opsworks/test_apps.py new file mode 100644 index 000000000..37d0f2fe4 --- /dev/null +++ b/tests/test_opsworks/test_apps.py @@ -0,0 +1,102 @@ +from __future__ import unicode_literals +import boto3 +from freezegun import freeze_time +import sure # noqa +import re + +from moto import mock_opsworks + + +@freeze_time("2015-01-01") +@mock_opsworks +def test_create_app_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_app( + StackId=stack_id, + Type="other", + Name="TestApp" + ) + + response.should.contain("AppId") + + second_stack_id = client.create_stack( + Name="test_stack_2", + Region="us-east-1", + ServiceRoleArn="service_arn", + DefaultInstanceProfileArn="profile_arn" + )['StackId'] + + response = client.create_app( + StackId=second_stack_id, + Type="other", + Name="TestApp" + ) + + response.should.contain("AppId") + + # ClientError + client.create_app.when.called_with( + StackId=stack_id, + Type="other", + Name="TestApp" + ).should.throw( + Exception, re.compile(r'already an app named "TestApp"') + ) + + # ClientError + client.create_app.when.called_with( + StackId="nothere", + Type="other", + Name="TestApp" + ).should.throw( + Exception, "nothere" + ) + +@freeze_time("2015-01-01") +@mock_opsworks +def test_describe_apps(): + 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'] + app_id = client.create_app( + StackId=stack_id, + Type="other", + Name="TestApp" + )['AppId'] + + rv1 = client.describe_apps(StackId=stack_id) + rv2 = client.describe_apps(AppIds=[app_id]) + rv1['Apps'].should.equal(rv2['Apps']) + + rv1['Apps'][0]['Name'].should.equal("TestApp") + + # ClientError + client.describe_apps.when.called_with( + StackId=stack_id, + AppIds=[app_id] + ).should.throw( + Exception, "Please provide one or more app IDs or a stack ID" + ) + # ClientError + client.describe_apps.when.called_with( + StackId="nothere" + ).should.throw( + Exception, "Unable to find stack with ID nothere" + ) + # ClientError + client.describe_apps.when.called_with( + AppIds=["nothere"] + ).should.throw( + Exception, "nothere" + )