Showing
1 changed file
with
268 additions
and
0 deletions
kappa/restapi.py
0 → 100644
1 | +# Copyright (c) 2014, 2015 Mitch Garnaat | ||
2 | +# | ||
3 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
4 | +# you may not use this file except in compliance with the License. | ||
5 | +# You may obtain a copy of the License at | ||
6 | +# | ||
7 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
8 | +# | ||
9 | +# Unless required by applicable law or agreed to in writing, software | ||
10 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
12 | +# See the License for the specific language governing permissions and | ||
13 | +# limitations under the License. | ||
14 | + | ||
15 | +import logging | ||
16 | + | ||
17 | +from botocore.exceptions import ClientError | ||
18 | + | ||
19 | +import kappa.awsclient | ||
20 | +import kappa.log | ||
21 | + | ||
22 | +LOG = logging.getLogger(__name__) | ||
23 | + | ||
24 | + | ||
25 | +class RestApi(object): | ||
26 | + | ||
27 | + def __init__(self, context, config): | ||
28 | + self._context = context | ||
29 | + self._config = config | ||
30 | + self._apigateway_client = kappa.awsclient.create_client( | ||
31 | + 'apigateway', context.session) | ||
32 | + self._api = None | ||
33 | + self._resources = None | ||
34 | + self._resource = None | ||
35 | + | ||
36 | + @property | ||
37 | + def arn(self): | ||
38 | + _, _, _, region, account, _ = self._context.function.arn.split(':', 5) | ||
39 | + arn = 'arn:aws:execute-api:{}:{}:{}/*/*/{}'.format( | ||
40 | + region, account, self.api_id, self.resource_name) | ||
41 | + return arn | ||
42 | + | ||
43 | + @property | ||
44 | + def api_name(self): | ||
45 | + return self._config['name'] | ||
46 | + | ||
47 | + @property | ||
48 | + def description(self): | ||
49 | + return self._config['description'] | ||
50 | + | ||
51 | + @property | ||
52 | + def resource_name(self): | ||
53 | + return self._config['resource']['name'] | ||
54 | + | ||
55 | + @property | ||
56 | + def parent_resource(self): | ||
57 | + return self._config['resource']['parent'] | ||
58 | + | ||
59 | + @property | ||
60 | + def full_path(self): | ||
61 | + parts = self.parent_resource.split('/') | ||
62 | + parts.append(self.resource_name) | ||
63 | + return '/'.join(parts) | ||
64 | + | ||
65 | + @property | ||
66 | + def api_id(self): | ||
67 | + api = self._get_api() | ||
68 | + return api.get('id') | ||
69 | + | ||
70 | + @property | ||
71 | + def resource_id(self): | ||
72 | + resources = self._get_resources() | ||
73 | + return resources.get(self.full_path).get('id') | ||
74 | + | ||
75 | + def _get_api(self): | ||
76 | + if self._api is None: | ||
77 | + try: | ||
78 | + response = self._apigateway_client.call( | ||
79 | + 'get_rest_apis') | ||
80 | + LOG.debug(response) | ||
81 | + for item in response['items']: | ||
82 | + if item['name'] == self.api_name: | ||
83 | + self._api = item | ||
84 | + except Exception: | ||
85 | + LOG.exception('Error finding restapi') | ||
86 | + return self._api | ||
87 | + | ||
88 | + def _get_resources(self): | ||
89 | + if self._resources is None: | ||
90 | + try: | ||
91 | + response = self._apigateway_client.call( | ||
92 | + 'get_resources', | ||
93 | + restApiId=self.api_id) | ||
94 | + LOG.debug(response) | ||
95 | + self._resources = {} | ||
96 | + for item in response['items']: | ||
97 | + self._resources[item['path']] = item | ||
98 | + except Exception: | ||
99 | + LOG.exception('Unable to find resources for: %s', | ||
100 | + self.api_name) | ||
101 | + return self._resources | ||
102 | + | ||
103 | + def create_restapi(self): | ||
104 | + if not self.api_exists(): | ||
105 | + LOG.info('creating restapi %s', self.api_name) | ||
106 | + try: | ||
107 | + response = self._apigateway_client.call( | ||
108 | + 'create_rest_api', | ||
109 | + name=self.api_name, | ||
110 | + description=self.description) | ||
111 | + LOG.debug(response) | ||
112 | + except Exception: | ||
113 | + LOG.exception('Unable to create new restapi') | ||
114 | + | ||
115 | + def create_resource_path(self): | ||
116 | + path = self.full_path | ||
117 | + parts = path.split('/') | ||
118 | + resources = self._get_resources() | ||
119 | + parent = None | ||
120 | + build_path = [] | ||
121 | + for part in parts: | ||
122 | + LOG.debug('part=%s', part) | ||
123 | + build_path.append(part) | ||
124 | + LOG.debug('build_path=%s', build_path) | ||
125 | + full_path = '/'.join(build_path) | ||
126 | + LOG.debug('full_path=%s', full_path) | ||
127 | + if full_path is '': | ||
128 | + parent = resources['/'] | ||
129 | + else: | ||
130 | + if full_path not in resources and parent: | ||
131 | + try: | ||
132 | + response = self._apigateway_client.call( | ||
133 | + 'create_resource', | ||
134 | + restApiId=self.api_id, | ||
135 | + parentId=parent['id'], | ||
136 | + pathPart=part) | ||
137 | + LOG.debug(response) | ||
138 | + resources[full_path] = response | ||
139 | + except Exception: | ||
140 | + LOG.exception('Unable to create new resource') | ||
141 | + parent = resources[full_path] | ||
142 | + self._item = resources[path] | ||
143 | + | ||
144 | + def create_method(self, method, config): | ||
145 | + LOG.info('creating method: %s', method) | ||
146 | + try: | ||
147 | + response = self._apigateway_client.call( | ||
148 | + 'put_method', | ||
149 | + restApiId=self.api_id, | ||
150 | + resourceId=self.resource_id, | ||
151 | + httpMethod=method, | ||
152 | + authorizationType=config.get('authorization_type'), | ||
153 | + apiKeyRequired=config.get('apikey_required', False) | ||
154 | + ) | ||
155 | + LOG.debug(response) | ||
156 | + LOG.debug('now create integration') | ||
157 | + uri = 'arn:aws:apigateway:{}:'.format( | ||
158 | + self._apigateway_client.region_name) | ||
159 | + uri += 'lambda:path/2015-03-31/functions/' | ||
160 | + uri += self._context.function.arn | ||
161 | + uri += ':${stageVariables.environment}/invocations' | ||
162 | + LOG.debug(uri) | ||
163 | + response = self._apigateway_client.call( | ||
164 | + 'put_integration', | ||
165 | + restApiId=self.api_id, | ||
166 | + resourceId=self.resource_id, | ||
167 | + httpMethod=method, | ||
168 | + integrationHttpMethod=method, | ||
169 | + type='AWS', | ||
170 | + uri=uri | ||
171 | + ) | ||
172 | + except Exception: | ||
173 | + LOG.exception('Unable to create integration: %s', method) | ||
174 | + | ||
175 | + def create_deployment(self): | ||
176 | + LOG.info('creating a deployment for %s to stage: %s', | ||
177 | + self.api_name, self._context.environment) | ||
178 | + try: | ||
179 | + response = self._apigateway_client.call( | ||
180 | + 'create_deployment', | ||
181 | + restApiId=self.api_id, | ||
182 | + stageName=self._context.environment | ||
183 | + ) | ||
184 | + LOG.debug(response) | ||
185 | + LOG.info('Now deployed to: %s', self.deployment_uri) | ||
186 | + except Exception: | ||
187 | + LOG.exception('Unable to create a deployment') | ||
188 | + | ||
189 | + def create_methods(self): | ||
190 | + resource_config = self._config['resource'] | ||
191 | + for method in resource_config.get('methods', dict()): | ||
192 | + if not self.method_exists(): | ||
193 | + method_config = resource_config['methods'][method] | ||
194 | + self.create_method(method, method_config) | ||
195 | + | ||
196 | + def api_exists(self): | ||
197 | + return self._get_api() | ||
198 | + | ||
199 | + def resource_exists(self): | ||
200 | + resources = self._get_resources() | ||
201 | + return resources.get(self.full_path) | ||
202 | + | ||
203 | + def method_exists(self, method): | ||
204 | + exists = False | ||
205 | + resource = self.resource_exists() | ||
206 | + if resource: | ||
207 | + methods = resource.get('resourceMethods') | ||
208 | + if methods: | ||
209 | + for method_name in methods: | ||
210 | + if method_name == method: | ||
211 | + exists = True | ||
212 | + return exists | ||
213 | + | ||
214 | + def find_parent_resource_id(self): | ||
215 | + parent_id = None | ||
216 | + resources = self._get_resources() | ||
217 | + for item in resources: | ||
218 | + if item['path'] == self.parent: | ||
219 | + parent_id = item['id'] | ||
220 | + return parent_id | ||
221 | + | ||
222 | + def api_update(self): | ||
223 | + LOG.info('updating restapi %s', self.api_name) | ||
224 | + | ||
225 | + def resource_update(self): | ||
226 | + LOG.info('updating resource %s', self.full_path) | ||
227 | + | ||
228 | + def add_permission(self): | ||
229 | + LOG.info('Adding permission for APIGateway to call function') | ||
230 | + self._context.function.add_permission( | ||
231 | + action='lambda:InvokeFunction', | ||
232 | + principal='apigateway.amazonaws.com', | ||
233 | + source_arn=self.arn) | ||
234 | + | ||
235 | + def deploy(self): | ||
236 | + if self.api_exists(): | ||
237 | + self.api_update() | ||
238 | + else: | ||
239 | + self.create_restapi() | ||
240 | + if self.resource_exists(): | ||
241 | + self.resource_update() | ||
242 | + else: | ||
243 | + self.create_resource_path() | ||
244 | + self.create_methods() | ||
245 | + self.add_permission() | ||
246 | + | ||
247 | + def delete(self): | ||
248 | + LOG.info('deleting resource %s', self.resource_name) | ||
249 | + try: | ||
250 | + response = self._apigateway_client.call( | ||
251 | + 'delete_resource', | ||
252 | + restApiId=self.api_id, | ||
253 | + resourceId=self.resource_id) | ||
254 | + LOG.debug(response) | ||
255 | + except ClientError: | ||
256 | + LOG.exception('Unable to delete resource ', self.resource_name) | ||
257 | + return response | ||
258 | + | ||
259 | + def status(self): | ||
260 | + try: | ||
261 | + response = self._apigateway_client.call( | ||
262 | + 'delete_', | ||
263 | + FunctionName=self.name) | ||
264 | + LOG.debug(response) | ||
265 | + except ClientError: | ||
266 | + LOG.exception('function %s not found', self.name) | ||
267 | + response = None | ||
268 | + return response |
-
Please register or login to post a comment