Mitch Garnaat

Fixing issue with aliases after the first deployment. Make sure role and policy…

… names have the environment name in them.
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
13 # limitations under the License. 13 # limitations under the License.
14 14
15 import logging 15 import logging
16 -import os
17 16
18 import jmespath 17 import jmespath
19 import boto3 18 import boto3
...@@ -44,9 +43,11 @@ class AWSClient(object): ...@@ -44,9 +43,11 @@ class AWSClient(object):
44 return self._profile_name 43 return self._profile_name
45 44
46 def _create_client(self): 45 def _create_client(self):
46 + global recording_path
47 session = boto3.session.Session( 47 session = boto3.session.Session(
48 region_name=self._region_name, profile_name=self._profile_name) 48 region_name=self._region_name, profile_name=self._profile_name)
49 - placebo.attach(session) 49 + if recording_path:
50 + placebo.attach(session)
50 client = session.client(self._service_name) 51 client = session.client(self._service_name)
51 return client 52 return client
52 53
...@@ -94,12 +95,7 @@ class AWSClient(object): ...@@ -94,12 +95,7 @@ class AWSClient(object):
94 95
95 _client_cache = {} 96 _client_cache = {}
96 97
97 - 98 +recording_path = None
98 -def save_recordings(recording_path):
99 - for key in _client_cache:
100 - client = _client_cache[key]
101 - full_path = os.path.join(recording_path, '{}.json'.format(key))
102 - client.client.meta.placebo.save(full_path)
103 99
104 100
105 def create_client(service_name, context): 101 def create_client(service_name, context):
...@@ -109,16 +105,5 @@ def create_client(service_name, context): ...@@ -109,16 +105,5 @@ def create_client(service_name, context):
109 if client_key not in _client_cache: 105 if client_key not in _client_cache:
110 client = AWSClient(service_name, context.region, 106 client = AWSClient(service_name, context.region,
111 context.profile) 107 context.profile)
112 - if 'placebo' in context.config:
113 - placebo_cfg = context.config['placebo']
114 - if placebo_cfg.get('mode') == 'play':
115 - full_path = os.path.join(
116 - placebo_cfg['recording_path'],
117 - '{}.json'.format(client_key))
118 - if os.path.exists(full_path):
119 - client.client.meta.placebo.load(full_path)
120 - client.client.meta.placebo.start()
121 - elif placebo_cfg['mode'] == 'record':
122 - client.client.meta.placebo.record()
123 _client_cache[client_key] = client 108 _client_cache[client_key] = client
124 return _client_cache[client_key] 109 return _client_cache[client_key]
......
...@@ -18,12 +18,11 @@ import time ...@@ -18,12 +18,11 @@ import time
18 import os 18 import os
19 import shutil 19 import shutil
20 20
21 -from botocore.exceptions import ClientError
22 -
23 import kappa.function 21 import kappa.function
24 import kappa.event_source 22 import kappa.event_source
25 import kappa.policy 23 import kappa.policy
26 import kappa.role 24 import kappa.role
25 +import kappa.awsclient
27 26
28 LOG = logging.getLogger(__name__) 27 LOG = logging.getLogger(__name__)
29 28
...@@ -34,15 +33,15 @@ InfoFmtString = '...%(message)s' ...@@ -34,15 +33,15 @@ InfoFmtString = '...%(message)s'
34 class Context(object): 33 class Context(object):
35 34
36 def __init__(self, config_file, environment=None, 35 def __init__(self, config_file, environment=None,
37 - debug=False, force=False): 36 + debug=False, recording_path=None):
38 if debug: 37 if debug:
39 self.set_logger('kappa', logging.DEBUG) 38 self.set_logger('kappa', logging.DEBUG)
40 else: 39 else:
41 self.set_logger('kappa', logging.INFO) 40 self.set_logger('kappa', logging.INFO)
41 + kappa.awsclient.recording_path = recording_path
42 self._load_cache() 42 self._load_cache()
43 self.config = yaml.load(config_file) 43 self.config = yaml.load(config_file)
44 self.environment = environment 44 self.environment = environment
45 - self.force = force
46 self.policy = kappa.policy.Policy( 45 self.policy = kappa.policy.Policy(
47 self, self.config['environments'][self.environment]) 46 self, self.config['environments'][self.environment])
48 self.role = kappa.role.Role( 47 self.role = kappa.role.Role(
...@@ -104,9 +103,13 @@ class Context(object): ...@@ -104,9 +103,13 @@ class Context(object):
104 return self.config.get('tests', '_tests') 103 return self.config.get('tests', '_tests')
105 104
106 @property 105 @property
106 + def source_dir(self):
107 + return self.config.get('source', '_src')
108 +
109 + @property
107 def unit_test_runner(self): 110 def unit_test_runner(self):
108 return self.config.get('unit_test_runner', 111 return self.config.get('unit_test_runner',
109 - 'nosetests . ../_tests/unit/') 112 + 'nosetests . ../{}/unit/'.format(self.test_dir))
110 113
111 @property 114 @property
112 def exec_role_arn(self): 115 def exec_role_arn(self):
......
...@@ -66,10 +66,6 @@ class Function(object): ...@@ -66,10 +66,6 @@ class Function(object):
66 return '{}.zip'.format(self._context.name) 66 return '{}.zip'.format(self._context.name)
67 67
68 @property 68 @property
69 - def path(self):
70 - return self._config.get('path', '_src')
71 -
72 - @property
73 def tests(self): 69 def tests(self):
74 return self._config.get('tests', '_tests') 70 return self._config.get('tests', '_tests')
75 71
...@@ -137,7 +133,7 @@ class Function(object): ...@@ -137,7 +133,7 @@ class Function(object):
137 # changed and needs to be updated so return True. 133 # changed and needs to be updated so return True.
138 changed = True 134 changed = True
139 self._copy_config_file() 135 self._copy_config_file()
140 - self.zip_lambda_function(self.zipfile_name, self.path) 136 + self.zip_lambda_function(self.zipfile_name, self._context.source_dir)
141 m = hashlib.md5() 137 m = hashlib.md5()
142 with open(self.zipfile_name, 'rb') as fp: 138 with open(self.zipfile_name, 'rb') as fp:
143 m.update(fp.read()) 139 m.update(fp.read())
...@@ -175,9 +171,9 @@ class Function(object): ...@@ -175,9 +171,9 @@ class Function(object):
175 171
176 def _copy_config_file(self): 172 def _copy_config_file(self):
177 config_name = '{}_config.json'.format(self._context.environment) 173 config_name = '{}_config.json'.format(self._context.environment)
178 - config_path = os.path.join(self.path, config_name) 174 + config_path = os.path.join(self._context.source_dir, config_name)
179 if os.path.exists(config_path): 175 if os.path.exists(config_path):
180 - dest_path = os.path.join(self.path, 'config.json') 176 + dest_path = os.path.join(self._context.source_dir, 'config.json')
181 LOG.debug('copy %s to %s', config_path, dest_path) 177 LOG.debug('copy %s to %s', config_path, dest_path)
182 shutil.copy2(config_path, dest_path) 178 shutil.copy2(config_path, dest_path)
183 179
...@@ -228,20 +224,24 @@ class Function(object): ...@@ -228,20 +224,24 @@ class Function(object):
228 LOG.exception('Unable to list aliases') 224 LOG.exception('Unable to list aliases')
229 return response['Versions'] 225 return response['Versions']
230 226
231 - def create_alias(self, name, description, version=None): 227 + def find_latest_version(self):
232 # Find the current (latest) version by version number 228 # Find the current (latest) version by version number
233 # First find the SHA256 of $LATEST 229 # First find the SHA256 of $LATEST
234 - if not version: 230 + versions = self.list_versions()
235 - versions = self.list_versions() 231 + for v in versions:
236 - for v in versions: 232 + if v['Version'] == '$LATEST':
237 - if v['Version'] == '$LATEST': 233 + latest_sha256 = v['CodeSha256']
238 - latest_sha256 = v['CodeSha256'] 234 + break
235 + for v in versions:
236 + if v['Version'] != '$LATEST':
237 + if v['CodeSha256'] == latest_sha256:
238 + version = v['Version']
239 break 239 break
240 - for v in versions: 240 + return version
241 - if v['Version'] != '$LATEST': 241 +
242 - if v['CodeSha256'] == latest_sha256: 242 + def create_alias(self, name, description, version=None):
243 - version = v['Version'] 243 + if not version:
244 - break 244 + version = self.find_latest_version()
245 try: 245 try:
246 LOG.debug('creating alias %s=%s', name, version) 246 LOG.debug('creating alias %s=%s', name, version)
247 response = self._lambda_client.call( 247 response = self._lambda_client.call(
...@@ -254,6 +254,23 @@ class Function(object): ...@@ -254,6 +254,23 @@ class Function(object):
254 except Exception: 254 except Exception:
255 LOG.exception('Unable to create alias') 255 LOG.exception('Unable to create alias')
256 256
257 + def update_alias(self, name, description, version=None):
258 + # Find the current (latest) version by version number
259 + # First find the SHA256 of $LATEST
260 + if not version:
261 + version = self.find_latest_version()
262 + try:
263 + LOG.debug('updating alias %s=%s', name, version)
264 + response = self._lambda_client.call(
265 + 'update_alias',
266 + FunctionName=self.name,
267 + Description=description,
268 + FunctionVersion=version,
269 + Name=name)
270 + LOG.debug(response)
271 + except Exception:
272 + LOG.exception('Unable to update alias')
273 +
257 def add_permissions(self): 274 def add_permissions(self):
258 if self.permissions: 275 if self.permissions:
259 time.sleep(5) 276 time.sleep(5)
...@@ -329,7 +346,7 @@ class Function(object): ...@@ -329,7 +346,7 @@ class Function(object):
329 ZipFile=zipdata, 346 ZipFile=zipdata,
330 Publish=True) 347 Publish=True)
331 LOG.debug(response) 348 LOG.debug(response)
332 - self.create_alias( 349 + self.update_alias(
333 self._context.environment, 350 self._context.environment,
334 'For the {} stage'.format(self._context.environment)) 351 'For the {} stage'.format(self._context.environment))
335 except Exception: 352 except Exception:
......
...@@ -35,7 +35,7 @@ class Policy(object): ...@@ -35,7 +35,7 @@ class Policy(object):
35 35
36 @property 36 @property
37 def name(self): 37 def name(self):
38 - return self._context.name 38 + return '{}_{}'.format(self._context.name, self.environment)
39 39
40 @property 40 @property
41 def description(self): 41 def description(self):
......
...@@ -45,7 +45,7 @@ class Role(object): ...@@ -45,7 +45,7 @@ class Role(object):
45 45
46 @property 46 @property
47 def name(self): 47 def name(self):
48 - return self._context.name 48 + return '{}_{}'.format(self._context.name, self._context.environment)
49 49
50 @property 50 @property
51 def arn(self): 51 def arn(self):
......
...@@ -40,9 +40,14 @@ pass_ctx = click.make_pass_decorator(Context) ...@@ -40,9 +40,14 @@ pass_ctx = click.make_pass_decorator(Context)
40 default='dev', 40 default='dev',
41 help='Specify which environment to work with (default dev)' 41 help='Specify which environment to work with (default dev)'
42 ) 42 )
43 +@click.option(
44 + '--record-path',
45 + type=click.Path(exists=True, file_okay=False, writable=True),
46 + help='Uses placebo to record AWS responses to this path'
47 +)
43 @click.pass_context 48 @click.pass_context
44 -def cli(ctx, config=None, debug=False, env=None): 49 +def cli(ctx, config=None, debug=False, env=None, record_path=None):
45 - ctx.obj = Context(config, env, debug) 50 + ctx.obj = Context(config, env, debug, record_path)
46 51
47 52
48 @cli.command() 53 @cli.command()
......