From 6abf6df1e3af4cfe73543d0960e8465edb466fa0 Mon Sep 17 00:00:00 2001 From: David Holsgrove Date: Thu, 8 Aug 2019 12:25:52 +1000 Subject: [PATCH 1/3] Add additional logging --- src/postgresql_user_provider.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/postgresql_user_provider.py b/src/postgresql_user_provider.py index a66c3b7..c5e766c 100644 --- a/src/postgresql_user_provider.py +++ b/src/postgresql_user_provider.py @@ -1,11 +1,13 @@ import boto3 import logging +import os import psycopg2 from botocore.exceptions import ClientError from psycopg2.extensions import AsIs from cfn_resource_provider import ResourceProvider log = logging.getLogger() +log.setLevel(os.environ.get("LOG_LEVEL", "INFO")) request_schema = { "$schema": "http://json-schema.org/draft-04/schema#", @@ -88,6 +90,9 @@ def __init__(self): self.connection = None self.request_schema = request_schema + def is_valid_request(self): + return super(PostgreSQLUser, self).is_valid_request() + def convert_property_types(self): self.heuristic_convert_property_types(self.properties) @@ -144,7 +149,8 @@ def deletion_policy(self): @property def connect_info(self): return {'host': self.host, 'port': self.port, 'dbname': self.dbname, - 'user': self.dbowner, 'password': self.dbowner_password} + 'user': self.dbowner, 'password': self.dbowner_password, + 'connect_timeout': 20} @property def allow_update(self): From 70a9995ca794d395981717ff731e9dc8ad21a0e9 Mon Sep 17 00:00:00 2001 From: David Holsgrove Date: Thu, 8 Aug 2019 12:49:00 +1000 Subject: [PATCH 2/3] Bump connection timeout and add error message on fail to connect --- src/postgresql_user_provider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/postgresql_user_provider.py b/src/postgresql_user_provider.py index c5e766c..846e95c 100644 --- a/src/postgresql_user_provider.py +++ b/src/postgresql_user_provider.py @@ -150,7 +150,7 @@ def deletion_policy(self): def connect_info(self): return {'host': self.host, 'port': self.port, 'dbname': self.dbname, 'user': self.dbowner, 'password': self.dbowner_password, - 'connect_timeout': 20} + 'connect_timeout': 60} @property def allow_update(self): @@ -169,6 +169,7 @@ def connect(self): self.connection = psycopg2.connect(**self.connect_info) self.connection.set_session(autocommit=True) except Exception as e: + log.error('Failed to connect to database - check its running and reachable') raise ValueError('Failed to connect, %s' % e) def close(self): From e06c4a869423b4bb54c4d5f081d022b253fcb093 Mon Sep 17 00:00:00 2001 From: David Holsgrove Date: Fri, 13 Sep 2019 11:28:28 +1000 Subject: [PATCH 3/3] Add additional SSM Parameter retrival --- docs/PostgreSQLUser.md | 15 +++++++++------ src/postgresql_user_provider.py | 32 +++++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/docs/PostgreSQLUser.md b/docs/PostgreSQLUser.md index c67271b..7583986 100644 --- a/docs/PostgreSQLUser.md +++ b/docs/PostgreSQLUser.md @@ -16,8 +16,10 @@ Properties: Database: Host: STRING Port: INTEGER - Database: STRING + DBName: STRING + DBNameParameterName: STRING User: STRING + UserParameterName: STRING Password: STRING PasswordParameterName: STRING ServiceToken: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:binxio-cfn-dbuser-provider-vpc-${AppVPC}' @@ -27,20 +29,21 @@ Properties: You can specify the following properties: - `Name` - of the user to create -- `Password` - of the user +- `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 - `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. +-- `DBName` - name to connect to. +-- `DBNameParameterName` - name of the parameter in the store containing the database name to connect to. -- `User` - name of the database owner. --- `Password` - to identify the user with. +-- `UserParameterName` - name of the parameter in the store containing 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. +Either `DBName` or `DBNameParameterName`, `User` or `UserParameterName`, `Password` or `PasswordParameterName` is required. ## Return values There are no return values from this resources. - diff --git a/src/postgresql_user_provider.py b/src/postgresql_user_provider.py index 846e95c..a82ad88 100644 --- a/src/postgresql_user_provider.py +++ b/src/postgresql_user_provider.py @@ -48,13 +48,19 @@ "type": "object", "oneOf": [ {"required": ["DBName", "Host", "Port", "User", "Password"]}, - {"required": ["DBName", "Host", "Port", "User", "PasswordParameterName"]} + {"required": ["DBName", "Host", "Port", "User", "PasswordParameterName"]}, + {"required": ["DBName", "Host", "Port", "UserParameterName", "PasswordParameterName"]}, + {"required": ["DBNameParameterName", "Host", "Port", "UserParameterName", "PasswordParameterName"]}, ], "properties": { "DBName": { "type": "string", "description": "the name of the database" }, + "DBNameParameterName": { + "type": "string", + "description": "the name of the database name in the Parameter Store" + }, "Host": { "type": "string", "description": "the host of the database" @@ -68,6 +74,10 @@ "type": "string", "description": "the username of the database owner" }, + "UserParameterName": { + "type": "string", + "description": "the name of the database owner username in the Parameter Store" + }, "Password": { "type": "string", "description": "the password of the database owner" @@ -96,19 +106,19 @@ def is_valid_request(self): def convert_property_types(self): self.heuristic_convert_property_types(self.properties) - def get_password(self, name): + def get_ssm_parameter(self, name): try: response = self.ssm.get_parameter(Name=name, WithDecryption=True) return response['Parameter']['Value'] except ClientError as e: - raise ValueError('Could not obtain password using name {}, {}'.format(name, e)) + raise ValueError('Could not obtain value using name {}, {}'.format(name, e)) @property def user_password(self): if 'Password' in self.properties: return self.get('Password') else: - return self.get_password(self.get('PasswordParameterName')) + return self.get_ssm_parameter(self.get('PasswordParameterName')) @property def dbowner_password(self): @@ -116,7 +126,7 @@ def dbowner_password(self): if 'Password' in db: return db.get('Password') else: - return self.get_password(db['PasswordParameterName']) + return self.get_ssm_parameter(db['PasswordParameterName']) @property def user(self): @@ -132,11 +142,19 @@ def port(self): @property def dbname(self): - return self.get('Database', {}).get('DBName', None) + db = self.get('Database') + if 'DBName' in db: + return db.get('DBName') + else: + return self.get_ssm_parameter(db['DBNameParameterName']) @property def dbowner(self): - return self.get('Database', {}).get('User', None) + db = self.get('Database') + if 'User' in db: + return db.get('User') + else: + return self.get_ssm_parameter(db['UserParameterName']) @property def with_database(self):