From 77f0a61c9f89202ae09bb7c5ed1e325139ffc516 Mon Sep 17 00:00:00 2001 From: TheDooner64 Date: Tue, 10 Jul 2018 13:50:47 -0400 Subject: [PATCH 1/9] Add scaffolding for Glue service, including create_database and get_database for the Glue Data Catalog --- moto/__init__.py | 1 + moto/glue/__init__.py | 5 +++++ moto/glue/exceptions.py | 9 ++++++++ moto/glue/models.py | 27 ++++++++++++++++++++++++ moto/glue/responses.py | 27 ++++++++++++++++++++++++ moto/glue/urls.py | 11 ++++++++++ moto/glue/utils.py | 1 + tests/test_glue/test_datacatalog.py | 30 +++++++++++++++++++++++++++ tests/test_s3/test_s3_storageclass.py | 3 --- tests/test_s3/test_s3_utils.py | 1 - 10 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 moto/glue/__init__.py create mode 100644 moto/glue/exceptions.py create mode 100644 moto/glue/models.py create mode 100644 moto/glue/responses.py create mode 100644 moto/glue/urls.py create mode 100644 moto/glue/utils.py create mode 100644 tests/test_glue/test_datacatalog.py diff --git a/moto/__init__.py b/moto/__init__.py index 0ce5e54d1..e5881cfca 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -24,6 +24,7 @@ from .elbv2 import mock_elbv2 # flake8: noqa from .emr import mock_emr, mock_emr_deprecated # flake8: noqa from .events import mock_events # flake8: noqa from .glacier import mock_glacier, mock_glacier_deprecated # flake8: noqa +from .glue import mock_glue # flake8: noqa from .iam import mock_iam, mock_iam_deprecated # flake8: noqa from .kinesis import mock_kinesis, mock_kinesis_deprecated # flake8: noqa from .kms import mock_kms, mock_kms_deprecated # flake8: noqa diff --git a/moto/glue/__init__.py b/moto/glue/__init__.py new file mode 100644 index 000000000..6b1f13326 --- /dev/null +++ b/moto/glue/__init__.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals +from .models import glue_backend + +glue_backends = {"global": glue_backend} +mock_glue = glue_backend.decorator diff --git a/moto/glue/exceptions.py b/moto/glue/exceptions.py new file mode 100644 index 000000000..0c8760f18 --- /dev/null +++ b/moto/glue/exceptions.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +from moto.core.exceptions import RESTError + + +class GlueClientError(RESTError): + + def __init__(self, *args, **kwargs): + kwargs.setdefault('template', 'single_error') + super(GlueClientError, self).__init__(*args, **kwargs) diff --git a/moto/glue/models.py b/moto/glue/models.py new file mode 100644 index 000000000..55cd46bcb --- /dev/null +++ b/moto/glue/models.py @@ -0,0 +1,27 @@ +from __future__ import unicode_literals + +from moto.core import BaseBackend, BaseModel +from moto.compat import OrderedDict + + +class GlueBackend(BaseBackend): + + def __init__(self): + self.databases = OrderedDict() + + def create_database(self, database_name): + database = FakeDatabase(database_name) + self.databases[database_name] = database + return database + + def get_database(self, database_name): + return self.databases[database_name] + + +class FakeDatabase(BaseModel): + + def __init__(self, database_name): + self.name = database_name + + +glue_backend = GlueBackend() diff --git a/moto/glue/responses.py b/moto/glue/responses.py new file mode 100644 index 000000000..f3ef6eb4d --- /dev/null +++ b/moto/glue/responses.py @@ -0,0 +1,27 @@ +from __future__ import unicode_literals + +import json + +from moto.core.responses import BaseResponse +from .models import glue_backend + + +class GlueResponse(BaseResponse): + + @property + def glue_backend(self): + return glue_backend + + @property + def parameters(self): + return json.loads(self.body) + + def create_database(self): + database_name = self.parameters['DatabaseInput']['Name'] + self.glue_backend.create_database(database_name) + return "" + + def get_database(self): + database_name = self.parameters.get('Name') + database = self.glue_backend.get_database(database_name) + return json.dumps({'Database': {'Name': database.name}}) diff --git a/moto/glue/urls.py b/moto/glue/urls.py new file mode 100644 index 000000000..f3eaa9cad --- /dev/null +++ b/moto/glue/urls.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals + +from .responses import GlueResponse + +url_bases = [ + "https?://glue(.*).amazonaws.com" +] + +url_paths = { + '{0}/$': GlueResponse.dispatch +} diff --git a/moto/glue/utils.py b/moto/glue/utils.py new file mode 100644 index 000000000..baffc4882 --- /dev/null +++ b/moto/glue/utils.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/tests/test_glue/test_datacatalog.py b/tests/test_glue/test_datacatalog.py new file mode 100644 index 000000000..77ad1c013 --- /dev/null +++ b/tests/test_glue/test_datacatalog.py @@ -0,0 +1,30 @@ +from __future__ import unicode_literals + +import sure # noqa +import boto3 + +from moto import mock_glue + + +def create_database(client, database_name): + return client.create_database( + DatabaseInput={ + 'Name': database_name + } + ) + + +def get_database(client, database_name): + return client.get_database(Name=database_name) + + +@mock_glue +def test_create_database(): + client = boto3.client('glue', region_name='us-east-1') + database_name = 'myspecialdatabase' + create_database(client, database_name) + + response = get_database(client, database_name) + database = response['Database'] + + database.should.equal({'Name': database_name}) diff --git a/tests/test_s3/test_s3_storageclass.py b/tests/test_s3/test_s3_storageclass.py index c4c83a285..2ed966022 100644 --- a/tests/test_s3/test_s3_storageclass.py +++ b/tests/test_s3/test_s3_storageclass.py @@ -101,6 +101,3 @@ def test_s3_default_storage_class(): # tests that the default storage class is still STANDARD list_of_objects["Contents"][0]["StorageClass"].should.equal("STANDARD") - - - diff --git a/tests/test_s3/test_s3_utils.py b/tests/test_s3/test_s3_utils.py index 9cda1f157..d874a0f1e 100644 --- a/tests/test_s3/test_s3_utils.py +++ b/tests/test_s3/test_s3_utils.py @@ -21,7 +21,6 @@ def test_force_ignore_subdomain_for_bucketnames(): os.environ['S3_IGNORE_SUBDOMAIN_BUCKETNAME'] = '1' expect(bucket_name_from_url('https://subdomain.localhost:5000/abc/resource')).should.equal(None) del(os.environ['S3_IGNORE_SUBDOMAIN_BUCKETNAME']) - def test_versioned_key_store(): From e67a8c6f1bc9079d6aa61da5bca54cf6533b7e4d Mon Sep 17 00:00:00 2001 From: TheDooner64 Date: Tue, 10 Jul 2018 13:52:53 -0400 Subject: [PATCH 2/9] Revert minor changes to s3 tests --- tests/test_s3/test_s3_storageclass.py | 3 +++ tests/test_s3/test_s3_utils.py | 1 + 2 files changed, 4 insertions(+) diff --git a/tests/test_s3/test_s3_storageclass.py b/tests/test_s3/test_s3_storageclass.py index 2ed966022..99908c501 100644 --- a/tests/test_s3/test_s3_storageclass.py +++ b/tests/test_s3/test_s3_storageclass.py @@ -101,3 +101,6 @@ def test_s3_default_storage_class(): # tests that the default storage class is still STANDARD list_of_objects["Contents"][0]["StorageClass"].should.equal("STANDARD") + + + diff --git a/tests/test_s3/test_s3_utils.py b/tests/test_s3/test_s3_utils.py index d874a0f1e..ce9f54c75 100644 --- a/tests/test_s3/test_s3_utils.py +++ b/tests/test_s3/test_s3_utils.py @@ -23,6 +23,7 @@ def test_force_ignore_subdomain_for_bucketnames(): del(os.environ['S3_IGNORE_SUBDOMAIN_BUCKETNAME']) + def test_versioned_key_store(): d = _VersionedKeyStore() From c5c57efbb5d08828dea08822c90b292d0d183cdb Mon Sep 17 00:00:00 2001 From: TheDooner64 Date: Wed, 11 Jul 2018 11:39:40 -0400 Subject: [PATCH 3/9] Creating a database that already exists in the glue data catalog raises an exception --- moto/glue/exceptions.py | 16 +++++++++++----- moto/glue/models.py | 4 ++++ tests/test_glue/test_datacatalog.py | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/moto/glue/exceptions.py b/moto/glue/exceptions.py index 0c8760f18..2f9d16d26 100644 --- a/moto/glue/exceptions.py +++ b/moto/glue/exceptions.py @@ -1,9 +1,15 @@ from __future__ import unicode_literals -from moto.core.exceptions import RESTError +from moto.core.exceptions import JsonRESTError -class GlueClientError(RESTError): +class GlueClientError(JsonRESTError): + code = 400 - def __init__(self, *args, **kwargs): - kwargs.setdefault('template', 'single_error') - super(GlueClientError, self).__init__(*args, **kwargs) + +class DatabaseAlreadyExistsException(GlueClientError): + def __init__(self): + self.code = 400 + super(DatabaseAlreadyExistsException, self).__init__( + 'DatabaseAlreadyExistsException', + 'Database already exists.' + ) diff --git a/moto/glue/models.py b/moto/glue/models.py index 55cd46bcb..357a2a52d 100644 --- a/moto/glue/models.py +++ b/moto/glue/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from moto.core import BaseBackend, BaseModel from moto.compat import OrderedDict +from.exceptions import DatabaseAlreadyExistsException class GlueBackend(BaseBackend): @@ -10,6 +11,9 @@ class GlueBackend(BaseBackend): self.databases = OrderedDict() def create_database(self, database_name): + if database_name in self.databases: + raise DatabaseAlreadyExistsException() + database = FakeDatabase(database_name) self.databases[database_name] = database return database diff --git a/tests/test_glue/test_datacatalog.py b/tests/test_glue/test_datacatalog.py index 77ad1c013..c7cdb1a7c 100644 --- a/tests/test_glue/test_datacatalog.py +++ b/tests/test_glue/test_datacatalog.py @@ -1,7 +1,9 @@ from __future__ import unicode_literals import sure # noqa +from nose.tools import assert_raises import boto3 +from botocore.client import ClientError from moto import mock_glue @@ -28,3 +30,15 @@ def test_create_database(): database = response['Database'] database.should.equal({'Name': database_name}) + + +@mock_glue +def test_create_database_already_exists(): + client = boto3.client('glue', region_name='us-east-1') + database_name = 'anewdatabase' + create_database(client, database_name) + + with assert_raises(ClientError) as exc: + create_database(client, database_name) + + exc.exception.response['Error']['Code'].should.equal('DatabaseAlreadyExistsException') From d988ee15fe9d587c8cf0b62e1eaf2c277b7a58d3 Mon Sep 17 00:00:00 2001 From: TheDooner64 Date: Thu, 26 Jul 2018 17:05:09 -0400 Subject: [PATCH 4/9] Add create_table, get_table, and get_tables for the Glue Data Catalog --- moto/glue/exceptions.py | 9 +++ moto/glue/models.py | 31 +++++++- moto/glue/responses.py | 36 +++++++++ tests/test_glue/__init__.py | 1 + tests/test_glue/fixtures/__init__.py | 1 + tests/test_glue/fixtures/datacatalog.py | 31 ++++++++ tests/test_glue/helpers.py | 46 ++++++++++++ tests/test_glue/test_datacatalog.py | 98 ++++++++++++++++++++----- 8 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 tests/test_glue/__init__.py create mode 100644 tests/test_glue/fixtures/__init__.py create mode 100644 tests/test_glue/fixtures/datacatalog.py create mode 100644 tests/test_glue/helpers.py diff --git a/moto/glue/exceptions.py b/moto/glue/exceptions.py index 2f9d16d26..62ea1525c 100644 --- a/moto/glue/exceptions.py +++ b/moto/glue/exceptions.py @@ -13,3 +13,12 @@ class DatabaseAlreadyExistsException(GlueClientError): 'DatabaseAlreadyExistsException', 'Database already exists.' ) + + +class TableAlreadyExistsException(GlueClientError): + def __init__(self): + self.code = 400 + super(TableAlreadyExistsException, self).__init__( + 'TableAlreadyExistsException', + 'Table already exists.' + ) diff --git a/moto/glue/models.py b/moto/glue/models.py index 357a2a52d..9f7e7657d 100644 --- a/moto/glue/models.py +++ b/moto/glue/models.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from moto.core import BaseBackend, BaseModel from moto.compat import OrderedDict -from.exceptions import DatabaseAlreadyExistsException +from.exceptions import DatabaseAlreadyExistsException, TableAlreadyExistsException class GlueBackend(BaseBackend): @@ -21,11 +21,40 @@ class GlueBackend(BaseBackend): def get_database(self, database_name): return self.databases[database_name] + def create_table(self, database_name, table_name, table_input): + database = self.get_database(database_name) + + if table_name in database.tables: + raise TableAlreadyExistsException() + + table = FakeTable(database_name, table_name, table_input) + database.tables[table_name] = table + return table + + def get_table(self, database_name, table_name): + database = self.get_database(database_name) + return database.tables[table_name] + + def get_tables(self, database_name): + database = self.get_database(database_name) + return [table for table_name, table in database.tables.iteritems()] + class FakeDatabase(BaseModel): def __init__(self, database_name): self.name = database_name + self.tables = OrderedDict() + + +class FakeTable(BaseModel): + + def __init__(self, database_name, table_name, table_input): + self.database_name = database_name + self.name = table_name + self.table_input = table_input + self.storage_descriptor = self.table_input.get('StorageDescriptor', {}) + self.partition_keys = self.table_input.get('PartitionKeys', []) glue_backend = GlueBackend() diff --git a/moto/glue/responses.py b/moto/glue/responses.py index f3ef6eb4d..bb64c40d4 100644 --- a/moto/glue/responses.py +++ b/moto/glue/responses.py @@ -25,3 +25,39 @@ class GlueResponse(BaseResponse): database_name = self.parameters.get('Name') database = self.glue_backend.get_database(database_name) return json.dumps({'Database': {'Name': database.name}}) + + def create_table(self): + database_name = self.parameters.get('DatabaseName') + table_input = self.parameters.get('TableInput') + table_name = table_input.get('Name') + self.glue_backend.create_table(database_name, table_name, table_input) + return "" + + def get_table(self): + database_name = self.parameters.get('DatabaseName') + table_name = self.parameters.get('Name') + table = self.glue_backend.get_table(database_name, table_name) + return json.dumps({ + 'Table': { + 'DatabaseName': table.database_name, + 'Name': table.name, + 'PartitionKeys': table.partition_keys, + 'StorageDescriptor': table.storage_descriptor + } + }) + + def get_tables(self): + database_name = self.parameters.get('DatabaseName') + tables = self.glue_backend.get_tables(database_name) + return json.dumps( + { + 'TableList': [ + { + 'DatabaseName': table.database_name, + 'Name': table.name, + 'PartitionKeys': table.partition_keys, + 'StorageDescriptor': table.storage_descriptor + } for table in tables + ] + } + ) diff --git a/tests/test_glue/__init__.py b/tests/test_glue/__init__.py new file mode 100644 index 000000000..baffc4882 --- /dev/null +++ b/tests/test_glue/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/tests/test_glue/fixtures/__init__.py b/tests/test_glue/fixtures/__init__.py new file mode 100644 index 000000000..baffc4882 --- /dev/null +++ b/tests/test_glue/fixtures/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/tests/test_glue/fixtures/datacatalog.py b/tests/test_glue/fixtures/datacatalog.py new file mode 100644 index 000000000..b2efe4154 --- /dev/null +++ b/tests/test_glue/fixtures/datacatalog.py @@ -0,0 +1,31 @@ +from __future__ import unicode_literals + +TABLE_INPUT = { + 'Owner': 'a_fake_owner', + 'Parameters': { + 'EXTERNAL': 'TRUE', + }, + 'Retention': 0, + 'StorageDescriptor': { + 'BucketColumns': [], + 'Compressed': False, + 'InputFormat': 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat', + 'NumberOfBuckets': -1, + 'OutputFormat': 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat', + 'Parameters': {}, + 'SerdeInfo': { + 'Parameters': { + 'serialization.format': '1' + }, + 'SerializationLibrary': 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe' + }, + 'SkewedInfo': { + 'SkewedColumnNames': [], + 'SkewedColumnValueLocationMaps': {}, + 'SkewedColumnValues': [] + }, + 'SortColumns': [], + 'StoredAsSubDirectories': False + }, + 'TableType': 'EXTERNAL_TABLE', +} diff --git a/tests/test_glue/helpers.py b/tests/test_glue/helpers.py new file mode 100644 index 000000000..4a51f9117 --- /dev/null +++ b/tests/test_glue/helpers.py @@ -0,0 +1,46 @@ +from __future__ import unicode_literals + +import copy + +from .fixtures.datacatalog import TABLE_INPUT + + +def create_database(client, database_name): + return client.create_database( + DatabaseInput={ + 'Name': database_name + } + ) + + +def get_database(client, database_name): + return client.get_database(Name=database_name) + + +def create_table_input(table_name, s3_location, columns=[], partition_keys=[]): + table_input = copy.deepcopy(TABLE_INPUT) + table_input['Name'] = table_name + table_input['PartitionKeys'] = partition_keys + table_input['StorageDescriptor']['Columns'] = columns + table_input['StorageDescriptor']['Location'] = s3_location + return table_input + + +def create_table(client, database_name, table_name, table_input): + return client.create_table( + DatabaseName=database_name, + TableInput=table_input + ) + + +def get_table(client, database_name, table_name): + return client.get_table( + DatabaseName=database_name, + Name=table_name + ) + + +def get_tables(client, database_name): + return client.get_tables( + DatabaseName=database_name + ) diff --git a/tests/test_glue/test_datacatalog.py b/tests/test_glue/test_datacatalog.py index c7cdb1a7c..7dabeb1f3 100644 --- a/tests/test_glue/test_datacatalog.py +++ b/tests/test_glue/test_datacatalog.py @@ -6,27 +6,16 @@ import boto3 from botocore.client import ClientError from moto import mock_glue - - -def create_database(client, database_name): - return client.create_database( - DatabaseInput={ - 'Name': database_name - } - ) - - -def get_database(client, database_name): - return client.get_database(Name=database_name) +from . import helpers @mock_glue def test_create_database(): client = boto3.client('glue', region_name='us-east-1') database_name = 'myspecialdatabase' - create_database(client, database_name) + helpers.create_database(client, database_name) - response = get_database(client, database_name) + response = helpers.get_database(client, database_name) database = response['Database'] database.should.equal({'Name': database_name}) @@ -35,10 +24,85 @@ def test_create_database(): @mock_glue def test_create_database_already_exists(): client = boto3.client('glue', region_name='us-east-1') - database_name = 'anewdatabase' - create_database(client, database_name) + database_name = 'cantcreatethisdatabasetwice' + helpers.create_database(client, database_name) with assert_raises(ClientError) as exc: - create_database(client, database_name) + helpers.create_database(client, database_name) exc.exception.response['Error']['Code'].should.equal('DatabaseAlreadyExistsException') + + +@mock_glue +def test_create_table(): + client = boto3.client('glue', region_name='us-east-1') + database_name = 'myspecialdatabase' + helpers.create_database(client, database_name) + + table_name = 'myspecialtable' + s3_location = 's3://my-bucket/{database_name}/{table_name}'.format( + database_name=database_name, + table_name=table_name + ) + + table_input = helpers.create_table_input(table_name, s3_location) + helpers.create_table(client, database_name, table_name, table_input) + + response = helpers.get_table(client, database_name, table_name) + table = response['Table'] + + table['Name'].should.equal(table_input['Name']) + table['StorageDescriptor'].should.equal(table_input['StorageDescriptor']) + table['PartitionKeys'].should.equal(table_input['PartitionKeys']) + + +@mock_glue +def test_create_table_already_exists(): + client = boto3.client('glue', region_name='us-east-1') + database_name = 'myspecialdatabase' + helpers.create_database(client, database_name) + + table_name = 'cantcreatethistabletwice' + s3_location = 's3://my-bucket/{database_name}/{table_name}'.format( + database_name=database_name, + table_name=table_name + ) + + table_input = helpers.create_table_input(table_name, s3_location) + helpers.create_table(client, database_name, table_name, table_input) + + with assert_raises(ClientError) as exc: + helpers.create_table(client, database_name, table_name, table_input) + + exc.exception.response['Error']['Code'].should.equal('TableAlreadyExistsException') + + +@mock_glue +def test_get_tables(): + client = boto3.client('glue', region_name='us-east-1') + database_name = 'myspecialdatabase' + helpers.create_database(client, database_name) + + table_names = ['myfirsttable', 'mysecondtable', 'mythirdtable'] + table_inputs = {} + + for table_name in table_names: + s3_location = 's3://my-bucket/{database_name}/{table_name}'.format( + database_name=database_name, + table_name=table_name + ) + table_input = helpers.create_table_input(table_name, s3_location) + table_inputs[table_name] = table_input + helpers.create_table(client, database_name, table_name, table_input) + + response = helpers.get_tables(client, database_name) + + tables = response['TableList'] + + assert len(tables) == 3 + + for table in tables: + table_name = table['Name'] + table_name.should.equal(table_inputs[table_name]['Name']) + table['StorageDescriptor'].should.equal(table_inputs[table_name]['StorageDescriptor']) + table['PartitionKeys'].should.equal(table_inputs[table_name]['PartitionKeys']) From 9339a476d2b2c7d23d254c11fa474f6e69426611 Mon Sep 17 00:00:00 2001 From: TheDooner64 Date: Sun, 5 Aug 2018 19:46:40 -0400 Subject: [PATCH 5/9] Adjust glue get_tables method to use items instead of iteritems --- moto/glue/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/glue/models.py b/moto/glue/models.py index 9f7e7657d..09b7d60ed 100644 --- a/moto/glue/models.py +++ b/moto/glue/models.py @@ -37,7 +37,7 @@ class GlueBackend(BaseBackend): def get_tables(self, database_name): database = self.get_database(database_name) - return [table for table_name, table in database.tables.iteritems()] + return [table for table_name, table in database.tables.items()] class FakeDatabase(BaseModel): From a42006462164347efaa45712590f9c26158103d7 Mon Sep 17 00:00:00 2001 From: Will Bengtson Date: Tue, 7 Aug 2018 10:31:36 -0700 Subject: [PATCH 6/9] IAM get account authorization details (#1736) * start of get_account_authorization_details for iam * add get_account_authorization_details dynamic template * remove old commented out template * Fix flake8 problems and add unit test --- moto/iam/models.py | 27 +++++++ moto/iam/responses.py | 153 +++++++++++++++++++++++++++++++++++++ tests/test_iam/test_iam.py | 65 ++++++++++++++++ 3 files changed, 245 insertions(+) diff --git a/moto/iam/models.py b/moto/iam/models.py index 8b632e555..697be7988 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -905,5 +905,32 @@ class IAMBackend(BaseBackend): def delete_account_alias(self, alias): self.account_aliases = [] + def get_account_authorization_details(self, filter): + policies = self.managed_policies.values() + local_policies = set(policies) - set(aws_managed_policies) + returned_policies = [] + + if len(filter) == 0: + return { + 'instance_profiles': self.instance_profiles.values(), + 'roles': self.roles.values(), + 'groups': self.groups.values(), + 'users': self.users.values(), + 'managed_policies': self.managed_policies.values() + } + + if 'AWSManagedPolicy' in filter: + returned_policies = aws_managed_policies + if 'LocalManagedPolicy' in filter: + returned_policies = returned_policies + list(local_policies) + + return { + 'instance_profiles': self.instance_profiles.values(), + 'roles': self.roles.values() if 'Role' in filter else [], + 'groups': self.groups.values() if 'Group' in filter else [], + 'users': self.users.values() if 'User' in filter else [], + 'managed_policies': returned_policies + } + iam_backend = IAMBackend() diff --git a/moto/iam/responses.py b/moto/iam/responses.py index 786afab08..9c1241c36 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -534,6 +534,18 @@ class IamResponse(BaseResponse): template = self.response_template(DELETE_ACCOUNT_ALIAS_TEMPLATE) return template.render() + def get_account_authorization_details(self): + filter_param = self._get_multi_param('Filter.member') + account_details = iam_backend.get_account_authorization_details(filter_param) + template = self.response_template(GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE) + return template.render( + instance_profiles=account_details['instance_profiles'], + policies=account_details['managed_policies'], + users=account_details['users'], + groups=account_details['groups'], + roles=account_details['roles'] + ) + ATTACH_ROLE_POLICY_TEMPLATE = """ @@ -1309,3 +1321,144 @@ DELETE_ACCOUNT_ALIAS_TEMPLATE = """ + + + {% for group in groups %} + + {{ group.path }} + {{ group.name }} + {{ group.id }} + {{ group.arn }} + + {% endfor %} + + false + + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + + +GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """ + + false + + {% for user in users %} + + + + {{ user.id }} + {{ user.path }} + {{ user.name }} + {{ user.arn }} + 2012-05-09T15:45:35Z + + {% endfor %} + + + EXAMPLEkakv9BCuUNFDtxWSyfzetYwEx2ADc8dnzfvERF5S6YMvXKx41t6gCl/eeaCX3Jo94/ + bKqezEAg8TEVS99EKFLxm3jtbpl25FDWEXAMPLE + + + {% for group in groups %} + + {{ group.id }} + + {% for policy in group.managed_policies %} + + {{ policy.name }} + {{ policy.arn }} + + {% endfor %} + + {{ group.name }} + {{ group.path }} + {{ group.arn }} + 2012-05-09T16:27:11Z + + + {% endfor %} + + + {% for role in roles %} + + + + {% for policy in role.managed_policies %} + + {{ policy.name }} + {{ policy.arn }} + + {% endfor %} + + + {% for profile in instance_profiles %} + + {{ profile.id }} + + {% for role in profile.roles %} + + {{ role.path }} + {{ role.arn }} + {{ role.name }} + {{ role.assume_role_policy_document }} + 2012-05-09T15:45:35Z + {{ role.id }} + + {% endfor %} + + {{ profile.name }} + {{ profile.path }} + {{ profile.arn }} + 2012-05-09T16:27:11Z + + {% endfor %} + + {{ role.path }} + {{ role.arn }} + {{ role.name }} + {{ role.assume_role_policy_document }} + 2014-07-30T17:09:20Z + {{ role.id }} + + {% endfor %} + + + {% for policy in policies %} + + {{ policy.name }} + {{ policy.default_version_id }} + {{ policy.id }} + {{ policy.path }} + + + + {"Version":"2012-10-17","Statement":{"Effect":"Allow", + "Action":["iam:CreatePolicy","iam:CreatePolicyVersion", + "iam:DeletePolicy","iam:DeletePolicyVersion","iam:GetPolicy", + "iam:GetPolicyVersion","iam:ListPolicies", + "iam:ListPolicyVersions","iam:SetDefaultPolicyVersion"], + "Resource":"*"}} + + true + v1 + 2012-05-09T16:27:11Z + + + {{ policy.arn }} + 1 + 2012-05-09T16:27:11Z + true + 2012-05-09T16:27:11Z + + {% endfor %} + + + + 92e79ae7-7399-11e4-8c85-4b53eEXAMPLE + +""" diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 182a60661..2225f0644 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -678,3 +678,68 @@ def test_update_access_key(): Status='Inactive') resp = client.list_access_keys(UserName=username) resp['AccessKeyMetadata'][0]['Status'].should.equal('Inactive') + + +@mock_iam +def test_get_account_authorization_details(): + import json + conn = boto3.client('iam', region_name='us-east-1') + conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/") + conn.create_user(Path='/', UserName='testCloudAuxUser') + conn.create_group(Path='/', GroupName='testCloudAuxGroup') + conn.create_policy( + PolicyName='testCloudAuxPolicy', + Path='/', + PolicyDocument=json.dumps({ + "Version": "2012-10-17", + "Statement": [ + { + "Action": "s3:ListBucket", + "Resource": "*", + "Effect": "Allow", + } + ] + }), + Description='Test CloudAux Policy' + ) + + result = conn.get_account_authorization_details(Filter=['Role']) + len(result['RoleDetailList']) == 1 + len(result['UserDetailList']) == 0 + len(result['GroupDetailList']) == 0 + len(result['Policies']) == 0 + + result = conn.get_account_authorization_details(Filter=['User']) + len(result['RoleDetailList']) == 0 + len(result['UserDetailList']) == 1 + len(result['GroupDetailList']) == 0 + len(result['Policies']) == 0 + + result = conn.get_account_authorization_details(Filter=['Group']) + len(result['RoleDetailList']) == 0 + len(result['UserDetailList']) == 0 + len(result['GroupDetailList']) == 1 + len(result['Policies']) == 0 + + result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy']) + len(result['RoleDetailList']) == 0 + len(result['UserDetailList']) == 0 + len(result['GroupDetailList']) == 0 + len(result['Policies']) == 1 + + # Check for greater than 1 since this should always be greater than one but might change. + # See iam/aws_managed_policies.py + result = conn.get_account_authorization_details(Filter=['AWSManagedPolicy']) + len(result['RoleDetailList']) == 0 + len(result['UserDetailList']) == 0 + len(result['GroupDetailList']) == 0 + len(result['Policies']) > 1 + + result = conn.get_account_authorization_details() + len(result['RoleDetailList']) == 1 + len(result['UserDetailList']) == 1 + len(result['GroupDetailList']) == 1 + len(result['Policies']) > 1 + + + From ba9e795394ea0c6490e1a7aa4bfef69d8c1b42bc Mon Sep 17 00:00:00 2001 From: Jack Danger Date: Tue, 7 Aug 2018 10:53:21 -0700 Subject: [PATCH 7/9] Version 1.3.4 (#1757) * bumping to version 1.3.4 * updating changelog * fixing generation of implementation coverage --- .bumpversion.cfg | 2 +- CHANGELOG.md | 8 ++ IMPLEMENTATION_COVERAGE.md | 113 ++++++----------------------- moto/__init__.py | 2 +- scripts/implementation_coverage.py | 29 +++++--- setup.py | 2 +- 6 files changed, 49 insertions(+), 107 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 3e15854ef..479af4af8 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.3.3 +current_version = 1.3.4 [bumpversion:file:setup.py] diff --git a/CHANGELOG.md b/CHANGELOG.md index fb3a5d8d5..0bf5d2535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Moto Changelog =================== +1.3.4 +------ + + * IAM get account authorization details + * adding account id to ManagedPolicy ARN + * APIGateway usage plans and usage plan keys + * ECR list images + 1.3.3 ------ diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 411f55a8b..98a216c79 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -58,7 +58,6 @@ - [ ] get_room - [ ] get_room_skill_parameter - [ ] get_skill_group -- [ ] list_device_events - [ ] list_skills - [ ] list_tags - [ ] put_room_skill_parameter @@ -82,7 +81,7 @@ - [ ] update_room - [ ] update_skill_group -## apigateway - 17% implemented +## apigateway - 24% implemented - [ ] create_api_key - [ ] create_authorizer - [ ] create_base_path_mapping @@ -95,8 +94,8 @@ - [X] create_resource - [X] create_rest_api - [X] create_stage -- [ ] create_usage_plan -- [ ] create_usage_plan_key +- [X] create_usage_plan +- [X] create_usage_plan_key - [ ] create_vpc_link - [ ] delete_api_key - [ ] delete_authorizer @@ -116,8 +115,8 @@ - [X] delete_resource - [X] delete_rest_api - [ ] delete_stage -- [ ] delete_usage_plan -- [ ] delete_usage_plan_key +- [X] delete_usage_plan +- [X] delete_usage_plan_key - [ ] delete_vpc_link - [ ] flush_stage_authorizers_cache - [ ] flush_stage_cache @@ -162,10 +161,10 @@ - [X] get_stages - [ ] get_tags - [ ] get_usage -- [ ] get_usage_plan -- [ ] get_usage_plan_key -- [ ] get_usage_plan_keys -- [ ] get_usage_plans +- [X] get_usage_plan +- [X] get_usage_plan_key +- [X] get_usage_plan_keys +- [X] get_usage_plans - [ ] get_vpc_link - [ ] get_vpc_links - [ ] import_api_keys @@ -352,7 +351,6 @@ - [ ] delete_scaling_plan - [ ] describe_scaling_plan_resources - [ ] describe_scaling_plans -- [ ] update_scaling_plan ## batch - 93% implemented - [ ] cancel_job @@ -767,8 +765,6 @@ - [ ] create_pipeline - [ ] delete_custom_action_type - [ ] delete_pipeline -- [ ] delete_webhook -- [ ] deregister_webhook_with_third_party - [ ] disable_stage_transition - [ ] enable_stage_transition - [ ] get_job_details @@ -779,7 +775,6 @@ - [ ] list_action_types - [ ] list_pipeline_executions - [ ] list_pipelines -- [ ] list_webhooks - [ ] poll_for_jobs - [ ] poll_for_third_party_jobs - [ ] put_action_revision @@ -788,8 +783,6 @@ - [ ] put_job_success_result - [ ] put_third_party_job_failure_result - [ ] put_third_party_job_success_result -- [ ] put_webhook -- [ ] register_webhook_with_third_party - [ ] retry_stage_execution - [ ] start_pipeline_execution - [ ] update_pipeline @@ -1065,7 +1058,6 @@ - [ ] create_project - [ ] create_remote_access_session - [ ] create_upload -- [ ] create_vpce_configuration - [ ] delete_device_pool - [ ] delete_instance_profile - [ ] delete_network_profile @@ -1073,7 +1065,6 @@ - [ ] delete_remote_access_session - [ ] delete_run - [ ] delete_upload -- [ ] delete_vpce_configuration - [ ] get_account_settings - [ ] get_device - [ ] get_device_instance @@ -1089,7 +1080,6 @@ - [ ] get_suite - [ ] get_test - [ ] get_upload -- [ ] get_vpce_configuration - [ ] install_to_remote_access_session - [ ] list_artifacts - [ ] list_device_instances @@ -1109,7 +1099,6 @@ - [ ] list_tests - [ ] list_unique_problems - [ ] list_uploads -- [ ] list_vpce_configurations - [ ] purchase_offering - [ ] renew_offering - [ ] schedule_run @@ -1120,7 +1109,6 @@ - [ ] update_instance_profile - [ ] update_network_profile - [ ] update_project -- [ ] update_vpce_configuration ## directconnect - 0% implemented - [ ] allocate_connection_on_interconnect @@ -1277,7 +1265,7 @@ - [ ] update_radius - [ ] verify_trust -## dynamodb - 21% implemented +## dynamodb - 22% implemented - [ ] batch_get_item - [ ] batch_write_item - [ ] create_backup @@ -1289,7 +1277,6 @@ - [ ] describe_backup - [ ] describe_continuous_backups - [ ] describe_global_table -- [ ] describe_global_table_settings - [ ] describe_limits - [ ] describe_table - [ ] describe_time_to_live @@ -1307,7 +1294,6 @@ - [ ] untag_resource - [ ] update_continuous_backups - [ ] update_global_table -- [ ] update_global_table_settings - [ ] update_item - [ ] update_table - [ ] update_time_to_live @@ -1318,7 +1304,7 @@ - [ ] get_shard_iterator - [ ] list_streams -## ec2 - 36% implemented +## ec2 - 37% implemented - [ ] accept_reserved_instances_exchange_quote - [ ] accept_vpc_endpoint_connections - [X] accept_vpc_peering_connection @@ -1356,7 +1342,6 @@ - [ ] create_default_vpc - [X] create_dhcp_options - [ ] create_egress_only_internet_gateway -- [ ] create_fleet - [ ] create_flow_logs - [ ] create_fpga_image - [X] create_image @@ -1391,7 +1376,6 @@ - [X] delete_customer_gateway - [ ] delete_dhcp_options - [ ] delete_egress_only_internet_gateway -- [ ] delete_fleets - [ ] delete_flow_logs - [ ] delete_fpga_image - [X] delete_internet_gateway @@ -1433,9 +1417,6 @@ - [ ] describe_egress_only_internet_gateways - [ ] describe_elastic_gpus - [ ] describe_export_tasks -- [ ] describe_fleet_history -- [ ] describe_fleet_instances -- [ ] describe_fleets - [ ] describe_flow_logs - [ ] describe_fpga_image_attribute - [ ] describe_fpga_images @@ -1532,7 +1513,6 @@ - [X] import_key_pair - [ ] import_snapshot - [ ] import_volume -- [ ] modify_fleet - [ ] modify_fpga_image_attribute - [ ] modify_hosts - [ ] modify_id_format @@ -1905,11 +1885,8 @@ - [ ] delete_delivery_stream - [ ] describe_delivery_stream - [ ] list_delivery_streams -- [ ] list_tags_for_delivery_stream - [ ] put_record - [ ] put_record_batch -- [ ] tag_delivery_stream -- [ ] untag_delivery_stream - [ ] update_destination ## fms - 0% implemented @@ -2231,7 +2208,7 @@ - [ ] describe_event_types - [ ] describe_events -## iam - 47% implemented +## iam - 48% implemented - [ ] add_client_id_to_open_id_connect_provider - [X] add_role_to_instance_profile - [X] add_user_to_group @@ -2281,7 +2258,7 @@ - [X] enable_mfa_device - [ ] generate_credential_report - [ ] get_access_key_last_used -- [ ] get_account_authorization_details +- [X] get_account_authorization_details - [ ] get_account_password_policy - [ ] get_account_summary - [ ] get_context_keys_for_custom_policy @@ -2536,38 +2513,6 @@ - [ ] start_next_pending_job_execution - [ ] update_job_execution -## iotanalytics - 0% implemented -- [ ] batch_put_message -- [ ] cancel_pipeline_reprocessing -- [ ] create_channel -- [ ] create_dataset -- [ ] create_dataset_content -- [ ] create_datastore -- [ ] create_pipeline -- [ ] delete_channel -- [ ] delete_dataset -- [ ] delete_dataset_content -- [ ] delete_datastore -- [ ] delete_pipeline -- [ ] describe_channel -- [ ] describe_dataset -- [ ] describe_datastore -- [ ] describe_logging_options -- [ ] describe_pipeline -- [ ] get_dataset_content -- [ ] list_channels -- [ ] list_datasets -- [ ] list_datastores -- [ ] list_pipelines -- [ ] put_logging_options -- [ ] run_pipeline_activity -- [ ] sample_channel_data -- [ ] start_pipeline_reprocessing -- [ ] update_channel -- [ ] update_dataset -- [ ] update_datastore -- [ ] update_pipeline - ## kinesis - 56% implemented - [X] add_tags_to_stream - [X] create_stream @@ -2815,7 +2760,7 @@ - [ ] update_domain_entry - [ ] update_load_balancer_attribute -## logs - 24% implemented +## logs - 27% implemented - [ ] associate_kms_key - [ ] cancel_export_task - [ ] create_export_task @@ -2830,7 +2775,7 @@ - [ ] delete_subscription_filter - [ ] describe_destinations - [ ] describe_export_tasks -- [ ] describe_log_groups +- [X] describe_log_groups - [X] describe_log_streams - [ ] describe_metric_filters - [ ] describe_resource_policies @@ -3569,9 +3514,6 @@ - [ ] update_tags_for_domain - [ ] view_billing -## runtime.sagemaker - 0% implemented -- [ ] invoke_endpoint - ## s3 - 15% implemented - [ ] abort_multipart_upload - [ ] complete_multipart_upload @@ -3703,13 +3645,13 @@ - [ ] put_attributes - [ ] select -## secretsmanager - 0% implemented +## secretsmanager - 20% implemented - [ ] cancel_rotate_secret -- [ ] create_secret +- [X] create_secret - [ ] delete_secret - [ ] describe_secret -- [ ] get_random_password -- [ ] get_secret_value +- [X] get_random_password +- [X] get_secret_value - [ ] list_secret_version_ids - [ ] list_secrets - [ ] put_secret_value @@ -3984,7 +3926,7 @@ - [X] tag_queue - [X] untag_queue -## ssm - 10% implemented +## ssm - 11% implemented - [X] add_tags_to_resource - [ ] cancel_command - [ ] create_activation @@ -3997,7 +3939,6 @@ - [ ] delete_activation - [ ] delete_association - [ ] delete_document -- [ ] delete_inventory - [ ] delete_maintenance_window - [X] delete_parameter - [X] delete_parameters @@ -4021,7 +3962,6 @@ - [ ] describe_instance_patch_states - [ ] describe_instance_patch_states_for_patch_group - [ ] describe_instance_patches -- [ ] describe_inventory_deletions - [ ] describe_maintenance_window_execution_task_invocations - [ ] describe_maintenance_window_execution_tasks - [ ] describe_maintenance_window_executions @@ -4053,7 +3993,7 @@ - [ ] list_association_versions - [ ] list_associations - [ ] list_command_invocations -- [ ] list_commands +- [X] list_commands - [ ] list_compliance_items - [ ] list_compliance_summaries - [ ] list_document_versions @@ -4464,36 +4404,25 @@ - [ ] update_resource ## workspaces - 0% implemented -- [ ] associate_ip_groups -- [ ] authorize_ip_rules -- [ ] create_ip_group - [ ] create_tags - [ ] create_workspaces -- [ ] delete_ip_group - [ ] delete_tags -- [ ] describe_ip_groups - [ ] describe_tags - [ ] describe_workspace_bundles - [ ] describe_workspace_directories - [ ] describe_workspaces - [ ] describe_workspaces_connection_status -- [ ] disassociate_ip_groups - [ ] modify_workspace_properties -- [ ] modify_workspace_state - [ ] reboot_workspaces - [ ] rebuild_workspaces -- [ ] revoke_ip_rules - [ ] start_workspaces - [ ] stop_workspaces - [ ] terminate_workspaces -- [ ] update_rules_of_ip_group ## xray - 0% implemented - [ ] batch_get_traces -- [ ] get_encryption_config - [ ] get_service_graph - [ ] get_trace_graph - [ ] get_trace_summaries -- [ ] put_encryption_config - [ ] put_telemetry_records - [ ] put_trace_segments diff --git a/moto/__init__.py b/moto/__init__.py index 0ce5e54d1..80c782a87 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -3,7 +3,7 @@ import logging # logging.getLogger('boto').setLevel(logging.CRITICAL) __title__ = 'moto' -__version__ = '1.3.3' +__version__ = '1.3.4' from .acm import mock_acm # flake8: noqa from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa diff --git a/scripts/implementation_coverage.py b/scripts/implementation_coverage.py index 74ce9590d..f15f3fb1c 100755 --- a/scripts/implementation_coverage.py +++ b/scripts/implementation_coverage.py @@ -6,6 +6,9 @@ from botocore.session import Session import boto3 +script_dir = os.path.dirname(os.path.abspath(__file__)) + + def get_moto_implementation(service_name): if not hasattr(moto, service_name): return None @@ -72,20 +75,22 @@ def write_implementation_coverage_to_file(coverage): except OSError: pass - for service_name in sorted(coverage): - implemented = coverage.get(service_name)['implemented'] - not_implemented = coverage.get(service_name)['not_implemented'] - operations = sorted(implemented + not_implemented) + implementation_coverage_file = "{}/../IMPLEMENTATION_COVERAGE.md".format(script_dir) + # rewrite the implementation coverage file with updated values + print("Writing to {}".format(implementation_coverage_file)) + with open(implementation_coverage_file, "a+") as file: + for service_name in sorted(coverage): + implemented = coverage.get(service_name)['implemented'] + not_implemented = coverage.get(service_name)['not_implemented'] + operations = sorted(implemented + not_implemented) - if implemented and not_implemented: - percentage_implemented = int(100.0 * len(implemented) / (len(implemented) + len(not_implemented))) - elif implemented: - percentage_implemented = 100 - else: - percentage_implemented = 0 + if implemented and not_implemented: + percentage_implemented = int(100.0 * len(implemented) / (len(implemented) + len(not_implemented))) + elif implemented: + percentage_implemented = 100 + else: + percentage_implemented = 0 - # rewrite the implementation coverage file with updated values - with open("../IMPLEMENTATION_COVERAGE.md", "a+") as file: file.write("\n") file.write("## {} - {}% implemented\n".format(service_name, percentage_implemented)) for op in operations: diff --git a/setup.py b/setup.py index 62f9026d7..304ce5f38 100755 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ else: setup( name='moto', - version='1.3.3', + version='1.3.4', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec', From 3830757ec64152e92aac4bc96b837e2108c0563f Mon Sep 17 00:00:00 2001 From: TheDooner64 Date: Tue, 7 Aug 2018 16:57:20 -0400 Subject: [PATCH 8/9] Add glue to backends to support server mode --- moto/backends.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/moto/backends.py b/moto/backends.py index cd8fe174f..8d707373f 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -20,6 +20,7 @@ from moto.elbv2 import elbv2_backends from moto.emr import emr_backends from moto.events import events_backends from moto.glacier import glacier_backends +from moto.glue import glue_backends from moto.iam import iam_backends from moto.instance_metadata import instance_metadata_backends from moto.kinesis import kinesis_backends @@ -65,6 +66,7 @@ BACKENDS = { 'events': events_backends, 'emr': emr_backends, 'glacier': glacier_backends, + 'glue': glue_backends, 'iam': iam_backends, 'moto_api': moto_api_backends, 'instance_metadata': instance_metadata_backends, From 1f3256ed40576806628a99dcac4d2d91515bac59 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Fri, 10 Aug 2018 16:40:31 -0700 Subject: [PATCH 9/9] Issue 1770: Deal with the friendly name properly - Save friendly name in create_secret. - Reference the saved friendly name in responses that have "Name" field. - Verify the received secret_id matches the current value. Don't just test for an empty string. - Add test for mismatched secret_id. --- moto/secretsmanager/models.py | 7 ++++--- tests/test_secretsmanager/test_secretsmanager.py | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py index 3923f90b0..31dadf73f 100644 --- a/moto/secretsmanager/models.py +++ b/moto/secretsmanager/models.py @@ -41,12 +41,12 @@ class SecretsManagerBackend(BaseBackend): def get_secret_value(self, secret_id, version_id, version_stage): - if self.secret_id == '': + if secret_id not in (self.secret_id, self.name): raise ResourceNotFoundException() response = json.dumps({ "ARN": secret_arn(self.region, self.secret_id), - "Name": self.secret_id, + "Name": self.name, "VersionId": "A435958A-D821-4193-B719-B7769357AER4", "SecretString": self.secret_string, "VersionStages": [ @@ -61,10 +61,11 @@ class SecretsManagerBackend(BaseBackend): self.secret_string = secret_string self.secret_id = name + self.name = name response = json.dumps({ "ARN": secret_arn(self.region, name), - "Name": self.secret_id, + "Name": self.name, "VersionId": "A435958A-D821-4193-B719-B7769357AER4", }) diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py index 6fefeb56f..a1544f72c 100644 --- a/tests/test_secretsmanager/test_secretsmanager.py +++ b/tests/test_secretsmanager/test_secretsmanager.py @@ -25,6 +25,15 @@ def test_get_secret_that_does_not_exist(): with assert_raises(ClientError): result = conn.get_secret_value(SecretId='i-dont-exist') +@mock_secretsmanager +def test_get_secret_with_mismatched_id(): + conn = boto3.client('secretsmanager', region_name='us-west-2') + create_secret = conn.create_secret(Name='java-util-test-password', + SecretString="foosecret") + + with assert_raises(ClientError): + result = conn.get_secret_value(SecretId='i-dont-exist') + @mock_secretsmanager def test_create_secret(): conn = boto3.client('secretsmanager', region_name='us-east-1')