Mitch Garnaat

Adding missing file.

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