Mitch Garnaat

Add the ability to generate the config files based on the environment specified.

...@@ -25,12 +25,13 @@ import kappa.role ...@@ -25,12 +25,13 @@ import kappa.role
25 LOG = logging.getLogger(__name__) 25 LOG = logging.getLogger(__name__)
26 26
27 DebugFmtString = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 27 DebugFmtString = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
28 -InfoFmtString = '\t%(message)s' 28 +InfoFmtString = '...%(message)s'
29 29
30 30
31 class Context(object): 31 class Context(object):
32 32
33 - def __init__(self, config_file, environment=None, debug=False): 33 + def __init__(self, config_file, environment=None,
34 + debug=False, force=False):
34 if debug: 35 if debug:
35 self.set_logger('kappa', logging.DEBUG) 36 self.set_logger('kappa', logging.DEBUG)
36 else: 37 else:
...@@ -38,6 +39,7 @@ class Context(object): ...@@ -38,6 +39,7 @@ class Context(object):
38 self._load_cache() 39 self._load_cache()
39 self.config = yaml.load(config_file) 40 self.config = yaml.load(config_file)
40 self.environment = environment 41 self.environment = environment
42 + self.force = force
41 self.policy = kappa.policy.Policy( 43 self.policy = kappa.policy.Policy(
42 self, self.config['environments'][self.environment]) 44 self, self.config['environments'][self.environment])
43 self.role = kappa.role.Role( 45 self.role = kappa.role.Role(
...@@ -173,6 +175,9 @@ class Context(object): ...@@ -173,6 +175,9 @@ class Context(object):
173 def invoke(self): 175 def invoke(self):
174 return self.function.invoke() 176 return self.function.invoke()
175 177
178 + def test(self):
179 + return self.function.invoke()
180 +
176 def dryrun(self): 181 def dryrun(self):
177 return self.function.dryrun() 182 return self.function.dryrun()
178 183
......
...@@ -16,6 +16,7 @@ import logging ...@@ -16,6 +16,7 @@ import logging
16 import os 16 import os
17 import zipfile 17 import zipfile
18 import time 18 import time
19 +import shutil
19 20
20 from botocore.exceptions import ClientError 21 from botocore.exceptions import ClientError
21 22
...@@ -122,7 +123,7 @@ class Function(object): ...@@ -122,7 +123,7 @@ class Function(object):
122 return self._log 123 return self._log
123 124
124 def tail(self): 125 def tail(self):
125 - LOG.debug('tailing function: %s', self.name) 126 + LOG.info('tailing function: %s', self.name)
126 return self.log.tail() 127 return self.log.tail()
127 128
128 def _zip_lambda_dir(self, zipfile_name, lambda_dir): 129 def _zip_lambda_dir(self, zipfile_name, lambda_dir):
...@@ -175,8 +176,17 @@ class Function(object): ...@@ -175,8 +176,17 @@ class Function(object):
175 except Exception: 176 except Exception:
176 LOG.exception('Unable to add permission') 177 LOG.exception('Unable to add permission')
177 178
179 + def _copy_config_file(self):
180 + config_name = '{}_config.json'.format(self._context.environment)
181 + config_path = os.path.join(self.path, config_name)
182 + if os.path.exists(config_path):
183 + dest_path = os.path.join(self.path, 'config.json')
184 + LOG.info('copy {} to {}'.format(config_path, dest_path))
185 + shutil.copyfile(config_path, dest_path)
186 +
178 def create(self): 187 def create(self):
179 - LOG.debug('creating %s', self.zipfile_name) 188 + LOG.info('creating function %s', self.name)
189 + self._copy_config_file()
180 self.zip_lambda_function(self.zipfile_name, self.path) 190 self.zip_lambda_function(self.zipfile_name, self.path)
181 with open(self.zipfile_name, 'rb') as fp: 191 with open(self.zipfile_name, 'rb') as fp:
182 exec_role = self._context.exec_role_arn 192 exec_role = self._context.exec_role_arn
...@@ -198,15 +208,26 @@ class Function(object): ...@@ -198,15 +208,26 @@ class Function(object):
198 LOG.exception('Unable to upload zip file') 208 LOG.exception('Unable to upload zip file')
199 self.add_permissions() 209 self.add_permissions()
200 210
201 - def update(self): 211 + def _do_update(self):
202 - LOG.debug('updating %s', self.zipfile_name) 212 + do_update = False
203 - self.zip_lambda_function(self.zipfile_name, self.path) 213 + if self._context.force:
214 + do_update = True
215 + else:
204 stats = os.stat(self.zipfile_name) 216 stats = os.stat(self.zipfile_name)
205 if self._context.cache.get('zipfile_size') != stats.st_size: 217 if self._context.cache.get('zipfile_size') != stats.st_size:
206 self._context.cache['zipfile_size'] = stats.st_size 218 self._context.cache['zipfile_size'] = stats.st_size
219 + do_update = True
220 + return do_update
221 +
222 + def update(self):
223 + LOG.info('updating %s', self.name)
224 + self._copy_config_file()
225 + if self._do_update():
207 self._context.save_cache() 226 self._context.save_cache()
208 with open(self.zipfile_name, 'rb') as fp: 227 with open(self.zipfile_name, 'rb') as fp:
209 try: 228 try:
229 + LOG.info('uploading new function zipfile %s',
230 + self.zipfile_name)
210 zipdata = fp.read() 231 zipdata = fp.read()
211 response = self._lambda_client.call( 232 response = self._lambda_client.call(
212 'update_function_code', 233 'update_function_code',
...@@ -214,9 +235,9 @@ class Function(object): ...@@ -214,9 +235,9 @@ class Function(object):
214 ZipFile=zipdata) 235 ZipFile=zipdata)
215 LOG.debug(response) 236 LOG.debug(response)
216 except Exception: 237 except Exception:
217 - LOG.exception('Unable to update zip file') 238 + LOG.exception('unable to update zip file')
218 else: 239 else:
219 - LOG.info('Code has not changed') 240 + LOG.info('function has not changed')
220 241
221 def deploy(self): 242 def deploy(self):
222 if self.exists(): 243 if self.exists():
...@@ -224,7 +245,7 @@ class Function(object): ...@@ -224,7 +245,7 @@ class Function(object):
224 return self.create() 245 return self.create()
225 246
226 def publish_version(self, description): 247 def publish_version(self, description):
227 - LOG.debug('publishing version of %s', self.name) 248 + LOG.info('publishing version of %s', self.name)
228 try: 249 try:
229 response = self._lambda_client.call( 250 response = self._lambda_client.call(
230 'publish_version', 251 'publish_version',
...@@ -237,7 +258,7 @@ class Function(object): ...@@ -237,7 +258,7 @@ class Function(object):
237 return response['Version'] 258 return response['Version']
238 259
239 def list_versions(self): 260 def list_versions(self):
240 - LOG.debug('listing versions of %s', self.name) 261 + LOG.info('listing versions of %s', self.name)
241 try: 262 try:
242 response = self._lambda_client.call( 263 response = self._lambda_client.call(
243 'list_versions_by_function', 264 'list_versions_by_function',
...@@ -248,7 +269,7 @@ class Function(object): ...@@ -248,7 +269,7 @@ class Function(object):
248 return response['Versions'] 269 return response['Versions']
249 270
250 def create_alias(self, name, description, version=None): 271 def create_alias(self, name, description, version=None):
251 - LOG.debug('creating alias of %s', self.name) 272 + LOG.info('creating alias of %s', self.name)
252 if version is None: 273 if version is None:
253 version = self.version 274 version = self.version
254 try: 275 try:
...@@ -263,7 +284,7 @@ class Function(object): ...@@ -263,7 +284,7 @@ class Function(object):
263 LOG.exception('Unable to create alias') 284 LOG.exception('Unable to create alias')
264 285
265 def list_aliases(self): 286 def list_aliases(self):
266 - LOG.debug('listing aliases of %s', self.name) 287 + LOG.info('listing aliases of %s', self.name)
267 try: 288 try:
268 response = self._lambda_client.call( 289 response = self._lambda_client.call(
269 'list_aliases', 290 'list_aliases',
...@@ -279,7 +300,7 @@ class Function(object): ...@@ -279,7 +300,7 @@ class Function(object):
279 self.create_alias(name, description, version) 300 self.create_alias(name, description, version)
280 301
281 def delete(self): 302 def delete(self):
282 - LOG.debug('deleting function %s', self.name) 303 + LOG.info('deleting function %s', self.name)
283 response = None 304 response = None
284 try: 305 try:
285 response = self._lambda_client.call( 306 response = self._lambda_client.call(
...@@ -291,7 +312,6 @@ class Function(object): ...@@ -291,7 +312,6 @@ class Function(object):
291 return response 312 return response
292 313
293 def status(self): 314 def status(self):
294 - LOG.debug('getting status for function %s', self.name)
295 try: 315 try:
296 response = self._lambda_client.call( 316 response = self._lambda_client.call(
297 'get_function', 317 'get_function',
......
...@@ -121,10 +121,10 @@ class Policy(object): ...@@ -121,10 +121,10 @@ class Policy(object):
121 LOG.exception('Error creating new Policy version') 121 LOG.exception('Error creating new Policy version')
122 122
123 def deploy(self): 123 def deploy(self):
124 - LOG.debug('deploying policy %s', self.name) 124 + LOG.info('deploying policy %s', self.name)
125 document = self.document() 125 document = self.document()
126 if not document: 126 if not document:
127 - LOG.debug('not a custom policy, no need to create it') 127 + LOG.info('not a custom policy, no need to create it')
128 return 128 return
129 policy = self.exists() 129 policy = self.exists()
130 if policy: 130 if policy:
...@@ -138,7 +138,7 @@ class Policy(object): ...@@ -138,7 +138,7 @@ class Policy(object):
138 self._context.save_cache() 138 self._context.save_cache()
139 self._add_policy_version() 139 self._add_policy_version()
140 else: 140 else:
141 - LOG.info('Policy unchanged') 141 + LOG.info('policy unchanged')
142 else: 142 else:
143 # create a new policy 143 # create a new policy
144 try: 144 try:
...@@ -157,7 +157,7 @@ class Policy(object): ...@@ -157,7 +157,7 @@ class Policy(object):
157 # This indicates that it was a custom policy created by kappa. 157 # This indicates that it was a custom policy created by kappa.
158 document = self.document() 158 document = self.document()
159 if self.arn and document: 159 if self.arn and document:
160 - LOG.debug('deleting policy %s', self.name) 160 + LOG.info('deleting policy %s', self.name)
161 response = self._iam_client.call( 161 response = self._iam_client.call(
162 'delete_policy', PolicyArn=self.arn) 162 'delete_policy', PolicyArn=self.arn)
163 LOG.debug(response) 163 LOG.debug(response)
......
...@@ -35,12 +35,18 @@ from kappa.context import Context ...@@ -35,12 +35,18 @@ from kappa.context import Context
35 '--environment', 35 '--environment',
36 help='Specify which environment to work with' 36 help='Specify which environment to work with'
37 ) 37 )
38 +@click.option(
39 + '--force/--no-force',
40 + default=False,
41 + help='Force an update of the Lambda function'
42 +)
38 @click.pass_context 43 @click.pass_context
39 -def cli(ctx, config=None, debug=False, environment=None): 44 +def cli(ctx, config=None, debug=False, environment=None, force=None):
40 config = config 45 config = config
41 ctx.obj['debug'] = debug 46 ctx.obj['debug'] = debug
42 ctx.obj['config'] = config 47 ctx.obj['config'] = config
43 ctx.obj['environment'] = environment 48 ctx.obj['environment'] = environment
49 + ctx.obj['force'] = force
44 50
45 51
46 @cli.command() 52 @cli.command()
...@@ -48,10 +54,10 @@ def cli(ctx, config=None, debug=False, environment=None): ...@@ -48,10 +54,10 @@ def cli(ctx, config=None, debug=False, environment=None):
48 def deploy(ctx): 54 def deploy(ctx):
49 """Deploy the Lambda function and any policies and roles required""" 55 """Deploy the Lambda function and any policies and roles required"""
50 context = Context(ctx.obj['config'], ctx.obj['environment'], 56 context = Context(ctx.obj['config'], ctx.obj['environment'],
51 - ctx.obj['debug']) 57 + ctx.obj['debug'], ctx.obj['force'])
52 - click.echo('deploying...') 58 + click.echo('deploying')
53 context.deploy() 59 context.deploy()
54 - click.echo('...done') 60 + click.echo('done')
55 61
56 62
57 @cli.command() 63 @cli.command()
...@@ -59,10 +65,10 @@ def deploy(ctx): ...@@ -59,10 +65,10 @@ def deploy(ctx):
59 def tag(ctx): 65 def tag(ctx):
60 """Deploy the Lambda function and any policies and roles required""" 66 """Deploy the Lambda function and any policies and roles required"""
61 context = Context(ctx.obj['config'], ctx.obj['environment'], 67 context = Context(ctx.obj['config'], ctx.obj['environment'],
62 - ctx.obj['debug']) 68 + ctx.obj['debug'], ctx.obj['force'])
63 - click.echo('deploying...') 69 + click.echo('tagging')
64 context.deploy() 70 context.deploy()
65 - click.echo('...done') 71 + click.echo('done')
66 72
67 73
68 @cli.command() 74 @cli.command()
...@@ -70,13 +76,27 @@ def tag(ctx): ...@@ -70,13 +76,27 @@ def tag(ctx):
70 def invoke(ctx): 76 def invoke(ctx):
71 """Invoke the command synchronously""" 77 """Invoke the command synchronously"""
72 context = Context(ctx.obj['config'], ctx.obj['environment'], 78 context = Context(ctx.obj['config'], ctx.obj['environment'],
73 - ctx.obj['debug']) 79 + ctx.obj['debug'], ctx.obj['force'])
74 - click.echo('invoking...') 80 + click.echo('invoking')
75 response = context.invoke() 81 response = context.invoke()
76 log_data = base64.b64decode(response['LogResult']) 82 log_data = base64.b64decode(response['LogResult'])
77 click.echo(log_data) 83 click.echo(log_data)
78 click.echo(response['Payload'].read()) 84 click.echo(response['Payload'].read())
79 - click.echo('...done') 85 + click.echo('done')
86 +
87 +
88 +@cli.command()
89 +@click.pass_context
90 +def test(ctx):
91 + """Test the command synchronously"""
92 + context = Context(ctx.obj['config'], ctx.obj['environment'],
93 + ctx.obj['debug'], ctx.obj['force'])
94 + click.echo('testing')
95 + response = context.test()
96 + log_data = base64.b64decode(response['LogResult'])
97 + click.echo(log_data)
98 + click.echo(response['Payload'].read())
99 + click.echo('done')
80 100
81 101
82 @cli.command() 102 @cli.command()
...@@ -84,11 +104,11 @@ def invoke(ctx): ...@@ -84,11 +104,11 @@ def invoke(ctx):
84 def dryrun(ctx): 104 def dryrun(ctx):
85 """Show you what would happen but don't actually do anything""" 105 """Show you what would happen but don't actually do anything"""
86 context = Context(ctx.obj['config'], ctx.obj['environment'], 106 context = Context(ctx.obj['config'], ctx.obj['environment'],
87 - ctx.obj['debug']) 107 + ctx.obj['debug'], ctx.obj['force'])
88 - click.echo('invoking dryrun...') 108 + click.echo('invoking dryrun')
89 response = context.dryrun() 109 response = context.dryrun()
90 click.echo(response) 110 click.echo(response)
91 - click.echo('...done') 111 + click.echo('done')
92 112
93 113
94 @cli.command() 114 @cli.command()
...@@ -96,11 +116,11 @@ def dryrun(ctx): ...@@ -96,11 +116,11 @@ def dryrun(ctx):
96 def invoke_async(ctx): 116 def invoke_async(ctx):
97 """Invoke the Lambda function asynchronously""" 117 """Invoke the Lambda function asynchronously"""
98 context = Context(ctx.obj['config'], ctx.obj['environment'], 118 context = Context(ctx.obj['config'], ctx.obj['environment'],
99 - ctx.obj['debug']) 119 + ctx.obj['debug'], ctx.obj['force'])
100 - click.echo('invoking async...') 120 + click.echo('invoking async')
101 response = context.invoke_async() 121 response = context.invoke_async()
102 click.echo(response) 122 click.echo(response)
103 - click.echo('...done') 123 + click.echo('done')
104 124
105 125
106 @cli.command() 126 @cli.command()
...@@ -108,12 +128,12 @@ def invoke_async(ctx): ...@@ -108,12 +128,12 @@ def invoke_async(ctx):
108 def tail(ctx): 128 def tail(ctx):
109 """Show the last 10 lines of the log file""" 129 """Show the last 10 lines of the log file"""
110 context = Context(ctx.obj['config'], ctx.obj['environment'], 130 context = Context(ctx.obj['config'], ctx.obj['environment'],
111 - ctx.obj['debug']) 131 + ctx.obj['debug'], ctx.obj['force'])
112 - click.echo('tailing logs...') 132 + click.echo('tailing logs')
113 for e in context.tail()[-10:]: 133 for e in context.tail()[-10:]:
114 ts = datetime.utcfromtimestamp(e['timestamp']//1000).isoformat() 134 ts = datetime.utcfromtimestamp(e['timestamp']//1000).isoformat()
115 click.echo("{}: {}".format(ts, e['message'])) 135 click.echo("{}: {}".format(ts, e['message']))
116 - click.echo('...done') 136 + click.echo('done')
117 137
118 138
119 @cli.command() 139 @cli.command()
...@@ -159,10 +179,10 @@ def status(ctx): ...@@ -159,10 +179,10 @@ def status(ctx):
159 def delete(ctx): 179 def delete(ctx):
160 """Delete the Lambda function and related policies and roles""" 180 """Delete the Lambda function and related policies and roles"""
161 context = Context(ctx.obj['config'], ctx.obj['environment'], 181 context = Context(ctx.obj['config'], ctx.obj['environment'],
162 - ctx.obj['debug']) 182 + ctx.obj['debug'], ctx.obj['force'])
163 - click.echo('deleting...') 183 + click.echo('deleting')
164 context.delete() 184 context.delete()
165 - click.echo('...done') 185 + click.echo('done')
166 186
167 187
168 @cli.command() 188 @cli.command()
...@@ -170,10 +190,10 @@ def delete(ctx): ...@@ -170,10 +190,10 @@ def delete(ctx):
170 def add_event_sources(ctx): 190 def add_event_sources(ctx):
171 """Add any event sources specified in the config file""" 191 """Add any event sources specified in the config file"""
172 context = Context(ctx.obj['config'], ctx.obj['environment'], 192 context = Context(ctx.obj['config'], ctx.obj['environment'],
173 - ctx.obj['debug']) 193 + ctx.obj['debug'], ctx.obj['force'])
174 - click.echo('adding event sources...') 194 + click.echo('adding event sources')
175 context.add_event_sources() 195 context.add_event_sources()
176 - click.echo('...done') 196 + click.echo('done')
177 197
178 198
179 @cli.command() 199 @cli.command()
...@@ -181,10 +201,10 @@ def add_event_sources(ctx): ...@@ -181,10 +201,10 @@ def add_event_sources(ctx):
181 def update_event_sources(ctx): 201 def update_event_sources(ctx):
182 """Update event sources specified in the config file""" 202 """Update event sources specified in the config file"""
183 context = Context(ctx.obj['config'], ctx.obj['environment'], 203 context = Context(ctx.obj['config'], ctx.obj['environment'],
184 - ctx.obj['debug']) 204 + ctx.obj['debug'], ctx.obj['force'])
185 - click.echo('updating event sources...') 205 + click.echo('updating event sources')
186 context.update_event_sources() 206 context.update_event_sources()
187 - click.echo('...done') 207 + click.echo('done')
188 208
189 209
190 cli(obj={}) 210 cli(obj={})
......