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 a66c3b7..a82ad88 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#", @@ -46,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" @@ -66,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" @@ -88,22 +100,25 @@ 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) - 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): @@ -111,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): @@ -127,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): @@ -144,7 +167,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': 60} @property def allow_update(self): @@ -163,6 +187,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):