From 7c0fa2056f6e7efb6cf73c7faf40bdc908740002 Mon Sep 17 00:00:00 2001 From: Mitch Garnaat Date: Thu, 5 Mar 2015 09:01:49 -0800 Subject: [PATCH 1/2] Some refactoring. Added a status command. Rewrote the CLI to take more advantage of click. --- bin/kappa | 115 ++++++++++++++++++++++++++++----------- kappa/context.py | 16 ++++-- kappa/event_source.py | 22 ++++++++ kappa/function.py | 13 +++++ kappa/stack.py | 13 ++++- requirements.txt | 2 +- setup.py | 2 +- tests/unit/test_stack.py | 4 +- 8 files changed, 144 insertions(+), 43 deletions(-) diff --git a/bin/kappa b/bin/kappa index fa7d352..f91f684 100755 --- a/bin/kappa +++ b/bin/kappa @@ -18,47 +18,96 @@ import click from kappa.context import Context -@click.command() -@click.option( - '--config', - help="Path to the Kappa config YAML file", +@click.group() +@click.argument( + 'config', type=click.File('rb'), envvar='KAPPA_CONFIG', - default=None ) @click.option( '--debug/--no-debug', default=False, help='Turn on debugging output' ) -@click.argument( - 'command', - required=True, - type=click.Choice(['deploy', 'test', 'tail', 'add-event-sources', 'delete']) -) -def main(config=None, debug=False, command=None): - ctx = Context(config, debug) - if command == 'deploy': - click.echo('Deploying ...') - ctx.deploy() - click.echo('...done') - elif command == 'test': - click.echo('Sending test data ...') - ctx.test() - click.echo('...done') - elif command == 'tail': - events = ctx.tail() - for event in events: - print(event['message']) - elif command == 'delete': - click.echo('Deleting ...') - ctx.delete() - click.echo('...done') - elif command == 'add-event-sources': - click.echo('Adding event sources ...') - ctx.add_event_sources() - click.echo('...done') +@click.pass_context +def cli(ctx, config=None, debug=False): + config = config + ctx.obj['debug'] = debug + ctx.obj['config'] = config + +@cli.command() +@click.pass_context +def deploy(ctx): + context = Context(ctx.obj['config'], ctx.obj['debug']) + click.echo('deploying...') + context.deploy() + click.echo('...done') + +@cli.command() +@click.pass_context +def test(ctx): + context = Context(ctx.obj['config'], ctx.obj['debug']) + click.echo('testing...') + context.test() + click.echo('...done') + +@cli.command() +@click.pass_context +def tail(ctx): + context = Context(ctx.obj['config'], ctx.obj['debug']) + click.echo('tailing logs...') + context.tail() + click.echo('...done') + +@cli.command() +@click.pass_context +def status(ctx): + context = Context(ctx.obj['config'], ctx.obj['debug']) + status = context.status() + click.echo(click.style('Stack', bold=True)) + if status['stack']: + for stack in status['stack']['Stacks']: + line = ' {}: {}'.format(stack['StackId'], stack['StackStatus']) + click.echo(click.style(line, fg='green')) + else: + click.echo(click.style(' None', fg='green')) + click.echo(click.style('Function', bold=True)) + if status['function']: + line = ' {}'.format( + status['function']['Configuration']['FunctionName']) + click.echo(click.style(line, fg='green')) + else: + click.echo(click.style(' None', fg='green')) + click.echo(click.style('Event Sources', bold=True)) + if status['event_sources']: + for event_source in status['event_sources']: + if 'EventSource' in event_source: + line = ' {}: {}'.format( + event_source['EventSource'], event_source['IsActive']) + click.echo(click.style(line, fg='green')) + else: + line = ' {}'.format( + event_source['CloudFunctionConfiguration']['Id']) + click.echo(click.style(line, fg='green')) + else: + click.echo(click.style(' None', fg='green')) + +@cli.command() +@click.pass_context +def delete(ctx): + context = Context(ctx.obj['config'], ctx.obj['debug']) + click.echo('deleting...') + context.delete() + click.echo('...done') + +@cli.command() +@click.pass_context +def add_event_sources(ctx): + context = Context(ctx.obj['config'], ctx.obj['debug']) + click.echo('adding event sources...') + context.add_event_sources() + click.echo('...done') if __name__ == '__main__': - main() + cli(obj={}) diff --git a/kappa/context.py b/kappa/context.py index 5f4e072..ba4b064 100644 --- a/kappa/context.py +++ b/kappa/context.py @@ -108,10 +108,7 @@ def add_event_sources(self): event_source.add(self.function) def deploy(self): - if self._stack.exists(): - self._stack.update() - else: - self._stack.create() + self._stack.update() self.function.upload() def test(self): @@ -123,3 +120,14 @@ def tail(self): def delete(self): self._stack.delete() self.function.delete() + for event_source in self.event_sources: + event_source.remove(self.function) + + def status(self): + status = {} + status['stack'] = self._stack.status() + status['function'] = self.function.status() + status['event_sources'] = [] + for event_source in self.event_sources: + status['event_sources'].append(event_source.status(self.function)) + return status diff --git a/kappa/event_source.py b/kappa/event_source.py index f518d6f..85a0676 100644 --- a/kappa/event_source.py +++ b/kappa/event_source.py @@ -13,6 +13,8 @@ import logging +from botocore.exceptions import ClientError + import kappa.aws LOG = logging.getLogger(__name__) @@ -70,6 +72,17 @@ def remove(self, function): LOG.debug(response) return response + def status(self, function): + LOG.debug('getting status for event source %s', self.arn) + try: + response = self._lambda.get_event_source( + UUID=self._get_uuid(function)) + LOG.debug(response) + except ClientError: + LOG.debug('event source %s does not exist', self.arn) + response = None + return response + class S3EventSource(EventSource): @@ -112,3 +125,12 @@ def remove(self, function): Bucket=self._get_bucket_name(), NotificationConfiguration=response) LOG.debug(response) + + def status(self, function): + LOG.debug('status for s3 notification') + response = self._s3.get_bucket_notification( + Bucket=self._get_bucket_name()) + LOG.debug(response) + if 'CloudFunctionConfiguration' not in response: + response = None + return response diff --git a/kappa/function.py b/kappa/function.py index ffd252d..2c6ac22 100644 --- a/kappa/function.py +++ b/kappa/function.py @@ -15,6 +15,8 @@ import os import zipfile +from botocore.exceptions import ClientError + import kappa.aws import kappa.log @@ -148,6 +150,17 @@ def delete(self): LOG.debug(response) return response + def status(self): + LOG.debug('getting status for function %s', self.name) + try: + response = self._lambda_svc.get_function( + FunctionName=self.name) + LOG.debug(response) + except ClientError: + LOG.debug('function %s not found', self.name) + response = None + return response + def invoke_asynch(self, data_file): LOG.debug('_invoke_async %s', data_file) with open(data_file) as fp: diff --git a/kappa/stack.py b/kappa/stack.py index cfa82e7..70cf0d3 100644 --- a/kappa/stack.py +++ b/kappa/stack.py @@ -98,7 +98,7 @@ def wait(self): msg = 'Could not create stack %s: %s' % (self.name, status) raise ValueError(msg) - def create(self): + def _create(self): LOG.debug('create_stack: stack_name=%s', self.name) template_body = open(self.template_path).read() try: @@ -110,7 +110,7 @@ def create(self): LOG.exception('Unable to create stack') self.wait() - def update(self): + def _update(self): LOG.debug('create_stack: stack_name=%s', self.name) template_body = open(self.template_path).read() try: @@ -125,6 +125,15 @@ def update(self): LOG.exception('Unable to update stack') self.wait() + def update(self): + if self.exists(): + self._update() + else: + self._create() + + def status(self): + return self.exists() + def delete(self): LOG.debug('delete_stack: stack_name=%s', self.name) try: diff --git a/requirements.txt b/requirements.txt index 52f549b..1a0a7a9 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -botocore==0.82.0 +botocore==0.94.0 click==3.3 PyYAML>=3.11 mock>=1.0.1 diff --git a/setup.py b/setup.py index 9d33274..2d4ebf9 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ import os requires = [ - 'botocore==0.82.0', + 'botocore==0.94.0', 'click==3.3', 'PyYAML>=3.11' ] diff --git a/tests/unit/test_stack.py b/tests/unit/test_stack.py index 8a02afe..9b038c4 100644 --- a/tests/unit/test_stack.py +++ b/tests/unit/test_stack.py @@ -56,10 +56,10 @@ def test_exists(self): stack = Stack(mock_context, Config) self.assertTrue(stack.exists()) - def test_create(self): + def test_update(self): mock_context = mock.Mock() stack = Stack(mock_context, Config) - stack.create() + stack.update() def test_delete(self): mock_context = mock.Mock() From 171582b4a76b75c0ab7663daaf3b44099f577ff3 Mon Sep 17 00:00:00 2001 From: Mitch Garnaat Date: Thu, 5 Mar 2015 09:17:15 -0800 Subject: [PATCH 2/2] Minor fix for landscape. --- kappa/event_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kappa/event_source.py b/kappa/event_source.py index 85a0676..89302cf 100644 --- a/kappa/event_source.py +++ b/kappa/event_source.py @@ -127,7 +127,7 @@ def remove(self, function): LOG.debug(response) def status(self, function): - LOG.debug('status for s3 notification') + LOG.debug('status for s3 notification for %s', function.name) response = self._s3.get_bucket_notification( Bucket=self._get_bucket_name()) LOG.debug(response)