forked from iann0036/cfn-stack-rename
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstack_rename
executable file
·244 lines (208 loc) · 12.7 KB
/
stack_rename
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
#!/usr/bin/env python3
import json
import os.path
from libs import command_parser, command_checker, custom_logger, file_handler, aws_handler
from libs import cloudformation_gather as cfn
from cfn_flip import to_json
from datetime import datetime
__version__ = '0.99beta1'
def main():
stack_name = options.stack_name
new_stack_name = options.new_stack
changeset_name = config_data['state']['change_set_name']
retain_url = None
changeset_url = None
update_url = None
new_stack_id = None
stack_params = []
cfn_client = aws_handler.AWS(data=config_data)
cfn_client.login()
cfn_client.cfn_client()
stack_desc = cfn_client.cfn_describe_stack(stack_name=stack_name)
new_stack_desc = cfn_client.cfn_describe_stack(stack_name=new_stack_name)
enable_s3 = config_data['s3']['enable']
if enable_s3:
s3_client = aws_handler.AWS(data=config_data)
s3_client.login()
s3_client.s3_client()
if not stack_desc:
logging.error(f'stack: {stack_name} does not exist aborting')
raise ValueError(f'Error {stack_name} does not exist')
if new_stack_desc:
logging.error(f'stack: {new_stack_name} already exists aborting')
raise ValueError(f'Error {new_stack_name} already exists')
stack_exports = cfn.stack_exports(stack_desc)
# for time being we will only inform the end user about the imported exports
# and get them to make the necessary changes manually.
stacks_importing = cfn.stacks_importing_exports(cfn_client, stack_exports)
if 'Parameters' in stack_desc:
stack_params = stack_desc['Parameters']
stack_id = stack_desc['StackId'] # original_stack_id
current_stack_template = cfn_client.cfn_get_template(stack_id=stack_id) # original_template
# Convert template from ordered dict to normal python dict
if not isinstance(current_stack_template, str):
current_stack_template = json.dumps(dict(current_stack_template))
stack_template = json.loads(to_json(current_stack_template))
stack_desc_resources = cfn_client.cfn_describe_resources(stack_id) # original_resources
resource_drifts = cfn.detect_drift(cfn_client, stack_id)
logging.info(f'Resource Drifts info found, sanitizing templates')
supported_imports, \
unsupported_imports, \
undriftables, \
sanitized_template, \
del_res_template = cfn.sanitize_template(config_data,
stack_template,
stack_desc_resources,
resource_drifts)
logging.info('The following resources do not have drift detection')
for ud_resource, ud_type in undriftables.items():
logging.info(f' * Resource: {ud_resource}, Type: {ud_type}')
logging.info('These will still be imported, but they do not support drift detection\n')
if len(unsupported_imports) > 1:
logging.warning(f'Please Note: The following resources will be deleted and then recreated:')
for ui_resource, ui_type in unsupported_imports.items():
logging.warning(f' * Resource {ui_resource}, Type: {ui_type}')
continue_migration = str(input('\nDo you wish to continue? (YES to do so): '))
if continue_migration != 'YES':
logging.warning(f'Aborting as requested')
exit(0)
logging.info('Sanitized Template, working out the change set information')
retain_template = cfn.set_resource_retention(template=stack_template,
supported_imports=supported_imports)
sanitized_resource_template, import_resources = cfn.sanitize_resources(config_data,
resource_drifts,
sanitized_template,
supported_imports,
undriftables,
stack_desc_resources)
logging.info('Sanitized Resources and worked out change_set resources_to_import parameters')
# remove outputs - not needed for creating change sets.
sanitized_resource_template.pop('Outputs', None)
# Creating a proper log of everything being queried - this serves 2 purposes 1st sanity checking
# 2nd if this fails we have all the data required to be able to try and recover - stack_recover
# serves this purpose.
logging.info('Creating all necessary state information for record and possible recovery')
config_data['state'][stack_name]['description'] = stack_desc
config_data['state'][stack_name]['exports'] = stack_exports
config_data['state'][stack_name]['imported_by'] = stacks_importing
config_data['state'][stack_name]['params'] = stack_params
config_data['state'][stack_name]['stack_id'] = stack_id
config_data['state'][stack_name]['resources'] = stack_desc_resources
config_data['state'][stack_name]['drifts'] = resource_drifts
config_data['state'][stack_name]['supported_resources'] = supported_imports
config_data['state'][stack_name]['unsupported_resources'] = unsupported_imports
config_data['state'][stack_name]['undriftable_resources'] = undriftables
config_data['state'][stack_name]['change_set_data'] = import_resources
state_root = config_data['state']['root']
state_path = config_data['state']['location']
current_state = dict()
current_state['date'] = date_stamp
io_handle.write_json(output_file=f"{state_path}/state.json", data=config_data)
io_handle.write_json(output_file=f"{state_path}/resources.json", data=stack_desc_resources)
io_handle.write_json(output_file=f"{state_path}/drifts.json", data=resource_drifts)
io_handle.write_json(output_file=f"{state_path}/parameters.json", data=stack_params)
io_handle.write_json(output_file=f"{state_path}/change_set.json", data=import_resources)
io_handle.write_json(output_file=f"{state_path}/unmodified_template.json", data=stack_template)
io_handle.write_json(output_file=f"{state_path}/retain_template.json", data=retain_template)
io_handle.write_json(output_file=f"{state_path}/change_set_template.json", data=sanitized_resource_template)
io_handle.write_json(output_file=f"{state_root}/current_state.json", data=current_state)
# upload templates to s3 if required
if enable_s3:
retain_file = f's3-templates/{date_stamp}/retain_{stack_name}.json'
changeset_file = f's3-templates/{date_stamp}/changeset_{new_stack_name}.json'
update_file = f's3-templates/{date_stamp}/update_{new_stack_name}.json'
retain_url = f"{config_data['s3']['uri']}/{os.path.basename(retain_file)}"
changeset_url = f"{config_data['s3']['uri']}/{os.path.basename(changeset_file)}"
update_url = f"{config_data['s3']['uri']}/{os.path.basename(update_file)}"
io_handle.write_json(output_file=retain_file, data=retain_template)
io_handle.write_json(output_file=changeset_file, data=sanitized_resource_template)
io_handle.write_json(output_file=update_file, data=stack_template)
retain_upload_success = s3_client.upload_s3(retain_file)
changeset_upload_success = s3_client.upload_s3(changeset_file)
update_upload_success = s3_client.upload_s3(update_file)
if not retain_upload_success:
raise ValueError(f'Failed to upload template {retain_file} to : {retain_url}')
if not changeset_upload_success:
raise ValueError(f'Failed to upload template {changeset_file} to: {changeset_url}')
if not update_upload_success:
raise ValueError(f'Failed to upload template {update_file} to: {update_url}')
logging.info(f'Successfully uploaded template {retain_file} to : {retain_url}')
logging.info(f'Successfully uploaded template {changeset_file} to: {changeset_url}')
logging.info(f'Successfully uploaded template {update_file} to: {update_url}')
verify_update = cfn.verify_action("Update", stack_id)
if verify_update:
if enable_s3:
update_response = cfn_client.cfn_s3_update_stack(stack_id, retain_url, params=stack_params)
else:
update_response = cfn_client.cfn_update_stack(stack_id, retain_template, params=stack_params)
cfn_client.cfn_waiter(stack_id=stack_id, waiter_type='stack_update_complete')
if update_response == "error":
cfn.verify_error()
# The whole stack has to be deleted nothing really can be done here other than to do it.
# use stack_recover if the create_changeset operation fails.
verify_delete = cfn.verify_action('Delete', stack_id)
if verify_delete:
delete_response = cfn_client.cfn_delete_stack(stack_id=stack_id)
cfn_client.cfn_waiter(stack_id=stack_id, waiter_type='stack_delete_complete')
verify_create_changeset = cfn.verify_action('Create ChangeSet', new_stack_name)
if verify_create_changeset:
if enable_s3:
new_stack_id, changeset_response = cfn_client.cfn_s3_create_changeset(new_stack_name,
changeset_url,
import_resources,
params=stack_params,
changeset_name=changeset_name)
else:
new_stack_id, changeset_response = cfn_client.cfn_create_changeset(new_stack_name,
sanitized_resource_template,
import_resources,
params=stack_params,
changeset_name=changeset_name)
cfn_client.cfn_waiter(stack_id=new_stack_id,
waiter_type='change_set_create_complete',
changeset_name=changeset_name)
verify_exec_changeset = cfn.verify_action('Execute ChangeSet', new_stack_id)
if verify_exec_changeset:
cfn_client.cfn_exec_changeset(changeset_name=changeset_name, stack_id=new_stack_id)
cfn_client.cfn_waiter(stack_id=new_stack_id, waiter_type='stack_import_complete')
# now we apply the original stack onto the new stack this will rebuild anything that couldn't be imported
# and clean up everything else including the retain flags we set for migration.
verify_update_new_stack = cfn.verify_action('Update', new_stack_id)
if verify_update_new_stack:
if enable_s3:
cfn_client.cfn_s3_update_stack(new_stack_id, update_url, params=stack_params)
else:
cfn_client.cfn_update_stack(new_stack_id, current_stack_template, params=stack_params)
cfn_client.cfn_waiter(stack_id=new_stack_id, waiter_type='stack_update_complete')
logging.warning(f'All complete \nIf you removed imports that this stack exported in any other stack '
f'please update those stacks and add in the imports.\n'
f'If the import name includes the stack name please make sure to update to include the new '
f'stack name {new_stack_name}\n'
f'If you deploy using a pipeline please make sure the pipeline is aware of the new '
f'stack name {new_stack_name} for deployment')
logging.info(f'Successfully renamed stack from: {stack_name} to {new_stack_name}')
if __name__ == "__main__":
name = 'stack_rename'
date_stamp = datetime.utcnow().strftime("%d%b%Y-%H%M%S")
# initialise the command line checker, add in all of the options
cmd_opts = command_parser.Commands(name=name, version=__version__)
cmd_opts.add_config()
cmd_opts.add_aws_auth()
cmd_opts.add_aws_config()
cmd_opts.add_cloudformation()
cmd_opts.add_aws_s3()
options, arg_parser = cmd_opts.set_options()
# set up the logging
logging = custom_logger.colourLog(name=name, config_file=options.config)
# set up the io handling
io_handle = file_handler.FileHandler()
# initialise the config data
config_data = io_handle.read_file(options.config)
# parse through the provided options make sure everything is set as required
# also do init sanity checks and config fixes/population
cmd_check = command_checker.CommandCheck(options=options, parser=arg_parser, config_data=config_data)
cmd_check.aws_auth()
cmd_check.aws_data(io_handle=io_handle)
cmd_check.aws_s3()
cmd_check.record_state(io_handle, date_stamp)
main()