diff --git a/Makefile b/Makefile index 52590c0..b33e7c1 100644 --- a/Makefile +++ b/Makefile @@ -58,16 +58,18 @@ venv: requirements.txt virtualenv -p python3 venv && \ . ./venv/bin/activate && \ pip install --quiet --upgrade pip && \ - pip install --quiet -r requirements.txt - + pip install --quiet -r requirements.txt + clean: rm -rf venv target - rm -rf src/*.pyc tests/*.pyc + rm -rf src/*.pyc tests/*.pyc src/__pycache__ tests/__pycache__ -test: venv +test-templates: for i in $$PWD/cloudformation/*; do \ aws cloudformation validate-template --template-body file://$$i > /dev/null || exit 1; \ done + +test: venv . ./venv/bin/activate && \ pip install --quiet -r requirements.txt -r test-requirements.txt && \ cd src && \ diff --git a/README.md b/README.md index 15d870b..b7d6494 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,13 @@ It is quite easy: you specify a CloudFormation resource of the [Custom::PostgreS ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-secret-provider' ``` -After the deployment, the Postgres user 'kong' has been created together with a matching database 'kong'. The password for the root database user has been obtained by querying the Parameter `/postgres/root/PGPASSWORD`. If you just want to create a user with which you can login to the PostgreSQL database server, without a database, specify `WithDatabase` as `false`. If `WithPublicSchema` is set to false, permission to create in the schema `public` is revoked. +After the deployment, the Postgres user 'kong' has been created together with a matching database 'kong'. +The password for the root database user has been obtained by querying the Parameter `/postgres/root/PGPASSWORD`. +If you just want to create a user with which you can login to the PostgreSQL database server, without a database, specify `WithDatabase` as `false`. +If `WithPublicSchema` is set to false, permission to create in the schema `public` is revoked. -The RetainPolicy by default is `Retain`. This means that the login to the database is disabled. If you specify drop, it will be dropped and your data will be lost. +The RetainPolicy by default is `Drop`. +This means the schema will be dropped and your data will be lost when deleting this resource in CloudFormation. ## Installation diff --git a/docker-compose.override.yaml b/docker-compose.override.yaml new file mode 100644 index 0000000..927c3be --- /dev/null +++ b/docker-compose.override.yaml @@ -0,0 +1,6 @@ +version: '2.4' + +services: + aurora: + ports: + - "5432:5432" diff --git a/docker-compose.test.yaml b/docker-compose.test.yaml new file mode 100644 index 0000000..40306ed --- /dev/null +++ b/docker-compose.test.yaml @@ -0,0 +1,6 @@ +version: '2.4' + +services: + aurora: + ports: + - "5432" diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..69fd49d --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,16 @@ +version: "2.4" + +services: + aurora: + image: 280925583500.dkr.ecr.eu-central-1.amazonaws.com/postgres:13 + ports: + - "5432" + environment: + - POSTGRES_DB=postgres + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=password + healthcheck: + test: ["CMD", "psql", "-t", "-d", "postgres", "-U", "postgres", "-c", "select usename from pg_catalog.pg_user"] + interval: 10s + timeout: 5s + retries: 5 diff --git a/docs/PostgreSQLExtension.md b/docs/PostgreSQLExtension.md new file mode 100644 index 0000000..ae6ba97 --- /dev/null +++ b/docs/PostgreSQLExtension.md @@ -0,0 +1,40 @@ +# Custom::PostgreSQLExtension +The `Custom::PostgresSQLExtension` resource creates a postgres Extension in the mentioned database. + + +## Syntax +To declare this entity in your AWS CloudFormation template, use the following syntax: + +```yaml +Type: Custom::PostgreSQLExtension +Properties: + Extension: STRING + DeletionPolicy: STRING + Database: + Host: STRING + Port: INTEGER + Database: STRING + User: STRING + Password: STRING + PasswordParameterName: STRING + ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-dbuser-provider-vpc-${AppVPC}' +``` + +## Properties +You can specify the following properties: + +- `Extension` - the name of a valid postgresql database extensions +- `DeletionPolicy` - when the resource is deleted. Default: `Drop` +- `Database` - connection information of the database owner + - `Host` - the database server is listening on. + - `Port` - port the database server is listening on. + - `Database` - name of the database to connect to. + - `User` - name of the database owner. + - `Password` - to identify the user with. + - `PasswordParameterName` - name of the parameter in the store containing the password of the user + +Either `Password` or `PasswordParameterName` is required. + +## Return values +There are no return values from this resources. + diff --git a/docs/PostgreSQLRoleGrant.md b/docs/PostgreSQLRoleGrant.md new file mode 100644 index 0000000..266c462 --- /dev/null +++ b/docs/PostgreSQLRoleGrant.md @@ -0,0 +1,40 @@ +# Custom::PostgreSQLRoleGrant +The `Custom::PostgresSQLRoleGrant` resource links a role to a role or user. + + +## Syntax +To declare this entity in your AWS CloudFormation template, use the following syntax: + +```yaml +Type: Custom::PostgreSQLRoleGrant +Properties: + Grantee: STRING + Role: STRING + Database: + Host: STRING + Port: INTEGER + Database: STRING + User: STRING + Password: STRING + PasswordParameterName: STRING + ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-dbuser-provider-vpc-${AppVPC}' +``` + +## Properties +You can specify the following properties: + +- `Grantee` - user or role to grant to +- `Role` - role to grant to user or role +- `Database` - connection information of the database owner + - `Host` - the database server is listening on. + - `Port` - port the database server is listening on. + - `Database` - name of the database to connect to. + - `User` - name of the database owner. + - `Password` - to identify the user with. + - `PasswordParameterName` - name of the parameter in the store containing the password of the user + +Either `Password` or `PasswordParameterName` is required. + +## Return values +There are no return values from this resources. + diff --git a/docs/PostgreSQLSchema.md b/docs/PostgreSQLSchema.md index 7ae30bb..75590c1 100644 --- a/docs/PostgreSQLSchema.md +++ b/docs/PostgreSQLSchema.md @@ -26,11 +26,11 @@ You can specify the following properties: - `Schema` - to create - `Owner` - of the schema -- `DeletionPolicy` - when the resource is deleted +- `DeletionPolicy` - when the resource is deleted. Default: `Drop` - `Database` - connection information of the database owner - `Host` - the database server is listening on. - `Port` - port the database server is listening on. - - `Database` - name to connect to. + - `Database` - name of the database to connect to. - `User` - name of the database owner. - `Password` - to identify the user with. - `PasswordParameterName` - name of the parameter in the store containing the password of the user diff --git a/docs/PostgreSQLUser.md b/docs/PostgreSQLUser.md index 1ecc3f9..9b217d3 100644 --- a/docs/PostgreSQLUser.md +++ b/docs/PostgreSQLUser.md @@ -30,11 +30,11 @@ You can specify the following properties: - `Password` - of the user - `PasswordParameterName` - name of the parameter in the store containing the password of the user - `WithDatabase` - if a database is to be created with the same name, defaults to true -- `DeletionPolicy` - when the resource is deleted +- `DeletionPolicy` - when the resource is deleted. Default: `Drop` - `Database` - connection information of the database owner - `Host` - the database server is listening on. - `Port` - port the database server is listening on. - - `Database` - name to connect to. + - `Database` - name of the database to connect to. - `User` - name of the database owner. - `Password` - to identify the user with. - `PasswordParameterName` - name of the parameter in the store containing the password of the user diff --git a/requirements.txt b/requirements.txt index a6eccc6..d27adaf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ psycopg2-binary requests jsonschema cfn_resource_provider>=1.0.6 +AsIs diff --git a/src/postgresql.py b/src/postgresql.py index ce35c6a..5f472d1 100644 --- a/src/postgresql.py +++ b/src/postgresql.py @@ -1,5 +1,7 @@ import os import logging + +import postgresql_extension_provider import postgresql_schema_provider import postgresql_role_grant_provider import postgresql_user_provider @@ -11,6 +13,8 @@ def handler(request, context): return postgresql_schema_provider.handler(request, context) elif request['ResourceType'] == 'Custom::PostgreSQLRoleGrant': return postgresql_role_grant_provider.handler(request, context) + elif request['ResourceType'] == 'Custom::PostgreSQLExtension': + return postgresql_extension_provider.handler(request, context) else: return postgresql_user_provider.handler(request, context) diff --git a/src/postgresql_extension_provider.py b/src/postgresql_extension_provider.py new file mode 100644 index 0000000..94a9444 --- /dev/null +++ b/src/postgresql_extension_provider.py @@ -0,0 +1,155 @@ +import logging +import os + +from postgresql_user_provider import PostgreSQLUser +from psycopg2.extensions import AsIs + +log = logging.getLogger() + +log.setLevel(os.environ.get("LOG_LEVEL", "INFO")) + +request_schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "oneOf": [{"required": ["Database", "Extension"]}], + "properties": { + "Database": {"$ref": "#/definitions/connection"}, + "Extension": { + "type": "string", + "pattern": "^[A-Za-z0-9_]*$", + "description": "postgres extension", + }, + "DeletionPolicy": { + "type": "string", + "default": "Drop", + "enum": ["Drop", "Retain"], + }, + }, + "definitions": { + "connection": { + "type": "object", + "oneOf": [ + {"required": ["DBName", "Host", "Port", "User", "Password"]}, + { + "required": [ + "DBName", + "Host", + "Port", + "User", + "PasswordParameterName", + ] + }, + ], + "properties": { + "DBName": {"type": "string", "description": "the name of the database"}, + "Host": {"type": "string", "description": "the host of the database"}, + "Port": { + "type": "integer", + "default": 5432, + "description": "the network port of the database", + }, + "User": { + "type": "string", + "description": "the username of the database owner", + }, + "Password": { + "type": "string", + "description": "the password of the database owner", + }, + "PasswordParameterName": { + "type": "string", + "description": "the name of the database owner password in the Parameter Store.", + }, + }, + } + }, +} + + +class PostgreSQLExtension(PostgreSQLUser): + def __init__(self): + super(PostgreSQLExtension, self).__init__() + self.request_schema = request_schema + + def is_supported_resource_type(self): + return self.resource_type == "Custom::PostgreSQLExtension" + + @property + def database(self): + return self.dbname + + @property + def extension(self): + return self.get("Extension") + + @property + def old_extension(self): + return self.get_old("Extension", self.extension) + + @property + def extension(self): + return self.get("Extension") + + @property + def deletion_policy(self): + return self.get("DeletionPolicy") + + def create_extension(self): + log.info("creating extension %s for database %s ", self.extension, self.database) + with self.connection.cursor() as cursor: + cursor.execute("Create Extension if not exists %s", [AsIs(self.extension)]) + + def update_extension(self): + if self.extension != self.old_extension: + log.info("removing old extension %s and implementing %s on database %s", self.old_extension, self.extension, self.database) + log.info("drop extension %s ", self.extension) + with self.connection.cursor() as cursor: + cursor.execute("DROP EXTENSION IF EXISTS %s CASCADE", [AsIs(self.old_extension)]) + log.info("creating extension %s on database %s ", self.extension, self.database) + with self.connection.cursor() as cursor: + cursor.execute("Create Extension if not exists %s", [AsIs(self.extension)]) + + def drop_extension(self): + log.info("drop extension %s ", self.extension) + with self.connection.cursor() as cursor: + cursor.execute("DROP EXTENSION IF EXISTS %s CASCADE", [AsIs(self.extension)]) + + def create(self): + try: + self.connect() + self.create_extension() + self.physical_resource_id = self.logical_resource_id + except Exception as e: + self.physical_resource_id = "could-not-create" + self.fail("Failed to create extension, %s" % e) + finally: + self.close() + + def update(self): + try: + self.connect() + self.update_extension() + except Exception as e: + self.fail("Failed to update the extension, %s" % e) + finally: + self.close() + + def delete(self): + if self.physical_resource_id == "could-not-create" or self.deletion_policy == "Retain": + self.success("extension was never created") + return + + try: + self.connect() + self.drop_extension() + except Exception as e: + return self.fail("Failed to drop extension %s" % e) + finally: + self.close() + + +provider = PostgreSQLExtension() + + +def handler(request, context): + return provider.handle(request, context) diff --git a/src/postgresql_role_grant_provider.py b/src/postgresql_role_grant_provider.py index 702c867..6ace536 100644 --- a/src/postgresql_role_grant_provider.py +++ b/src/postgresql_role_grant_provider.py @@ -110,7 +110,7 @@ def update(self): self.close() def delete(self): - if self.physical_resource_id == "could-not-create": + if self.physical_resource_id == "could-not-create" or self.deletion_policy == "retain": self.success("role was never granted") return diff --git a/src/postgresql_schema_provider.py b/src/postgresql_schema_provider.py index 8eaaa21..5e35fb9 100644 --- a/src/postgresql_schema_provider.py +++ b/src/postgresql_schema_provider.py @@ -1,10 +1,13 @@ import logging +import os from postgresql_user_provider import PostgreSQLUser from psycopg2.extensions import AsIs log = logging.getLogger() +log.setLevel(os.environ.get("LOG_LEVEL", "INFO")) + request_schema = { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", @@ -23,7 +26,7 @@ }, "DeletionPolicy": { "type": "string", - "default": "Retain", + "default": "Drop", "enum": ["Drop", "Retain"], }, }, @@ -107,10 +110,9 @@ def create_schema(self): ) def drop_schema(self): - if self.deletion_policy == "Drop": - log.info("drop schema %s ", self.schema) - with self.connection.cursor() as cursor: - cursor.execute("DROP SCHEMA %s CASCADE", [AsIs(self.schema)]) + log.info("drop schema %s ", self.schema) + with self.connection.cursor() as cursor: + cursor.execute("DROP SCHEMA %s CASCADE", [AsIs(self.schema)]) def update_schema(self): if self.owner != self.old_owner: @@ -150,7 +152,7 @@ def update(self): self.close() def delete(self): - if self.physical_resource_id == "could-not-create": + if self.physical_resource_id == "could-not-create" or self.deletion_policy == "Retain": self.success("schema was never created") return @@ -158,7 +160,7 @@ def delete(self): self.connect() self.drop_schema() except Exception as e: - return self.fail("failed to drop schema %s", e) + return self.fail("failed to drop schema %s" % e) finally: self.close() diff --git a/src/postgresql_user_provider.py b/src/postgresql_user_provider.py index 8f8a942..5211f13 100644 --- a/src/postgresql_user_provider.py +++ b/src/postgresql_user_provider.py @@ -189,7 +189,7 @@ def role_exists(self): def drop_user(self): with self.connection.cursor() as cursor: - if self.deletion_policy == 'Drop': + if self.deletion_policy != 'Retain': log.info('drop role %s', self.user) cursor.execute('DROP ROLE %s', [AsIs(self.user)]) else: @@ -197,7 +197,7 @@ def drop_user(self): cursor.execute("ALTER ROLE %s NOLOGIN", [AsIs(self.user)]) def drop_database(self): - if self.deletion_policy == 'Drop': + if self.deletion_policy != 'Retain': log.info('drop database of %s', self.user) with self.connection.cursor() as cursor: cursor.execute('GRANT %s TO %s', [ @@ -276,7 +276,7 @@ def update(self): self.close() def delete(self): - if self.physical_resource_id == 'could-not-create': + if self.physical_resource_id == 'could-not-create' or self.deletion_policy == "Retain": self.success('user was never created') try: diff --git a/tests/test_postgresql_extension_provider.py b/tests/test_postgresql_extension_provider.py new file mode 100644 index 0000000..03c04d1 --- /dev/null +++ b/tests/test_postgresql_extension_provider.py @@ -0,0 +1,77 @@ +import logging +import os +import uuid + +import psycopg2 + +from postgresql import handler + +logging.basicConfig(level=logging.INFO) + + +def test_create_extension(): + extension1 = "btree_gin" + request = Request("Create", extension1) + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + # rename to extension2 + extension2 = "btree_gist" + request = Request("Update", extension2, response["PhysicalResourceId"]) + request["OldResourceProperties"] = {"Extension": extension1} + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + request = Request("Delete", extension2, response["PhysicalResourceId"]) + request["ResourceProperties"]["DeletionPolicy"] = "Drop" + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + +def test_drop_extension(): + extension1 = "pgcrypto" + request = Request("Create", extension1) + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + request = Request("Delete", extension1, response["PhysicalResourceId"]) + request["ResourceProperties"]["DeletionPolicy"] = "Drop" + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + +class Request(dict): + def __init__(self, request_type, extension, physical_resource_id=None): + self.update( + { + "RequestType": request_type, + "ResponseURL": "https://httpbin.org/put", + "StackId": "arn:aws:cloudformation:us-west-2:EXAMPLE/stack-name/guid", + "RequestId": "request-%s" % str(uuid.uuid4()), + "ResourceType": "Custom::PostgreSQLExtension", + "LogicalResourceId": "Whatever", + "ResourceProperties": { + "Extension": extension, + "Database": { + "User": "postgres", + "Password": "password", + "Host": os.getenv("DOCKER0", "localhost"), + "Port": os.getenv("DBPORT", 5432), + "DBName": "postgres", + }, + }, + } + ) + if physical_resource_id is not None: + self["PhysicalResourceId"] = physical_resource_id + + def db_connection(self): + p = self["ResourceProperties"] + args = { + "host": p["Database"]["Host"], + "port": p["Database"]["Port"], + "dbname": p["Database"]["DBName"], + "user": p["Database"]["User"], + "password": p["Database"]["Password"], + } + return psycopg2.connect(**args) diff --git a/tests/test_postgresql_grant_role_provider.py b/tests/test_postgresql_grant_role_provider.py index 35abfde..e7fd98a 100644 --- a/tests/test_postgresql_grant_role_provider.py +++ b/tests/test_postgresql_grant_role_provider.py @@ -1,4 +1,5 @@ import logging +import os import uuid import psycopg2 @@ -66,8 +67,8 @@ def __init__(self, request_type, role, grantee, physical_resource_id=None): "Database": { "User": "postgres", "Password": "password", - "Host": "localhost", - "Port": 5432, + "Host": os.getenv("DOCKER0", "localhost"), + "Port": os.getenv("DBPORT", 5432), "DBName": "postgres", }, }, diff --git a/tests/test_postgresql_schema_provider.py b/tests/test_postgresql_schema_provider.py index 82f60f4..ba87a4d 100644 --- a/tests/test_postgresql_schema_provider.py +++ b/tests/test_postgresql_schema_provider.py @@ -1,4 +1,5 @@ import logging +import os import uuid import psycopg2 @@ -35,6 +36,20 @@ def test_create_schema(pg_users): response = handler(request, {}) assert response["Status"] == "SUCCESS", response["Reason"] + +def test_drop_schema(pg_users): + user1, user2 = pg_users + schema1 = "schema_{}".format(str(uuid.uuid4()).replace("-", "")) + request = Request("Create", schema1, user1) + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + request = Request("Delete", schema1, user2, response["PhysicalResourceId"]) + request["ResourceProperties"]["DeletionPolicy"] = "Drop" + response = handler(request, {}) + assert response["Status"] == "SUCCESS", response["Reason"] + + @pytest.fixture def pg_users(): uid = str(uuid.uuid4()).replace("-", "") @@ -52,14 +67,12 @@ def pg_users(): yield (name, name2) with connection.cursor() as cursor: - #cursor.execute("DROP OWNED BY %s CASCADE", [AsIs(name)]) + # cursor.execute("DROP OWNED BY %s CASCADE", [AsIs(name)]) for n in [name, name2]: cursor.execute("DROP ROLE %s", [AsIs(n)]) connection.commit() - - class Request(dict): def __init__(self, request_type, schema, owner, physical_resource_id=None): self.update( @@ -76,8 +89,8 @@ def __init__(self, request_type, schema, owner, physical_resource_id=None): "Database": { "User": "postgres", "Password": "password", - "Host": "localhost", - "Port": 5432, + "Host": os.getenv("DOCKER0", "localhost"), + "Port": os.getenv("DBPORT", 5432), "DBName": "postgres", }, }, diff --git a/tests/test_postgresql_user_provider.py b/tests/test_postgresql_user_provider.py index 000b4ed..5890263 100644 --- a/tests/test_postgresql_user_provider.py +++ b/tests/test_postgresql_user_provider.py @@ -1,9 +1,12 @@ -import sys, json +import logging +import os +import sys import uuid -import psycopg2 + import boto3 -import logging -from postgresql_user_provider import handler, request_schema +import psycopg2 + +from postgresql_user_provider import handler logging.basicConfig(level=logging.INFO) @@ -20,8 +23,8 @@ def __init__(self, request_type, user, physical_resource_id=None, with_database= 'LogicalResourceId': 'Whatever', 'ResourceProperties': { 'User': user, 'Password': 'password', 'WithDatabase': with_database, - 'Database': {'User': 'postgres', 'Password': 'password', 'Host': 'localhost', - 'Port': 5432, 'DBName': 'postgres'} + 'Database': {'User': 'postgres', 'Password': 'password', 'Host': os.getenv("DOCKER0", "localhost"), + 'Port': os.getenv("DBPORT", 5432), 'DBName': 'postgres'} }}) if physical_resource_id is not None: self['PhysicalResourceId'] = physical_resource_id @@ -42,6 +45,7 @@ def test_user_connection(self, password=None): 'user': p['User'], 'password': password} return psycopg2.connect(**args) + def test_invalid_user_name(): event = Event('Create', 'a-user', with_database=False) response = handler(event, {}) @@ -51,7 +55,7 @@ def test_invalid_user_name(): def test_password_with_special_chars(): name = 'u%s' % str(uuid.uuid4()).replace('-', '') event = Event('Create', name, with_database=False) - event['ResourceProperties']['Password'] = "abd'\\efg~" + event['ResourceProperties']['Password'] = "abd'\\efg~" response = handler(event, {}) assert response['Status'] == 'SUCCESS', response['Reason'] @@ -66,15 +70,18 @@ def test_password_with_special_chars(): response = handler(event, {}) assert response['Status'] == 'SUCCESS', response['Reason'] + def test_create_user(): # create a test user name = 'u%s' % str(uuid.uuid4()).replace('-', '') event = Event('Create', name, with_database=False) + host = event['ResourceProperties']['Database']['Host'] + port = event['ResourceProperties']['Database']['Port'] response = handler(event, {}) assert response['Status'] == 'SUCCESS', response['Reason'] assert 'PhysicalResourceId' in response physical_resource_id = response['PhysicalResourceId'] - expect_id = 'postgresql:localhost:5432:postgres::%(name)s' % {'name': name} + expect_id = 'postgresql:%(host)s:%(port)s:postgres::%(name)s' % {'host': host, 'port': port, 'name': name} assert physical_resource_id == expect_id, 'expected %s, got %s' % (expect_id, physical_resource_id) with event.test_user_connection() as connection: @@ -105,17 +112,24 @@ def test_create_user(): response = handler(event, {}) assert response['Status'] == 'SUCCESS', response['Reason'] + event = Event('Delete', name, physical_resource_id, with_database=True) + event['ResourceProperties']['DeletionPolicy'] = '' + response = handler(event, {}) + assert response['Status'] == 'SUCCESS', response['Reason'] + def test_update_password(): # create a test database name = 'u%s' % str(uuid.uuid4()).replace('-', '') event = Event('Create', name, with_database=True) event['DeletionPolicy'] = 'Drop' + host = event['ResourceProperties']['Database']['Host'] + port = event['ResourceProperties']['Database']['Port'] response = handler(event, {}) assert response['Status'] == 'SUCCESS', '%s' % response['Reason'] assert 'PhysicalResourceId' in response physical_resource_id = response['PhysicalResourceId'] - expect_id = 'postgresql:localhost:5432:postgres:%(name)s:%(name)s' % {'name': name} + expect_id = 'postgresql:%(host)s:%(port)s:postgres:%(name)s:%(name)s' % {'host': host, 'port': port, 'name': name} assert physical_resource_id == expect_id, 'expected %s, got %s' % (expect_id, physical_resource_id) # update the password @@ -144,11 +158,13 @@ def test_create_database(): # create a test database name = 'u%s' % str(uuid.uuid4()).replace('-', '') event = Event('Create', name, with_database=True) + host = event['ResourceProperties']['Database']['Host'] + port = event['ResourceProperties']['Database']['Port'] response = handler(event, {}) assert response['Status'] == 'SUCCESS', '%s' % response['Reason'] assert 'PhysicalResourceId' in response physical_resource_id = response['PhysicalResourceId'] - expect_id = 'postgresql:localhost:5432:postgres:%(name)s:%(name)s' % {'name': name} + expect_id = 'postgresql:%(host)s:%(port)s:postgres:%(name)s:%(name)s' % {'host': host, 'port': port, 'name': name} assert physical_resource_id == expect_id, 'expected %s, got %s' % (expect_id, physical_resource_id) # create the database again @@ -201,8 +217,8 @@ def test_password_parameter_use(): ssm = boto3.client('ssm') uuid_string = str(uuid.uuid4()).replace('-', '') name = 'user_%s' % uuid_string - user_password_name = 'p%s' % uuid_string - dbowner_password_name = 'o%s' % uuid_string + user_password_name = 'testparameterp%s' % uuid_string + dbowner_password_name = 'testparametero%s' % uuid_string try: event = Event('Create', name)