Skip to content

Commit

Permalink
feat: dynamic parameters (#35)
Browse files Browse the repository at this point in the history
* feat: app support for dynamic parameters

* chore: remove function documentation in favor of api message format schema

* feat: add initial support for pipeline params

* chore: remove invalid tests for new branch
  • Loading branch information
kalleeh authored and shendriksen committed Nov 13, 2019
1 parent 5dadcb5 commit d8dc5d0
Show file tree
Hide file tree
Showing 27 changed files with 115 additions and 307 deletions.
7 changes: 7 additions & 0 deletions lambda_layers/dependencies/python/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ class ParameterNotFound(Error):
pass


class UnknownParameter(Error):
"""
Parameter not found in Parameter Store
"""
pass


class PermissionDenied(Error):
"""Raised when an action is performed on a resource
the principal doesn't have permissions to."""
Expand Down
30 changes: 4 additions & 26 deletions lambda_layers/dependencies/python/managers/app_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,6 @@ def __init__(self, event):
def _generate_params(self, payload):
""" Dynamically generates a CloudFormation compatible
dict with the params passed in from a request payload.
Args:
Basic Usage:
>>> resp = _generate_params(params)
Returns:
List: List of dicts containing key:value pairs
representing CloudFormation Params
[
{
'ParameterKey': 'Name',
'ParamaterValue': 'value-of-parameter'
}
]
"""
params = {}
LOGGER.debug('Generating parameters.')
Expand All @@ -67,21 +54,13 @@ def _generate_params(self, payload):

ssm_params = parameter_store.get_parameters()
LOGGER.debug(
'Loaded SSM Dictionary intoattaatta Config: %s',
'Loaded SSM Dictionary into Config: %s',
ssm_params)

# mark parameters that should be re-used in CloudFormation and
# modify depending on payload.
reuse_params = []
params['DesiredCount'] = payload['tasks'] if 'tasks' in payload else reuse_params.append('DesiredCount')
params['HealthCheckPath'] = payload['health_check_path'] if 'health_check_path' in payload else reuse_params.append('HealthCheckPath')
params['DockerImage'] = payload['image'] if 'image' in payload else reuse_params.append('DockerImage')
LOGGER.debug(
'Reusing parameters: %s',
reuse_params)
config = payload['config']
params.update(config)

# we need to dynamically generate the priorty param to insert
# since it's required by CFN.
# dynamically generate the priorty param to insert
params['Priority'] = str(elb_helper.get_random_rule_priority(
ssm_params['platform']['loadbalancer']['listener-arn']))

Expand All @@ -90,7 +69,6 @@ def _generate_params(self, payload):
'ParameterKey',
'ParameterValue',
clean=True)
params = params + transform_utils.reuse_vals(reuse_params)

