Showing
9 changed files
with
168 additions
and
47 deletions
... | @@ -31,7 +31,8 @@ deploy | ... | @@ -31,7 +31,8 @@ deploy |
31 | 31 | ||
32 | The ``deploy`` command does whatever is required to deploy the | 32 | The ``deploy`` command does whatever is required to deploy the |
33 | current version of your Lambda function such as creating/updating policies and | 33 | current version of your Lambda function such as creating/updating policies and |
34 | -roles and creating or updating the function itself. | 34 | +roles, creating or updating the function itself, and adding any event sources |
35 | +specified in your config file. | ||
35 | 36 | ||
36 | When the command is run the first time, it creates all of the relevant | 37 | When the command is run the first time, it creates all of the relevant |
37 | resources required. On subsequent invocations, it will attempt to determine | 38 | resources required. On subsequent invocations, it will attempt to determine |
... | @@ -49,6 +50,10 @@ invoke | ... | @@ -49,6 +50,10 @@ invoke |
49 | The ``invoke`` command makes a synchronous call to your Lambda function, | 50 | The ``invoke`` command makes a synchronous call to your Lambda function, |
50 | passing test data and display the resulting log data and any response returned | 51 | passing test data and display the resulting log data and any response returned |
51 | from your Lambda function. | 52 | from your Lambda function. |
53 | + | ||
54 | +The ``invoke`` command takes one positional argument, the ``data_file``. This | ||
55 | +should be the path to a JSON data file that will be sent to the function as | ||
56 | +data. | ||
52 | 57 | ||
53 | tag | 58 | tag |
54 | --- | 59 | --- |
... | @@ -84,9 +89,10 @@ event_sources | ... | @@ -84,9 +89,10 @@ event_sources |
84 | ------------- | 89 | ------------- |
85 | 90 | ||
86 | The ``event_sources`` command provides access the commands available for | 91 | The ``event_sources`` command provides access the commands available for |
87 | -dealing with event sources. This command has an additional option: | 92 | +dealing with event sources. This command takes an additional positional |
93 | +argument, ``command``. | ||
88 | 94 | ||
89 | -* --command - the command to run (add|update|enable|disable) | 95 | +* command - the command to run (list|enable|disable) |
90 | 96 | ||
91 | status | 97 | status |
92 | ------ | 98 | ------ | ... | ... |
docs/config_file_example.rst
0 → 100644
1 | +The Config File | ||
2 | +=============== | ||
3 | + | ||
4 | +The config file is at the heart of kappa. It is what describes your functions | ||
5 | +and drives your deployments. This section provides a reference for all of the | ||
6 | +elements of the kappa config file. | ||
7 | + | ||
8 | + | ||
9 | +Example | ||
10 | +------- | ||
11 | + | ||
12 | +Here is an example config file showing all possible sections. | ||
13 | + | ||
14 | +.. sourcecode:: yaml | ||
15 | + :linenos: | ||
16 | + | ||
17 | + --- | ||
18 | + name: kappa-python-sample | ||
19 | + environments: | ||
20 | + env1: | ||
21 | + profile: profile1 | ||
22 | + region: us-west-2 | ||
23 | + policy: | ||
24 | + resources: | ||
25 | + - arn: arn:aws:dynamodb:us-west-2:123456789012:table/foo | ||
26 | + actions: | ||
27 | + - "*" | ||
28 | + - arn: arn:aws:logs:*:*:* | ||
29 | + actions: | ||
30 | + - "*" | ||
31 | + event_sources: | ||
32 | + - | ||
33 | + arn: arn:aws:kinesis:us-west-2:123456789012:stream/foo | ||
34 | + starting_position: LATEST | ||
35 | + batch_size: 100 | ||
36 | + env2: | ||
37 | + profile: profile2 | ||
38 | + region: us-west-2 | ||
39 | + policy_resources: | ||
40 | + - arn: arn:aws:dynamodb:us-west-2:234567890123:table/foo | ||
41 | + actions: | ||
42 | + - "*" | ||
43 | + - arn: arn:aws:logs:*:*:* | ||
44 | + actions: | ||
45 | + - "*" | ||
46 | + event_sources: | ||
47 | + - | ||
48 | + arn: arn:aws:kinesis:us-west-2:234567890123:stream/foo | ||
49 | + starting_position: LATEST | ||
50 | + batch_size: 100 | ||
51 | + lambda: | ||
52 | + description: A simple Python sample | ||
53 | + handler: simple.handler | ||
54 | + runtime: python2.7 | ||
55 | + memory_size: 256 | ||
56 | + timeout: 3 | ||
57 | + vpc_config: | ||
58 | + security_group_ids: | ||
59 | + - sg-12345678 | ||
60 | + - sg-23456789 | ||
61 | + subnet_ids: | ||
62 | + - subnet-12345678 | ||
63 | + - subnet-23456789 | ||
64 | + | ||
65 | + | ||
66 | +Explanations: | ||
67 | + | ||
68 | +=========== ============================================================= | ||
69 | +Line Number Description | ||
70 | +=========== ============================================================= | ||
71 | +2 This name will be used to name the function itself as well as | ||
72 | + any policies and roles created for use by the function. | ||
73 | +3 A map of environments. Each environment represents one | ||
74 | + possible deployment target. For example, you might have a | ||
75 | + dev and a prod. The names can be whatever you want but the | ||
76 | + environment names are specified using the --env option when | ||
77 | + you deploy. | ||
78 | +5 The profile name associated with this environment. This | ||
79 | + refers to a profile in your AWS credential file. | ||
80 | +6 The AWS region associated with this environment. | ||
81 | +7 This section defines the elements of the IAM policy that will | ||
82 | + be created for this function in this environment. | ||
83 | +9 Each resource your function needs access to needs to be | ||
84 | + listed here. Provide the ARN of the resource as well as | ||
85 | + a list of actions. This could be wildcarded to allow all | ||
86 | + actions but preferably should list the specific actions you | ||
87 | + want to allow. | ||
88 | +15 If your Lambda function has any event sources, this would be | ||
89 | + where you list them. Here, the example shows a Kinesis | ||
90 | + stream but this could also be a DynamoDB stream, an SNS | ||
91 | + topic, or an S3 bucket. | ||
92 | +18 For Kinesis streams and DynamoDB streams, you can specify | ||
93 | + the starting position (one of LATEST or TRIM_HORIZON) and | ||
94 | + the batch size. | ||
95 | +35 This section contains settings specify to your Lambda | ||
96 | + function. See the Lambda docs for details on these. | ||
97 | +=========== ============================================================= |
... | @@ -3,8 +3,8 @@ | ... | @@ -3,8 +3,8 @@ |
3 | You can adapt this file completely to your liking, but it should at least | 3 | You can adapt this file completely to your liking, but it should at least |
4 | contain the root `toctree` directive. | 4 | contain the root `toctree` directive. |
5 | 5 | ||
6 | -Welcome to kappa's documentation! | 6 | +Welcome to kappa's documentation |
7 | -================================= | 7 | +================================ |
8 | 8 | ||
9 | Contents: | 9 | Contents: |
10 | 10 | ||
... | @@ -12,7 +12,8 @@ Contents: | ... | @@ -12,7 +12,8 @@ Contents: |
12 | :maxdepth: 2 | 12 | :maxdepth: 2 |
13 | 13 | ||
14 | why | 14 | why |
15 | - config_file | 15 | + how |
16 | + config_file_example | ||
16 | commands | 17 | commands |
17 | 18 | ||
18 | 19 | ... | ... |
... | @@ -187,6 +187,15 @@ class Context(object): | ... | @@ -187,6 +187,15 @@ class Context(object): |
187 | for event_source in self.event_sources: | 187 | for event_source in self.event_sources: |
188 | event_source.update(self.function) | 188 | event_source.update(self.function) |
189 | 189 | ||
190 | + def list_event_sources(self): | ||
191 | + event_sources = [] | ||
192 | + for event_source in self.event_sources: | ||
193 | + event_sources.append({'arn': event_source.arn, | ||
194 | + 'starting_position': event_source.starting_position, | ||
195 | + 'batch_size': event_source.batch_size, | ||
196 | + 'enabled': event_source.enabled}) | ||
197 | + return event_sources | ||
198 | + | ||
190 | def enable_event_sources(self): | 199 | def enable_event_sources(self): |
191 | for event_source in self.event_sources: | 200 | for event_source in self.event_sources: |
192 | event_source.enable(self.function) | 201 | event_source.enable(self.function) |
... | @@ -206,6 +215,7 @@ class Context(object): | ... | @@ -206,6 +215,7 @@ class Context(object): |
206 | LOG.debug('Waiting for policy/role propogation') | 215 | LOG.debug('Waiting for policy/role propogation') |
207 | time.sleep(5) | 216 | time.sleep(5) |
208 | self.function.create() | 217 | self.function.create() |
218 | + self.add_event_sources() | ||
209 | 219 | ||
210 | def deploy(self): | 220 | def deploy(self): |
211 | if self.policy: | 221 | if self.policy: | ... | ... |
... | @@ -33,7 +33,7 @@ class EventSource(object): | ... | @@ -33,7 +33,7 @@ class EventSource(object): |
33 | 33 | ||
34 | @property | 34 | @property |
35 | def starting_position(self): | 35 | def starting_position(self): |
36 | - return self._config.get('starting_position', 'TRIM_HORIZON') | 36 | + return self._config.get('starting_position', 'LATEST') |
37 | 37 | ||
38 | @property | 38 | @property |
39 | def batch_size(self): | 39 | def batch_size(self): |
... | @@ -41,7 +41,7 @@ class EventSource(object): | ... | @@ -41,7 +41,7 @@ class EventSource(object): |
41 | 41 | ||
42 | @property | 42 | @property |
43 | def enabled(self): | 43 | def enabled(self): |
44 | - return self._config.get('enabled', True) | 44 | + return self._config.get('enabled', False) |
45 | 45 | ||
46 | 46 | ||
47 | class KinesisEventSource(EventSource): | 47 | class KinesisEventSource(EventSource): |
... | @@ -161,7 +161,7 @@ class S3EventSource(EventSource): | ... | @@ -161,7 +161,7 @@ class S3EventSource(EventSource): |
161 | 161 | ||
162 | def add(self, function): | 162 | def add(self, function): |
163 | notification_spec = { | 163 | notification_spec = { |
164 | - 'LambdaFunctionConfigurations':[ | 164 | + 'LambdaFunctionConfigurations': [ |
165 | { | 165 | { |
166 | 'Id': self._make_notification_id(function.name), | 166 | 'Id': self._make_notification_id(function.name), |
167 | 'Events': [e for e in self._config['events']], | 167 | 'Events': [e for e in self._config['events']], | ... | ... |
... | @@ -57,7 +57,7 @@ class Function(object): | ... | @@ -57,7 +57,7 @@ class Function(object): |
57 | @property | 57 | @property |
58 | def dependencies(self): | 58 | def dependencies(self): |
59 | return self._config.get('dependencies', list()) | 59 | return self._config.get('dependencies', list()) |
60 | - | 60 | + |
61 | @property | 61 | @property |
62 | def description(self): | 62 | def description(self): |
63 | return self._config['description'] | 63 | return self._config['description'] |
... | @@ -71,6 +71,18 @@ class Function(object): | ... | @@ -71,6 +71,18 @@ class Function(object): |
71 | return self._config['memory_size'] | 71 | return self._config['memory_size'] |
72 | 72 | ||
73 | @property | 73 | @property |
74 | + def vpc_config(self): | ||
75 | + vpc_config = {} | ||
76 | + if 'vpc_config' in self._config: | ||
77 | + if 'security_group_ids' in self._config['vpc_config']: | ||
78 | + sgids = self._config['vpc_config']['security_group_ids'] | ||
79 | + vpc_config['SecurityGroupIds'] = ','.join(sgids) | ||
80 | + if 'subnet_ids' in self._config['vpc_config']: | ||
81 | + snids = self._config['vpc_config']['subnet_ids'] | ||
82 | + vpc_config['SubnetIds'] = ','.join(snids) | ||
83 | + return vpc_config | ||
84 | + | ||
85 | + @property | ||
74 | def zipfile_name(self): | 86 | def zipfile_name(self): |
75 | return '{}.zip'.format(self._context.name) | 87 | return '{}.zip'.format(self._context.name) |
76 | 88 | ||
... | @@ -372,6 +384,7 @@ class Function(object): | ... | @@ -372,6 +384,7 @@ class Function(object): |
372 | Description=self.description, | 384 | Description=self.description, |
373 | Timeout=self.timeout, | 385 | Timeout=self.timeout, |
374 | MemorySize=self.memory_size, | 386 | MemorySize=self.memory_size, |
387 | + VpcConfig=self.vpc_config, | ||
375 | Publish=True) | 388 | Publish=True) |
376 | LOG.debug(response) | 389 | LOG.debug(response) |
377 | description = 'For stage {}'.format( | 390 | description = 'For stage {}'.format( |
... | @@ -418,6 +431,7 @@ class Function(object): | ... | @@ -418,6 +431,7 @@ class Function(object): |
418 | response = self._lambda_client.call( | 431 | response = self._lambda_client.call( |
419 | 'update_function_configuration', | 432 | 'update_function_configuration', |
420 | FunctionName=self.name, | 433 | FunctionName=self.name, |
434 | + VpcConfig=self.vpc_config, | ||
421 | Role=exec_role, | 435 | Role=exec_role, |
422 | Handler=self.handler, | 436 | Handler=self.handler, |
423 | Description=self.description, | 437 | Description=self.description, | ... | ... |
... | @@ -23,34 +23,32 @@ LOG = logging.getLogger(__name__) | ... | @@ -23,34 +23,32 @@ LOG = logging.getLogger(__name__) |
23 | 23 | ||
24 | class Policy(object): | 24 | class Policy(object): |
25 | 25 | ||
26 | + _path_prefix = '/kappa/' | ||
27 | + | ||
26 | def __init__(self, context, config): | 28 | def __init__(self, context, config): |
27 | - self._context = context | 29 | + self.context = context |
28 | - self._config = config | 30 | + self.config = config |
29 | self._iam_client = kappa.awsclient.create_client( | 31 | self._iam_client = kappa.awsclient.create_client( |
30 | - 'iam', self._context.session) | 32 | + 'iam', self.context.session) |
31 | - self._arn = self._config['policy'].get('arn', None) | 33 | + self._arn = self.config['policy'].get('arn', None) |
32 | - | ||
33 | - @property | ||
34 | - def environment(self): | ||
35 | - return self._context.environment | ||
36 | 34 | ||
37 | @property | 35 | @property |
38 | def name(self): | 36 | def name(self): |
39 | - return '{}_{}'.format(self._context.name, self.environment) | 37 | + return '{}_{}'.format(self.context.name, self.context.environment) |
40 | 38 | ||
41 | @property | 39 | @property |
42 | def description(self): | 40 | def description(self): |
43 | return 'A kappa policy to control access to {} resources'.format( | 41 | return 'A kappa policy to control access to {} resources'.format( |
44 | - self.environment) | 42 | + self.context.environment) |
45 | 43 | ||
46 | def document(self): | 44 | def document(self): |
47 | - if ('resources' not in self._config['policy'] and | 45 | + if ('resources' not in self.config['policy'] and |
48 | - 'statements' not in self._config['policy']): | 46 | + 'statements' not in self.config['policy']): |
49 | return None | 47 | return None |
50 | - document = {"Version": "2012-10-17"} | 48 | + document = {'Version': '2012-10-17'} |
51 | statements = [] | 49 | statements = [] |
52 | document['Statement'] = statements | 50 | document['Statement'] = statements |
53 | - for resource in self._config['policy']['resources']: | 51 | + for resource in self.config['policy']['resources']: |
54 | arn = resource['arn'] | 52 | arn = resource['arn'] |
55 | _, _, service, _ = arn.split(':', 3) | 53 | _, _, service, _ = arn.split(':', 3) |
56 | statement = {"Effect": "Allow", | 54 | statement = {"Effect": "Allow", |
... | @@ -60,15 +58,11 @@ class Policy(object): | ... | @@ -60,15 +58,11 @@ class Policy(object): |
60 | actions.append("{}:{}".format(service, action)) | 58 | actions.append("{}:{}".format(service, action)) |
61 | statement['Action'] = actions | 59 | statement['Action'] = actions |
62 | statements.append(statement) | 60 | statements.append(statement) |
63 | - for statement in self._config['policy'].get('statements', []): | 61 | + for statement in self.config['policy'].get('statements', []): |
64 | statements.append(statement) | 62 | statements.append(statement) |
65 | return json.dumps(document, indent=2, sort_keys=True) | 63 | return json.dumps(document, indent=2, sort_keys=True) |
66 | 64 | ||
67 | @property | 65 | @property |
68 | - def path(self): | ||
69 | - return self._config.get('path', '/kappa/') | ||
70 | - | ||
71 | - @property | ||
72 | def arn(self): | 66 | def arn(self): |
73 | if self._arn is None: | 67 | if self._arn is None: |
74 | policy = self.exists() | 68 | policy = self.exists() |
... | @@ -79,7 +73,7 @@ class Policy(object): | ... | @@ -79,7 +73,7 @@ class Policy(object): |
79 | def _find_all_policies(self): | 73 | def _find_all_policies(self): |
80 | try: | 74 | try: |
81 | response = self._iam_client.call( | 75 | response = self._iam_client.call( |
82 | - 'list_policies', PathPrefix=self.path) | 76 | + 'list_policies', PathPrefix=self._path_prefix) |
83 | except Exception: | 77 | except Exception: |
84 | LOG.exception('Error listing policies') | 78 | LOG.exception('Error listing policies') |
85 | response = {} | 79 | response = {} |
... | @@ -130,11 +124,11 @@ class Policy(object): | ... | @@ -130,11 +124,11 @@ class Policy(object): |
130 | m = hashlib.md5() | 124 | m = hashlib.md5() |
131 | m.update(document.encode('utf-8')) | 125 | m.update(document.encode('utf-8')) |
132 | policy_md5 = m.hexdigest() | 126 | policy_md5 = m.hexdigest() |
133 | - cached_md5 = self._context.get_cache_value('policy_md5') | 127 | + cached_md5 = self.context.get_cache_value('policy_md5') |
134 | LOG.debug('policy_md5: %s', policy_md5) | 128 | LOG.debug('policy_md5: %s', policy_md5) |
135 | LOG.debug('cached md5: %s', cached_md5) | 129 | LOG.debug('cached md5: %s', cached_md5) |
136 | if policy_md5 != cached_md5: | 130 | if policy_md5 != cached_md5: |
137 | - self._context.set_cache_value('policy_md5', policy_md5) | 131 | + self.context.set_cache_value('policy_md5', policy_md5) |
138 | return True | 132 | return True |
139 | return False | 133 | return False |
140 | 134 | ||
... | @@ -156,7 +150,7 @@ class Policy(object): | ... | @@ -156,7 +150,7 @@ class Policy(object): |
156 | try: | 150 | try: |
157 | response = self._iam_client.call( | 151 | response = self._iam_client.call( |
158 | 'create_policy', | 152 | 'create_policy', |
159 | - Path=self.path, PolicyName=self.name, | 153 | + Path=self._path_prefix, PolicyName=self.name, |
160 | PolicyDocument=document, | 154 | PolicyDocument=document, |
161 | Description=self.description) | 155 | Description=self.description) |
162 | LOG.debug(response) | 156 | LOG.debug(response) | ... | ... |
... | @@ -139,20 +139,19 @@ def delete(ctx): | ... | @@ -139,20 +139,19 @@ def delete(ctx): |
139 | 139 | ||
140 | 140 | ||
141 | @cli.command() | 141 | @cli.command() |
142 | -@click.option( | 142 | +@click.argument('command', |
143 | - '--command', | 143 | + type=click.Choice(['list', 'enable', 'disable'])) |
144 | - type=click.Choice(['add', 'update', 'enable', 'disable']), | ||
145 | - help='Operation to perform on event sources') | ||
146 | @pass_ctx | 144 | @pass_ctx |
147 | def event_sources(ctx, command): | 145 | def event_sources(ctx, command): |
148 | - """Add any event sources specified in the config file""" | 146 | + """List, enable, and disable event sources specified in the config file""" |
149 | - if command == 'add': | 147 | + if command == 'list': |
150 | - click.echo('adding event sources') | 148 | + click.echo('listing event sources') |
151 | - ctx.add_event_sources() | 149 | + event_sources = ctx.list_event_sources() |
152 | - click.echo('done') | 150 | + for es in event_sources: |
153 | - elif command == 'update': | 151 | + click.echo('arn: {}'.format(es['arn'])) |
154 | - click.echo('updating event sources') | 152 | + click.echo('starting position: {}'.format(es['starting_position'])) |
155 | - ctx.update_event_sources() | 153 | + click.echo('batch size: {}'.format(es['batch_size'])) |
154 | + click.echo('enabled: {}'.format(es['enabled'])) | ||
156 | click.echo('done') | 155 | click.echo('done') |
157 | elif command == 'enable': | 156 | elif command == 'enable': |
158 | click.echo('enabling event sources') | 157 | click.echo('enabling event sources') | ... | ... |
-
Please register or login to post a comment