Mitch Garnaat

Some refactoring. Added a status command. Rewrote the CLI to take more advantage of click.

...@@ -18,47 +18,96 @@ import click ...@@ -18,47 +18,96 @@ import click
18 from kappa.context import Context 18 from kappa.context import Context
19 19
20 20
21 -@click.command() 21 +@click.group()
22 -@click.option( 22 +@click.argument(
23 - '--config', 23 + 'config',
24 - help="Path to the Kappa config YAML file",
25 type=click.File('rb'), 24 type=click.File('rb'),
26 envvar='KAPPA_CONFIG', 25 envvar='KAPPA_CONFIG',
27 - default=None
28 ) 26 )
29 @click.option( 27 @click.option(
30 '--debug/--no-debug', 28 '--debug/--no-debug',
31 default=False, 29 default=False,
32 help='Turn on debugging output' 30 help='Turn on debugging output'
33 ) 31 )
34 -@click.argument( 32 +@click.pass_context
35 - 'command', 33 +def cli(ctx, config=None, debug=False):
36 - required=True, 34 + config = config
37 - type=click.Choice(['deploy', 'test', 'tail', 'add-event-sources', 'delete']) 35 + ctx.obj['debug'] = debug
38 -) 36 + ctx.obj['config'] = config
39 -def main(config=None, debug=False, command=None): 37 +
40 - ctx = Context(config, debug) 38 +@cli.command()
41 - if command == 'deploy': 39 +@click.pass_context
42 - click.echo('Deploying ...') 40 +def deploy(ctx):
43 - ctx.deploy() 41 + context = Context(ctx.obj['config'], ctx.obj['debug'])
44 - click.echo('...done') 42 + click.echo('deploying...')
45 - elif command == 'test': 43 + context.deploy()
46 - click.echo('Sending test data ...') 44 + click.echo('...done')
47 - ctx.test() 45 +
48 - click.echo('...done') 46 +@cli.command()
49 - elif command == 'tail': 47 +@click.pass_context
50 - events = ctx.tail() 48 +def test(ctx):
51 - for event in events: 49 + context = Context(ctx.obj['config'], ctx.obj['debug'])
52 - print(event['message']) 50 + click.echo('testing...')
53 - elif command == 'delete': 51 + context.test()
54 - click.echo('Deleting ...') 52 + click.echo('...done')
55 - ctx.delete() 53 +
56 - click.echo('...done') 54 +@cli.command()
57 - elif command == 'add-event-sources': 55 +@click.pass_context
58 - click.echo('Adding event sources ...') 56 +def tail(ctx):
59 - ctx.add_event_sources() 57 + context = Context(ctx.obj['config'], ctx.obj['debug'])
60 - click.echo('...done') 58 + click.echo('tailing logs...')
59 + context.tail()
60 + click.echo('...done')
61 +
62 +@cli.command()
63 +@click.pass_context
64 +def status(ctx):
65 + context = Context(ctx.obj['config'], ctx.obj['debug'])
66 + status = context.status()
67 + click.echo(click.style('Stack', bold=True))
68 + if status['stack']:
69 + for stack in status['stack']['Stacks']:
70 + line = ' {}: {}'.format(stack['StackId'], stack['StackStatus'])
71 + click.echo(click.style(line, fg='green'))
72 + else:
73 + click.echo(click.style(' None', fg='green'))
74 + click.echo(click.style('Function', bold=True))
75 + if status['function']:
76 + line = ' {}'.format(
77 + status['function']['Configuration']['FunctionName'])
78 + click.echo(click.style(line, fg='green'))
79 + else:
80 + click.echo(click.style(' None', fg='green'))
81 + click.echo(click.style('Event Sources', bold=True))
82 + if status['event_sources']:
83 + for event_source in status['event_sources']:
84 + if 'EventSource' in event_source:
85 + line = ' {}: {}'.format(
86 + event_source['EventSource'], event_source['IsActive'])
87 + click.echo(click.style(line, fg='green'))
88 + else:
89 + line = ' {}'.format(
90 + event_source['CloudFunctionConfiguration']['Id'])
91 + click.echo(click.style(line, fg='green'))
92 + else:
93 + click.echo(click.style(' None', fg='green'))
94 +
95 +@cli.command()
96 +@click.pass_context
97 +def delete(ctx):
98 + context = Context(ctx.obj['config'], ctx.obj['debug'])
99 + click.echo('deleting...')
100 + context.delete()
101 + click.echo('...done')
102 +
103 +@cli.command()
104 +@click.pass_context
105 +def add_event_sources(ctx):
106 + context = Context(ctx.obj['config'], ctx.obj['debug'])
107 + click.echo('adding event sources...')
108 + context.add_event_sources()
109 + click.echo('...done')
61 110
62 111
63 if __name__ == '__main__': 112 if __name__ == '__main__':
64 - main() 113 + cli(obj={})
......
...@@ -108,10 +108,7 @@ class Context(object): ...@@ -108,10 +108,7 @@ class Context(object):
108 event_source.add(self.function) 108 event_source.add(self.function)
109 109
110 def deploy(self): 110 def deploy(self):
111 - if self._stack.exists(): 111 + self._stack.update()
112 - self._stack.update()
113 - else:
114 - self._stack.create()
115 self.function.upload() 112 self.function.upload()
116 113
117 def test(self): 114 def test(self):
...@@ -123,3 +120,14 @@ class Context(object): ...@@ -123,3 +120,14 @@ class Context(object):
123 def delete(self): 120 def delete(self):
124 self._stack.delete() 121 self._stack.delete()
125 self.function.delete() 122 self.function.delete()
123 + for event_source in self.event_sources:
124 + event_source.remove(self.function)
125 +
126 + def status(self):
127 + status = {}
128 + status['stack'] = self._stack.status()
129 + status['function'] = self.function.status()
130 + status['event_sources'] = []
131 + for event_source in self.event_sources:
132 + status['event_sources'].append(event_source.status(self.function))
133 + return status
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
13 13
14 import logging 14 import logging
15 15
16 +from botocore.exceptions import ClientError
17 +
16 import kappa.aws 18 import kappa.aws
17 19
18 LOG = logging.getLogger(__name__) 20 LOG = logging.getLogger(__name__)
...@@ -70,6 +72,17 @@ class KinesisEventSource(EventSource): ...@@ -70,6 +72,17 @@ class KinesisEventSource(EventSource):
70 LOG.debug(response) 72 LOG.debug(response)
71 return response 73 return response
72 74
75 + def status(self, function):
76 + LOG.debug('getting status for event source %s', self.arn)
77 + try:
78 + response = self._lambda.get_event_source(
79 + UUID=self._get_uuid(function))
80 + LOG.debug(response)
81 + except ClientError:
82 + LOG.debug('event source %s does not exist', self.arn)
83 + response = None
84 + return response
85 +
73 86
74 class S3EventSource(EventSource): 87 class S3EventSource(EventSource):
75 88
...@@ -112,3 +125,12 @@ class S3EventSource(EventSource): ...@@ -112,3 +125,12 @@ class S3EventSource(EventSource):
112 Bucket=self._get_bucket_name(), 125 Bucket=self._get_bucket_name(),
113 NotificationConfiguration=response) 126 NotificationConfiguration=response)
114 LOG.debug(response) 127 LOG.debug(response)
128 +
129 + def status(self, function):
130 + LOG.debug('status for s3 notification')
131 + response = self._s3.get_bucket_notification(
132 + Bucket=self._get_bucket_name())
133 + LOG.debug(response)
134 + if 'CloudFunctionConfiguration' not in response:
135 + response = None
136 + return response
......
...@@ -15,6 +15,8 @@ import logging ...@@ -15,6 +15,8 @@ import logging
15 import os 15 import os
16 import zipfile 16 import zipfile
17 17
18 +from botocore.exceptions import ClientError
19 +
18 import kappa.aws 20 import kappa.aws
19 import kappa.log 21 import kappa.log
20 22
...@@ -148,6 +150,17 @@ class Function(object): ...@@ -148,6 +150,17 @@ class Function(object):
148 LOG.debug(response) 150 LOG.debug(response)
149 return response 151 return response
150 152
153 + def status(self):
154 + LOG.debug('getting status for function %s', self.name)
155 + try:
156 + response = self._lambda_svc.get_function(
157 + FunctionName=self.name)
158 + LOG.debug(response)
159 + except ClientError:
160 + LOG.debug('function %s not found', self.name)
161 + response = None
162 + return response
163 +
151 def invoke_asynch(self, data_file): 164 def invoke_asynch(self, data_file):
152 LOG.debug('_invoke_async %s', data_file) 165 LOG.debug('_invoke_async %s', data_file)
153 with open(data_file) as fp: 166 with open(data_file) as fp:
......
...@@ -98,7 +98,7 @@ class Stack(object): ...@@ -98,7 +98,7 @@ class Stack(object):
98 msg = 'Could not create stack %s: %s' % (self.name, status) 98 msg = 'Could not create stack %s: %s' % (self.name, status)
99 raise ValueError(msg) 99 raise ValueError(msg)
100 100
101 - def create(self): 101 + def _create(self):
102 LOG.debug('create_stack: stack_name=%s', self.name) 102 LOG.debug('create_stack: stack_name=%s', self.name)
103 template_body = open(self.template_path).read() 103 template_body = open(self.template_path).read()
104 try: 104 try:
...@@ -110,7 +110,7 @@ class Stack(object): ...@@ -110,7 +110,7 @@ class Stack(object):
110 LOG.exception('Unable to create stack') 110 LOG.exception('Unable to create stack')
111 self.wait() 111 self.wait()
112 112
113 - def update(self): 113 + def _update(self):
114 LOG.debug('create_stack: stack_name=%s', self.name) 114 LOG.debug('create_stack: stack_name=%s', self.name)
115 template_body = open(self.template_path).read() 115 template_body = open(self.template_path).read()
116 try: 116 try:
...@@ -125,6 +125,15 @@ class Stack(object): ...@@ -125,6 +125,15 @@ class Stack(object):
125 LOG.exception('Unable to update stack') 125 LOG.exception('Unable to update stack')
126 self.wait() 126 self.wait()
127 127
128 + def update(self):
129 + if self.exists():
130 + self._update()
131 + else:
132 + self._create()
133 +
134 + def status(self):
135 + return self.exists()
136 +
128 def delete(self): 137 def delete(self):
129 LOG.debug('delete_stack: stack_name=%s', self.name) 138 LOG.debug('delete_stack: stack_name=%s', self.name)
130 try: 139 try:
......
1 -botocore==0.82.0 1 +botocore==0.94.0
2 click==3.3 2 click==3.3
3 PyYAML>=3.11 3 PyYAML>=3.11
4 mock>=1.0.1 4 mock>=1.0.1
......
...@@ -5,7 +5,7 @@ from setuptools import setup, find_packages ...@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
5 import os 5 import os
6 6
7 requires = [ 7 requires = [
8 - 'botocore==0.82.0', 8 + 'botocore==0.94.0',
9 'click==3.3', 9 'click==3.3',
10 'PyYAML>=3.11' 10 'PyYAML>=3.11'
11 ] 11 ]
......
...@@ -56,10 +56,10 @@ class TestStack(unittest.TestCase): ...@@ -56,10 +56,10 @@ class TestStack(unittest.TestCase):
56 stack = Stack(mock_context, Config) 56 stack = Stack(mock_context, Config)
57 self.assertTrue(stack.exists()) 57 self.assertTrue(stack.exists())
58 58
59 - def test_create(self): 59 + def test_update(self):
60 mock_context = mock.Mock() 60 mock_context = mock.Mock()
61 stack = Stack(mock_context, Config) 61 stack = Stack(mock_context, Config)
62 - stack.create() 62 + stack.update()
63 63
64 def test_delete(self): 64 def test_delete(self):
65 mock_context = mock.Mock() 65 mock_context = mock.Mock()
......