LOGGER.debug(
'Returning parameters: %s',
Expand Down
67 changes: 39 additions & 28 deletions lambda_layers/dependencies/python/managers/pipeline_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import transform_utils

from managers.stack_manager import StackManager
from parameter_store import ParameterStore

patch_all()

Expand Down Expand Up @@ -121,46 +122,56 @@ def put_approval_result(
def _generate_params(self, payload):
""" Dynamically generates a CloudFormation compatible
dict with the params passed in from a request payload.
Args:
Basic Usage:
>>> resp = _generate_params(params)
Returns:
List: List of dicts containing key:value pairs
representing CloudFormation Params
[
{
'ParameterKey': 'Name',
'ParamaterValue': 'value-of-parameter'
}
]
"""
params = {}
LOGGER.debug('Generating parameters.')
parameter_store = ParameterStore(
platform_config.PLATFORM_REGION,
boto3
)

ssm_params = parameter_store.get_parameters()
LOGGER.debug(
'Loaded SSM Dictionary into Config: %s',
ssm_params)

# mark parameters that should be re-used in CloudFormation
# and modify depending on payload.
reuse_params = []

params['GitHubUser'] = payload['github_user'] \
if 'github_user' in payload else reuse_params.append('GitHubUser')
params['GitHubToken'] = payload['github_token'] \
if 'github_token' in payload else reuse_params.append('GitHubToken')
params['GitHubRepo'] = payload['github_repo'] \
if 'github_repo' in payload else reuse_params.append('GitHubRepo')

params['ServiceDev'] = transform_utils.add_prefix(payload['app_dev']) \
if 'app_dev' in payload else None
params['ServiceTest'] = transform_utils.add_prefix(payload['app_test']) \
if 'app_test' in payload else None
params['ServiceProd'] = transform_utils.add_prefix(payload['app_name']) \
if 'app_name' in payload else None
params['GitHubBranch'] = payload['github_branch'] \
if 'github_branch' in payload else None
source = payload['source']
params.update(source)

#TODO: Make dynamic through generated templates.
environments = ['ServiceProd']
no_environments = len(payload['environments'])

if no_environments >= 2:
environments.append('ServiceDev')
if no_environments == 3:
environments.append('ServiceTest')

i = 0
for environment_name in payload['environments']:
environment = {environments[i]: transform_utils.add_prefix(environment_name)}
params.update(environment)
i = i + 1

params['GitHubToken'] = source['GitHubToken'] \
if 'GitHubToken' in source else reuse_params.append('GitHubToken')

source = payload['source']
params.update(source)

params = transform_utils.dict_to_kv(
params,
'ParameterKey',
'ParameterValue',
clean=True)
params = params + transform_utils.reuse_vals(reuse_params)

LOGGER.debug(
'Returning parameters: %s',
params)

return params
42 changes: 24 additions & 18 deletions lambda_layers/dependencies/python/managers/service_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
Amazon Web Services, Inc. or Amazon Web Services EMEA SARL or both.
"""

import boto3

from aws_xray_sdk.core import patch_all
from logger import configure_logger

import platform_config
import transform_utils

from managers.stack_manager import StackManager
from parameter_store import ParameterStore

patch_all()

Expand All @@ -39,28 +43,30 @@ def __init__(self, event):
def _generate_params(self, payload):
""" Dynamically generates a CloudFormation compatible
dict with the params passed in from a request payload.
Args:
Basic Usage:
>>> resp = _generate_params(params)
Returns:
List: List of dicts containing key:value pairs
representing CloudFormation Params
[
{
'ParameterKey': 'Name',
'ParamaterValue': 'value-of-parameter'
}
]
"""
params = {}
LOGGER.debug('Generating parameters.')
parameter_store = ParameterStore(
platform_config.PLATFORM_REGION,
boto3
)

ssm_params = parameter_store.get_parameters()
LOGGER.debug(
'Loaded SSM Dictionary into Config: %s',
ssm_params)

# mark parameters that should be re-used in CloudFormation and modify depending on payload.
reuse_params = []
config = payload['config']
params.update(config)

params['ServiceBindings'] = payload['service_bindings'] if 'service_bindings' in payload else None
params = transform_utils.dict_to_kv(
params,
'ParameterKey',
'ParameterValue',
clean=True)

params = transform_utils.dict_to_kv(params, 'ParameterKey', 'ParameterValue', clean=True)
params = params + transform_utils.reuse_vals(reuse_params)
LOGGER.debug(
'Returning parameters: %s',
params)

return params
11 changes: 10 additions & 1 deletion lambda_layers/dependencies/python/managers/stack_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
from abc import ABCMeta, abstractmethod

from exceptions import AlreadyExists, InvalidInput, NoSuchObject, \
PermissionDenied, InsufficientCapabilities, LimitExceeded, UnknownError
PermissionDenied, InsufficientCapabilities, LimitExceeded, \
UnknownParameter, UnknownError

import boto3
from botocore.exceptions import ValidationError, ClientError
Expand Down Expand Up @@ -131,6 +132,11 @@ def create_stack(self, stack_name, payload):
exc_info=True)

raise LimitExceeded from ex
except ClientError as e:
LOGGER.exception(e)
if e.response['Error']['Code'] == 'ValidationError' and \
'do not exist in the template' in e.response['Error']['Message']:
raise UnknownParameter from e
except Exception as ex:
LOGGER.exception(
'Unknown error.', exc_info=True)
Expand Down Expand Up @@ -217,6 +223,9 @@ def update_stack(self, payload):
)
except ClientError as e:
LOGGER.exception(e)
if e.response['Error']['Code'] == 'ValidationError' and \
'do not exist in the template' in e.response['Error']['Message']:
raise UnknownParameter(e.response['Error']['Message'])
if e.response['Error']['Code'] == 'ValidationError' and \
'does not exist' in e.response['Error']['Message']:
raise NoSuchObject from e
Expand Down
4 changes: 2 additions & 2 deletions lambda_layers/dependencies/python/response_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def success(data, code=200):


def error(data, code=500):
raise Exception({
raise Exception(json.dumps({
"body": prepareBody(data),
"statusCode": code
})
}))


def prepareBody(data):
Expand Down
1 change: 0 additions & 1 deletion lambda_layers/dependencies/python/tests/elb_helper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,3 @@ def test_uniqueness(client):
result1 = elb_helper.get_random_rule_priority(listener_arn)
result2 = elb_helper.get_random_rule_priority(listener_arn)
assert result1 != result2

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
def test_success_with_string_param():
expected_response = {
'body': 'Woo!',
'headers': {'Content-Type': 'application/json'},
'statusCode': 200
}

Expand Down
16 changes: 0 additions & 16 deletions src/apps/create_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@

def post(event, _context):
""" Creates a new app belonging to the authenticated user.
Args:
None:
Basic Usage:
>>> POST /apps
>>> Payload Example:
{
"name": "my-app",
"health_check_path": "/", [Optional]
"tasks": "1", [Optional]
"image": "nginx:latest", [Optional]
"subtype": "shared-lb", [Optional]
"version": "latest" [Optional]
}
Returns:
List: List of JSON objects containing app information
"""
app = AppManager(event)

Expand Down
17 changes: 0 additions & 17 deletions src/apps/list_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,6 @@

def get(event, _context):
""" Returns the apps belonging to the authenticated user.
Args:
None:
Basic Usage:
>>> GET /apps
Returns:
Dict: Dict with list of JSON object containing app information
{
'apps'
[
{
'name': 'mystack',
'description': 'status'
...
}
]
}
"""
LOGGER.debug('Instantiating AppManager.')
app = AppManager(event)
Expand Down
7 changes: 0 additions & 7 deletions src/apps/name/delete_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,6 @@
def delete(event, _context):
""" Validates that the app belongs to the authenticated user
and deletes the app.
Args:
name (string): Name of the app (CloudFormation Stack)
Basic Usage:
>>> DELETE /apps/my-app
Returns:
List: List of JSON objects containing app information
"""
app = AppManager(event)

Expand Down
17 changes: 0 additions & 17 deletions src/apps/name/describe_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,6 @@

def get(event, _context):
""" Describes detailed information about an app
Args:
name (string): Name of the app (CloudFormation Stack)
Basic Usage:
>>> GET /apps/my-app
Returns:
Dict: Dict with list of JSON object containing app information
{
'apps'
[
{
'name': 'mystack',
'description': 'status'
...
}
]
}
"""
app = AppManager(event)

Expand Down
Loading

0 comments on commit d8dc5d0

Please sign in to comment.