forked from aws-samples/aws-lex-web-ui
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlex-manager.py
307 lines (265 loc) · 9.25 KB
/
lex-manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
#!/usr/bin/env python
##########################################################################
# Copyright 2017-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use this file
# except in compliance with the License. A copy of the License is located at
#
# http://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on an "AS IS"
# BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied. See the
# License for the specific language governing permissions and limitations under the License.
##########################################################################
"""Lex Model Building Service helper script
Used to import/export/delete Lex bots and associated resources
(i.e. intents, slot types).
Can be run as a shell script or used as a Lambda Function for CloudFormation
Custom Resources.
"""
import logging
import json
from lexutils import LexBotImporter, LexBotExporter, LexBotDeleter
DEFAULT_LOGGING_LEVEL = logging.INFO
logging.basicConfig(
format='[%(levelname)s] %(message)s',
level=DEFAULT_LOGGING_LEVEL
)
logger = logging.getLogger(__name__)
logger.setLevel(DEFAULT_LOGGING_LEVEL)
BOT_DEFINITION_FILENAME = 'bot-definition.json'
BOT_EXPORT_FILENAME = 'bot-definition-export.json'
def read_bot_definition_file(file_name=BOT_DEFINITION_FILENAME):
with open(file_name) as bot_json_file:
bot_definition = json.load(bot_json_file)
logger.info(
'sucessfully read bot definition from file: {}'.format(file_name)
)
return bot_definition
def write_bot_definition_file(bot_definition, file_name=BOT_EXPORT_FILENAME):
with open(file_name, 'w') as bot_json_file:
json.dump(bot_definition, bot_json_file,
indent=2,
sort_keys=True,
default=str
)
logger.info(
'sucessfully wrote bot definition to file: {}'.format(file_name)
)
def import_bot(bot_definition=None, definition_filename=BOT_DEFINITION_FILENAME):
if (bot_definition is None):
bot_definition = read_bot_definition_file(definition_filename)
bot_importer = LexBotImporter(
bot_definition=bot_definition,
logging_level=DEFAULT_LOGGING_LEVEL,
)
bot_importer.import_bot()
return bot_definition
def export_bot(bot_name=None, file_name=None):
if (bot_name is None):
bot_definition = read_bot_definition_file()
bot_name = bot_definition['bot']['name']
bot_exporter = LexBotExporter(
bot_name=bot_name,
logging_level=DEFAULT_LOGGING_LEVEL,
)
bot_definition = bot_exporter.export()
if (file_name is None):
print(
json.dumps(bot_definition, indent=2, sort_keys=True, default=str)
)
else:
write_bot_definition_file(bot_definition, file_name)
return bot_definition
def delete_bot(bot_name=None):
if (bot_name is None):
bot_definition = read_bot_definition_file()
bot_name = bot_definition['bot']['name']
bot_deleter = LexBotDeleter(
bot_name=bot_name,
logging_level=DEFAULT_LOGGING_LEVEL,
)
bot_definition = bot_deleter.bot_definition
bot_deleter.delete()
return bot_definition
def get_parsed_args():
""" Parse arguments passed when running as a shell script
"""
parser = argparse.ArgumentParser(
description='Lex bot manager. Import, export or delete a Lex bot.'
' Used to import/export/delete Lex bots and associated resources'
' (i.e. intents, slot types).'
)
format_group = parser.add_mutually_exclusive_group()
format_group.add_argument('-i', '--import',
nargs='?',
default=argparse.SUPPRESS,
const=BOT_DEFINITION_FILENAME,
metavar='file',
help='Import bot definition from file into account. Defaults to: {}'
.format(BOT_DEFINITION_FILENAME),
)
format_group.add_argument('-e', '--export',
nargs='?',
default=argparse.SUPPRESS,
metavar='botname',
help='Export bot definition as JSON to stdout'
' Defaults to reading the botname from the definition file: {}'
.format(BOT_DEFINITION_FILENAME),
)
format_group.add_argument('-d', '--delete',
nargs=1,
default=argparse.SUPPRESS,
metavar='botname',
help='Deletes the bot passed as argument and its associated resources.'
)
args = parser.parse_args()
if not bool(vars(args)):
parser.print_help()
sys.exit(1)
return args
def main(argv):
""" Main function used when running as a shell script
"""
parsed_args = get_parsed_args()
if 'import' in parsed_args:
try:
# using the keyword import is problematic
# turning to dict as workaround
import_bot(definition_filename=vars(parsed_args)['import'])
except Exception as e:
error = 'failed to import bot {}'.format(e)
logging.error(error);
sys.exit(1)
if 'export' in parsed_args:
try:
export_bot(bot_name=parsed_args.export)
except Exception as e:
error = 'failed to export bot {}'.format(e)
logging.error(error);
sys.exit(1)
if 'delete' in parsed_args:
try:
delete_bot(parsed_args.delete.pop())
except Exception as e:
error = 'failed to delete bot {}'.format(e)
logging.error(error);
sys.exit(1)
def add_prefix(bot_definition, prefix='WebUi'):
""" Adds a prefix to resource names in a bot definition
Used to differentiate bots from the same definition file in CloudFormation
when running multiple stacks in the same region and to make policy easier
"""
bot = bot_definition['bot']
bot['name'] = prefix + bot['name']
bot['intents'] = list(map(
lambda intent: dict(
intentName=(prefix + intent.pop('intentName')),
**intent
),
bot_definition['bot']['intents']
))
slot_types = list(map(
lambda slot_type: dict(
name=(prefix + slot_type.pop('name')),
**slot_type
),
bot_definition['slot_types']
))
intents = map(
lambda intent: dict(
name=(prefix + intent.pop('name')),
slots=list(map(
lambda slot: dict(
slotType=(prefix + slot.pop('slotType')),
**slot
) if (prefix + slot['slotType']) in [s['name'] for s in slot_types]
else slot,
intent.pop('slots')
)),
**intent
),
bot_definition['intents']
)
return dict(
bot=bot,
intents=list(intents),
slot_types=slot_types
)
def test_handler():
import time
handler(
event=dict(
RequestType='Create',
ResourceProperties=dict(
NamePrefix='WebUiTest',
)
),
context={},
)
sleep_time = 30
logger.info('sleeping for {} seconds before deleting.'.format(sleep_time))
time.sleep(sleep_time)
handler(
event=dict(
RequestType='Delete',
ResourceProperties=dict(
NamePrefix='WebUiTest',
)
),
context={},
)
def handler(event, context):
""" CloudFormation Custom Resource Lambda Handler
"""
import cfnresponse
logger.info('event: {}'.format(cfnresponse.json_dump_format(event)))
request_type = event.get('RequestType')
resource_properties = event.get('ResourceProperties')
response_status = cfnresponse.SUCCESS
response = {}
response_id = event.get('RequestId')
reason = request_type
error = ''
name_prefix = resource_properties.get('NamePrefix')
should_delete = resource_properties.get('ShouldDelete', True)
bot_definition = read_bot_definition_file(BOT_DEFINITION_FILENAME)
bot_definition_prefix = add_prefix(bot_definition, name_prefix)
if (request_type in ['Create', 'Update']):
try:
response_import = import_bot(bot_definition=bot_definition_prefix)
response['BotName'] = response_import['bot']['name']
except Exception as e:
error = 'failed to {} bot: {}'.format(request_type, e)
pass
if (request_type == 'Delete' and should_delete != 'false'):
try:
bot_definition = read_bot_definition_file()
bot_name = name_prefix + bot_definition['bot']['name']
delete_bot(bot_name)
except Exception as e:
error = 'failed to delete bot: {}'.format(e)
pass
if error:
logger.error(error)
response_status = cfnresponse.FAILED
reason = error
if bool(context):
cfnresponse.send(
event,
context,
response_status,
response,
response_id,
reason
)
if __name__ == '__main__':
#from IPython.core.debugger import Pdb ; Pdb().set_trace() # XXX
import sys
import argparse
# test lambda handler from shell with -t
if len(sys.argv) > 1 and sys.argv[1] == '-t':
test_handler()
# otherwise call main and parse arguments
else:
main(sys.argv)