Some refactoring. Added a status command. Rewrote the CLI to take more advantage of click.
Showing
8 changed files
with
139 additions
and
38 deletions
... | @@ -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']) |
42 | + click.echo('deploying...') | ||
43 | + context.deploy() | ||
44 | click.echo('...done') | 44 | click.echo('...done') |
45 | - elif command == 'test': | 45 | + |
46 | - click.echo('Sending test data ...') | 46 | +@cli.command() |
47 | - ctx.test() | 47 | +@click.pass_context |
48 | +def test(ctx): | ||
49 | + context = Context(ctx.obj['config'], ctx.obj['debug']) | ||
50 | + click.echo('testing...') | ||
51 | + context.test() | ||
48 | click.echo('...done') | 52 | click.echo('...done') |
49 | - elif command == 'tail': | 53 | + |
50 | - events = ctx.tail() | 54 | +@cli.command() |
51 | - for event in events: | 55 | +@click.pass_context |
52 | - print(event['message']) | 56 | +def tail(ctx): |
53 | - elif command == 'delete': | 57 | + context = Context(ctx.obj['config'], ctx.obj['debug']) |
54 | - click.echo('Deleting ...') | 58 | + click.echo('tailing logs...') |
55 | - ctx.delete() | 59 | + context.tail() |
56 | click.echo('...done') | 60 | click.echo('...done') |
57 | - elif command == 'add-event-sources': | 61 | + |
58 | - click.echo('Adding event sources ...') | 62 | +@cli.command() |
59 | - ctx.add_event_sources() | 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() | ||
60 | click.echo('...done') | 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(): | ||
112 | self._stack.update() | 111 | 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: | ... | ... |
... | @@ -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() | ... | ... |
-
Please register or login to post a comment