Mitch Garnaat

Another WIP commit on the road to an update for the new Lambda API.

...@@ -41,12 +41,20 @@ def cli(ctx, config=None, debug=False): ...@@ -41,12 +41,20 @@ def cli(ctx, config=None, debug=False):
41 @click.pass_context 41 @click.pass_context
42 def create(ctx): 42 def create(ctx):
43 context = Context(ctx.obj['config'], ctx.obj['debug']) 43 context = Context(ctx.obj['config'], ctx.obj['debug'])
44 - click.echo('deploying...') 44 + click.echo('creating...')
45 context.create() 45 context.create()
46 click.echo('...done') 46 click.echo('...done')
47 47
48 @cli.command() 48 @cli.command()
49 @click.pass_context 49 @click.pass_context
50 +def update_code(ctx):
51 + context = Context(ctx.obj['config'], ctx.obj['debug'])
52 + click.echo('updating code...')
53 + context.update_code()
54 + click.echo('...done')
55 +
56 +@cli.command()
57 +@click.pass_context
50 def invoke(ctx): 58 def invoke(ctx):
51 context = Context(ctx.obj['config'], ctx.obj['debug']) 59 context = Context(ctx.obj['config'], ctx.obj['debug'])
52 click.echo('invoking...') 60 click.echo('invoking...')
...@@ -93,11 +101,12 @@ def status(ctx): ...@@ -93,11 +101,12 @@ def status(ctx):
93 click.echo(click.style('Event Sources', bold=True)) 101 click.echo(click.style('Event Sources', bold=True))
94 if status['event_sources']: 102 if status['event_sources']:
95 for event_source in status['event_sources']: 103 for event_source in status['event_sources']:
96 - line = ' {}: {}'.format( 104 + if event_source:
97 - event_source['EventSourceArn'], event_source['State']) 105 + line = ' {}: {}'.format(
98 - click.echo(click.style(line, fg='green')) 106 + event_source['EventSourceArn'], event_source['State'])
99 - else: 107 + click.echo(click.style(line, fg='green'))
100 - click.echo(click.style(' None', fg='green')) 108 + else:
109 + click.echo(click.style(' None', fg='green'))
101 110
102 @cli.command() 111 @cli.command()
103 @click.pass_context 112 @click.pass_context
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
13 13
14 import logging 14 import logging
15 import yaml 15 import yaml
16 +import time
16 17
17 import kappa.function 18 import kappa.function
18 import kappa.event_source 19 import kappa.event_source
...@@ -107,8 +108,12 @@ class Context(object): ...@@ -107,8 +108,12 @@ class Context(object):
107 self, event_source_cfg)) 108 self, event_source_cfg))
108 elif svc == 'sns': 109 elif svc == 'sns':
109 self.event_sources.append( 110 self.event_sources.append(
110 - kappa.event_source.SNSEventSource(self, 111 + kappa.event_source.SNSEventSource(
111 - event_source_cfg)) 112 + self, event_source_cfg))
113 + elif svc == 'dynamodb':
114 + self.event_sources.append(
115 + kappa.event_source.DynamoDBStreamEventSource(
116 + self, event_source_cfg))
112 else: 117 else:
113 msg = 'Unknown event source: %s' % event_source_cfg['arn'] 118 msg = 'Unknown event source: %s' % event_source_cfg['arn']
114 raise ValueError(msg) 119 raise ValueError(msg)
...@@ -122,8 +127,16 @@ class Context(object): ...@@ -122,8 +127,16 @@ class Context(object):
122 self.policy.create() 127 self.policy.create()
123 if self.role: 128 if self.role:
124 self.role.create() 129 self.role.create()
130 + # There is a consistency problem here.
131 + # If you don't wait for a bit, the function.create call
132 + # will fail because the policy has not been attached to the role.
133 + LOG.debug('Waiting for policy/role propogation')
134 + time.sleep(5)
125 self.function.create() 135 self.function.create()
126 136
137 + def update_code(self):
138 + self.function.update()
139 +
127 def invoke(self): 140 def invoke(self):
128 return self.function.invoke() 141 return self.function.invoke()
129 142
...@@ -131,13 +144,15 @@ class Context(object): ...@@ -131,13 +144,15 @@ class Context(object):
131 return self.function.tail() 144 return self.function.tail()
132 145
133 def delete(self): 146 def delete(self):
134 - if self.policy:
135 - self.policy.delete()
136 - if self.role:
137 - self.role.delete()
138 - self.function.delete()
139 for event_source in self.event_sources: 147 for event_source in self.event_sources:
140 event_source.remove(self.function) 148 event_source.remove(self.function)
149 + self.function.delete()
150 + time.sleep(5)
151 + if self.role:
152 + self.role.delete()
153 + time.sleep(5)
154 + if self.policy:
155 + self.policy.delete()
141 156
142 def status(self): 157 def status(self):
143 status = {} 158 status = {}
......
...@@ -77,17 +77,27 @@ class KinesisEventSource(EventSource): ...@@ -77,17 +77,27 @@ class KinesisEventSource(EventSource):
77 return response 77 return response
78 78
79 def status(self, function): 79 def status(self, function):
80 + response = None
80 LOG.debug('getting status for event source %s', self.arn) 81 LOG.debug('getting status for event source %s', self.arn)
81 - try: 82 + uuid = self._get_uuid(function)
82 - response = self._lambda.get_event_source_mapping( 83 + if uuid:
83 - UUID=self._get_uuid(function)) 84 + try:
84 - LOG.debug(response) 85 + response = self._lambda.get_event_source_mapping(
85 - except ClientError: 86 + UUID=self._get_uuid(function))
86 - LOG.debug('event source %s does not exist', self.arn) 87 + LOG.debug(response)
87 - response = None 88 + except ClientError:
89 + LOG.debug('event source %s does not exist', self.arn)
90 + response = None
91 + else:
92 + LOG.debug('No UUID for event source %s', self.arn)
88 return response 93 return response
89 94
90 95
96 +class DynamoDBStreamEventSource(KinesisEventSource):
97 +
98 + pass
99 +
100 +
91 class S3EventSource(EventSource): 101 class S3EventSource(EventSource):
92 102
93 def __init__(self, context, config): 103 def __init__(self, context, config):
......
...@@ -148,6 +148,7 @@ class Function(object): ...@@ -148,6 +148,7 @@ class Function(object):
148 self.zip_lambda_function(self.zipfile_name, self.path) 148 self.zip_lambda_function(self.zipfile_name, self.path)
149 with open(self.zipfile_name, 'rb') as fp: 149 with open(self.zipfile_name, 'rb') as fp:
150 exec_role = self._context.exec_role_arn 150 exec_role = self._context.exec_role_arn
151 + LOG.debug('exec_role=%s', exec_role)
151 try: 152 try:
152 zipdata = fp.read() 153 zipdata = fp.read()
153 response = self._lambda_svc.create_function( 154 response = self._lambda_svc.create_function(
...@@ -164,10 +165,27 @@ class Function(object): ...@@ -164,10 +165,27 @@ class Function(object):
164 LOG.exception('Unable to upload zip file') 165 LOG.exception('Unable to upload zip file')
165 self.add_permissions() 166 self.add_permissions()
166 167
168 + def update(self):
169 + LOG.debug('updating %s', self.zipfile_name)
170 + self.zip_lambda_function(self.zipfile_name, self.path)
171 + with open(self.zipfile_name, 'rb') as fp:
172 + try:
173 + zipdata = fp.read()
174 + response = self._lambda_svc.update_function_code(
175 + FunctionName=self.name,
176 + ZipFile=zipdata)
177 + LOG.debug(response)
178 + except Exception:
179 + LOG.exception('Unable to update zip file')
180 +
167 def delete(self): 181 def delete(self):
168 LOG.debug('deleting function %s', self.name) 182 LOG.debug('deleting function %s', self.name)
169 - response = self._lambda_svc.delete_function(FunctionName=self.name) 183 + response = None
170 - LOG.debug(response) 184 + try:
185 + response = self._lambda_svc.delete_function(FunctionName=self.name)
186 + LOG.debug(response)
187 + except ClientError:
188 + LOG.debug('function %s: not found', self.name)
171 return response 189 return response
172 190
173 def status(self): 191 def status(self):
......
...@@ -77,9 +77,11 @@ class Policy(object): ...@@ -77,9 +77,11 @@ class Policy(object):
77 LOG.exception('Error creating Policy') 77 LOG.exception('Error creating Policy')
78 78
79 def delete(self): 79 def delete(self):
80 - LOG.debug('deleting policy %s', self.name) 80 + response = None
81 - response = self._iam_svc.delete_policy(PolicyArn=self.arn) 81 + if self.arn:
82 - LOG.debug(response) 82 + LOG.debug('deleting policy %s', self.name)
83 + response = self._iam_svc.delete_policy(PolicyArn=self.arn)
84 + LOG.debug(response)
83 return response 85 return response
84 86
85 def status(self): 87 def status(self):
......
...@@ -79,13 +79,28 @@ class Role(object): ...@@ -79,13 +79,28 @@ class Role(object):
79 Path=self.Path, RoleName=self.name, 79 Path=self.Path, RoleName=self.name,
80 AssumeRolePolicyDocument=AssumeRolePolicyDocument) 80 AssumeRolePolicyDocument=AssumeRolePolicyDocument)
81 LOG.debug(response) 81 LOG.debug(response)
82 - except Exception: 82 + if self._context.policy:
83 + response = self._iam_svc.attach_role_policy(
84 + RoleName=self.name,
85 + PolicyArn=self._context.policy.arn)
86 + LOG.debug(response)
87 + except ClientError:
83 LOG.exception('Error creating Role') 88 LOG.exception('Error creating Role')
84 89
85 def delete(self): 90 def delete(self):
91 + response = None
86 LOG.debug('deleting role %s', self.name) 92 LOG.debug('deleting role %s', self.name)
87 - response = self._iam_svc.delete_role(RoleName=self.name) 93 + try:
88 - LOG.debug(response) 94 + LOG.debug('First detach the policy from the role')
95 + policy_arn = self._context.policy.arn
96 + if policy_arn:
97 + response = self._iam_svc.detach_role_policy(
98 + RoleName=self.name, PolicyArn=policy_arn)
99 + LOG.debug(response)
100 + response = self._iam_svc.delete_role(RoleName=self.name)
101 + LOG.debug(response)
102 + except ClientError:
103 + LOG.exception('role %s not found', self.name)
89 return response 104 return response
90 105
91 def status(self): 106 def status(self):
......