cloud-mu 3.1.5 → 3.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  4. data/ansible/roles/mu-windows/files/config.xml +76 -0
  5. data/ansible/roles/mu-windows/tasks/main.yml +16 -0
  6. data/bin/mu-adopt +16 -12
  7. data/bin/mu-azure-tests +57 -0
  8. data/bin/mu-cleanup +2 -4
  9. data/bin/mu-configure +52 -0
  10. data/bin/mu-deploy +3 -3
  11. data/bin/mu-findstray-tests +25 -0
  12. data/bin/mu-gen-docs +2 -4
  13. data/bin/mu-load-config.rb +2 -1
  14. data/bin/mu-node-manage +15 -16
  15. data/bin/mu-run-tests +37 -12
  16. data/cloud-mu.gemspec +3 -3
  17. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  18. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  19. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  20. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  21. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  22. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  23. data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
  24. data/extras/clean-stock-amis +25 -19
  25. data/extras/generate-stock-images +1 -0
  26. data/extras/image-generators/AWS/win2k12.yaml +2 -0
  27. data/extras/image-generators/AWS/win2k16.yaml +2 -0
  28. data/extras/image-generators/AWS/win2k19.yaml +2 -0
  29. data/modules/mommacat.ru +1 -1
  30. data/modules/mu.rb +86 -98
  31. data/modules/mu/adoption.rb +373 -58
  32. data/modules/mu/cleanup.rb +214 -303
  33. data/modules/mu/cloud.rb +128 -1733
  34. data/modules/mu/cloud/database.rb +49 -0
  35. data/modules/mu/cloud/dnszone.rb +44 -0
  36. data/modules/mu/cloud/machine_images.rb +212 -0
  37. data/modules/mu/cloud/providers.rb +81 -0
  38. data/modules/mu/cloud/resource_base.rb +929 -0
  39. data/modules/mu/cloud/server.rb +40 -0
  40. data/modules/mu/cloud/server_pool.rb +1 -0
  41. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  42. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  43. data/modules/mu/cloud/wrappers.rb +169 -0
  44. data/modules/mu/config.rb +123 -81
  45. data/modules/mu/config/alarm.rb +2 -6
  46. data/modules/mu/config/bucket.rb +32 -3
  47. data/modules/mu/config/cache_cluster.rb +2 -2
  48. data/modules/mu/config/cdn.rb +100 -0
  49. data/modules/mu/config/collection.rb +1 -1
  50. data/modules/mu/config/container_cluster.rb +7 -2
  51. data/modules/mu/config/database.rb +84 -105
  52. data/modules/mu/config/database.yml +1 -2
  53. data/modules/mu/config/dnszone.rb +5 -4
  54. data/modules/mu/config/doc_helpers.rb +5 -6
  55. data/modules/mu/config/endpoint.rb +2 -1
  56. data/modules/mu/config/firewall_rule.rb +3 -19
  57. data/modules/mu/config/folder.rb +1 -1
  58. data/modules/mu/config/function.rb +17 -8
  59. data/modules/mu/config/group.rb +1 -1
  60. data/modules/mu/config/habitat.rb +1 -1
  61. data/modules/mu/config/job.rb +89 -0
  62. data/modules/mu/config/loadbalancer.rb +57 -11
  63. data/modules/mu/config/log.rb +1 -1
  64. data/modules/mu/config/msg_queue.rb +1 -1
  65. data/modules/mu/config/nosqldb.rb +1 -1
  66. data/modules/mu/config/notifier.rb +8 -19
  67. data/modules/mu/config/ref.rb +92 -14
  68. data/modules/mu/config/role.rb +1 -1
  69. data/modules/mu/config/schema_helpers.rb +38 -37
  70. data/modules/mu/config/search_domain.rb +1 -1
  71. data/modules/mu/config/server.rb +12 -13
  72. data/modules/mu/config/server_pool.rb +3 -7
  73. data/modules/mu/config/storage_pool.rb +1 -1
  74. data/modules/mu/config/tail.rb +11 -0
  75. data/modules/mu/config/user.rb +1 -1
  76. data/modules/mu/config/vpc.rb +27 -23
  77. data/modules/mu/config/vpc.yml +0 -1
  78. data/modules/mu/defaults/AWS.yaml +90 -90
  79. data/modules/mu/defaults/Azure.yaml +1 -0
  80. data/modules/mu/defaults/Google.yaml +1 -0
  81. data/modules/mu/deploy.rb +34 -20
  82. data/modules/mu/groomer.rb +16 -1
  83. data/modules/mu/groomers/ansible.rb +69 -4
  84. data/modules/mu/groomers/chef.rb +51 -4
  85. data/modules/mu/logger.rb +120 -144
  86. data/modules/mu/master.rb +97 -4
  87. data/modules/mu/mommacat.rb +160 -874
  88. data/modules/mu/mommacat/daemon.rb +23 -14
  89. data/modules/mu/mommacat/naming.rb +110 -3
  90. data/modules/mu/mommacat/search.rb +497 -0
  91. data/modules/mu/mommacat/storage.rb +252 -194
  92. data/modules/mu/{clouds → providers}/README.md +1 -1
  93. data/modules/mu/{clouds → providers}/aws.rb +258 -57
  94. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  95. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  96. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  99. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +95 -84
  100. data/modules/mu/providers/aws/database.rb +1744 -0
  101. data/modules/mu/{clouds → providers}/aws/dnszone.rb +26 -12
  102. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  103. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +39 -32
  104. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  105. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  106. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  107. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  108. data/modules/mu/providers/aws/job.rb +466 -0
  109. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +77 -47
  110. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  111. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  112. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  113. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  114. data/modules/mu/{clouds → providers}/aws/role.rb +76 -48
  115. data/modules/mu/{clouds → providers}/aws/search_domain.rb +172 -41
  116. data/modules/mu/{clouds → providers}/aws/server.rb +66 -98
  117. data/modules/mu/{clouds → providers}/aws/server_pool.rb +42 -60
  118. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  119. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  120. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  121. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  122. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +0 -0
  123. data/modules/mu/{clouds → providers}/aws/vpc.rb +143 -74
  124. data/modules/mu/{clouds → providers}/aws/vpc_subnet.rb +0 -0
  125. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  126. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  127. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  128. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  129. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  130. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  131. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  132. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  133. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  134. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  135. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  136. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  137. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  138. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  139. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  140. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  141. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  142. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  143. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  144. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  145. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  146. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  147. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  148. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  149. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  150. data/modules/mu/{clouds → providers}/google.rb +29 -6
  151. data/modules/mu/{clouds → providers}/google/bucket.rb +4 -4
  152. data/modules/mu/{clouds → providers}/google/container_cluster.rb +38 -20
  153. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  154. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  155. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  156. data/modules/mu/{clouds → providers}/google/function.rb +6 -6
  157. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  158. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  159. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  160. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  161. data/modules/mu/{clouds → providers}/google/server.rb +41 -24
  162. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  163. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  164. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  165. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  166. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  167. data/modules/mu/{clouds → providers}/google/vpc.rb +45 -14
  168. data/modules/tests/aws-jobs-functions.yaml +46 -0
  169. data/modules/tests/centos6.yaml +15 -0
  170. data/modules/tests/centos7.yaml +15 -0
  171. data/modules/tests/centos8.yaml +12 -0
  172. data/modules/tests/ecs.yaml +2 -2
  173. data/modules/tests/eks.yaml +1 -1
  174. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  175. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  176. data/modules/tests/microservice_app.yaml +288 -0
  177. data/modules/tests/rds.yaml +108 -0
  178. data/modules/tests/regrooms/rds.yaml +123 -0
  179. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  180. data/modules/tests/super_complex_bok.yml +2 -2
  181. data/modules/tests/super_simple_bok.yml +3 -5
  182. data/spec/mu/clouds/azure_spec.rb +2 -2
  183. metadata +122 -92
  184. data/modules/mu/clouds/aws/database.rb +0 -1974
  185. data/modules/mu/clouds/aws/endpoint.rb +0 -596
@@ -1,1974 +0,0 @@
1
- # Copyright:: Copyright (c) 2014 eGlobalTech, Inc., all rights reserved
2
- #
3
- # Licensed under the BSD-3 license (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License in the root of the project or at
6
- #
7
- # http://egt-labs.com/mu/LICENSE.html
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
- autoload :Net, 'net/ssh/gateway'
16
-
17
- module MU
18
- class Cloud
19
- class AWS
20
- # A database as configured in {MU::Config::BasketofKittens::databases}
21
- class Database < MU::Cloud::Database
22
-
23
- # Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us.
24
- # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
25
- def initialize(**args)
26
- super
27
- @config["groomer"] = MU::Config.defaultGroomer unless @config["groomer"]
28
- @groomclass = MU::Groomer.loadGroomer(@config["groomer"])
29
-
30
- @mu_name ||=
31
- if @config and @config['engine'] and @config["engine"].match(/^sqlserver/)
32
- @deploy.getResourceName(@config["name"], max_length: 15)
33
- else
34
- @deploy.getResourceName(@config["name"], max_length: 63)
35
- end
36
-
37
- @mu_name.gsub(/(--|-$)/i, "").gsub(/(_)/, "-").gsub!(/^[^a-z]/i, "")
38
- end
39
-
40
- # Called automatically by {MU::Deploy#createResources}
41
- # @return [String]: The cloud provider's identifier for this database instance.
42
- def create
43
- # RDS is picky, we can't just use our regular node names for things like
44
- # the default schema or username. And it varies from engine to engine.
45
- basename = @config["name"]+@deploy.timestamp+MU.seed.downcase
46
- basename.gsub!(/[^a-z0-9]/i, "")
47
- @config["db_name"] = MU::Cloud::AWS::Database.getName(basename, type: "dbname", config: @config)
48
- @config['master_user'] = MU::Cloud::AWS::Database.getName(basename, type: "dbuser", config: @config) unless @config['master_user']
49
-
50
- # Lets make sure automatic backups are enabled when DB instance is deployed in Multi-AZ so failover actually works. Maybe default to 1 instead?
51
- if @config['multi_az_on_create'] or @config['multi_az_on_deploy'] or @config["create_cluster"]
52
- if @config["backup_retention_period"].nil? or @config["backup_retention_period"] == 0
53
- @config["backup_retention_period"] = 35
54
- MU.log "Multi-AZ deployment specified but backup retention period disabled or set to 0. Changing to #{@config["backup_retention_period"]} ", MU::WARN
55
- end
56
-
57
- if @config["preferred_backup_window"].nil?
58
- @config["preferred_backup_window"] = "05:00-05:30"
59
- MU.log "Multi-AZ deployment specified but no backup window specified. Changing to #{@config["preferred_backup_window"]} ", MU::WARN
60
- end
61
- end
62
-
63
- @config["snapshot_id"] =
64
- if @config["creation_style"] == "existing_snapshot"
65
- getExistingSnapshot ? getExistingSnapshot : createNewSnapshot
66
- elsif @config["creation_style"] == "new_snapshot"
67
- createNewSnapshot
68
- end
69
-
70
- @config['source_identifier'] = @config['identifier'] if @config["creation_style"] == "point_in_time"
71
- @config['identifier'] = @mu_name unless @config["creation_style"] == "existing"
72
- @config["subnet_group_name"] = @mu_name
73
- MU.log "Using the database identifier #{@config['identifier']}"
74
-
75
-
76
- if @config["create_cluster"]
77
- getPassword
78
- createSubnetGroup
79
-
80
- if @config.has_key?("parameter_group_family")
81
- @config["parameter_group_name"] = @config['identifier']
82
- createDBClusterParameterGroup
83
- end
84
-
85
- @cloud_id = createDbCluster
86
- elsif @config["add_cluster_node"]
87
- cluster = nil
88
- rr = @config["member_of_cluster"]
89
- cluster = @deploy.findLitterMate(type: "database", name: rr['db_name']) if rr['db_name']
90
-
91
- if cluster.nil?
92
- tag_key, tag_value = rr['tag'].split(/=/, 2) if !rr['tag'].nil?
93
- found = MU::MommaCat.findStray(
94
- rr['cloud'],
95
- "database",
96
- deploy_id: rr["deploy_id"],
97
- cloud_id: rr["db_id"],
98
- tag_key: tag_key,
99
- tag_value: tag_value,
100
- region: rr["region"],
101
- dummy_ok: true
102
- )
103
- cluster = found.first if found.size == 1
104
- end
105
-
106
- raise MuError, "Couldn't resolve cluster node reference to a unique live Database in #{@mu_name}" if cluster.nil? || cluster.cloud_id.nil?
107
- @config['cluster_identifier'] = cluster.cloud_id.downcase
108
- # We're overriding @config["subnet_group_name"] because we need each cluster member to use the cluster's subnet group instead of a unique subnet group
109
- @config["subnet_group_name"] = @config['cluster_identifier']
110
- @config["creation_style"] = "new" if @config["creation_style"] != "new"
111
-
112
- if @config.has_key?("parameter_group_family")
113
- @config["parameter_group_name"] = @config['identifier']
114
- createDBParameterGroup
115
- end
116
-
117
- @cloud_id = createDb
118
- else
119
- source_db = nil
120
- if @config['read_replica_of']
121
- rr = @config['read_replica_of']
122
- source_db = @deploy.findLitterMate(type: "database", name: rr['db_name']) if rr['db_name']
123
-
124
- if source_db.nil?
125
- tag_key, tag_value = rr['tag'].split(/=/, 2) if !rr['tag'].nil?
126
- found = MU::MommaCat.findStray(
127
- rr['cloud'],
128
- "database",
129
- deploy_id: rr["deploy_id"],
130
- cloud_id: rr["db_id"],
131
- tag_key: tag_key,
132
- tag_value: tag_value,
133
- region: rr["region"],
134
- dummy_ok: true
135
- )
136
- source_db = found.first if found.size == 1
137
- end
138
-
139
- raise MuError, "Couldn't resolve read replica reference to a unique live Database in #{@mu_name}" if source_db.nil? or source_db.cloud_id.nil?
140
- @config['source_identifier'] = source_db.cloud_id
141
- end
142
-
143
- getPassword
144
- if source_db.nil? or @config['region'] != source_db.config['region']
145
- createSubnetGroup
146
- else
147
- MU.log "Note: Read Replicas automatically reside in the same subnet group as the source database, if they're both in the same region. This replica may not land in the VPC you intended.", MU::WARN
148
- end
149
-
150
- if @config.has_key?("parameter_group_family")
151
- @config["parameter_group_name"] = @config['identifier']
152
- createDBParameterGroup
153
- end
154
-
155
- @cloud_id = createDb
156
- end
157
- end
158
-
159
- # Canonical Amazon Resource Number for this resource
160
- # @return [String]
161
- def arn
162
- cloud_desc.db_instance_arn
163
- end
164
-
165
- # Locate an existing Database or Databases and return an array containing matching AWS resource descriptors for those that match.
166
- # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching Databases
167
- def self.find(**args)
168
- found = {}
169
-
170
- if args[:cloud_id]
171
- resp = MU::Cloud::AWS::Database.getDatabaseById(args[:cloud_id], region: args[:region], credentials: args[:credentials])
172
- found[args[:cloud_id]] = resp if resp
173
- elsif args[:tag_value]
174
- MU::Cloud::AWS.rds(credentials: args[:credentials], region: args[:region]).describe_db_instances.db_instances.each { |db|
175
- resp = MU::Cloud::AWS.rds(credentials: args[:credentials], region: args[:region]).list_tags_for_resource(
176
- resource_name: MU::Cloud::AWS::Database.getARN(db.db_instance_identifier, "db", "rds", region: args[:region], credentials: args[:credentials])
177
- )
178
- if resp && resp.tag_list && !resp.tag_list.empty?
179
- resp.tag_list.each { |tag|
180
- found[db.db_instance_identifier] = db if tag.key == args[:tag_key] and tag.value == args[:tag_value]
181
- }
182
- end
183
- }
184
- else
185
- MU::Cloud::AWS.rds(credentials: args[:credentials], region: args[:region]).describe_db_instances.db_instances.each { |db|
186
- found[db.db_instance_identifier] = db
187
- }
188
- end
189
-
190
- return found
191
- end
192
-
193
- # Construct an Amazon Resource Name for an RDS resource. The RDS API is
194
- # peculiar, and we often need this identifier in order to do things that
195
- # the other APIs can do with shorthand.
196
- # @param resource [String]: The name of the resource
197
- # @param resource_type [String]: The type of the resource (one of `db, es, og, pg, ri, secgrp, snapshot, subgrp`)
198
- # @param client_type [String]: The name of the client (eg. elasticache, rds, ec2, s3)
199
- # @param region [String]: The region in which the resource resides.
200
- # @param account_number [String]: The account in which the resource resides.
201
- # @return [String]
202
- def self.getARN(resource, resource_type, client_type, region: MU.curRegion, account_number: nil, credentials: nil)
203
- account_number ||= MU::Cloud::AWS.credToAcct(credentials)
204
- aws_str = MU::Cloud::AWS.isGovCloud?(region) ? "aws-us-gov" : "aws"
205
- "arn:#{aws_str}:#{client_type}:#{region}:#{account_number}:#{resource_type}:#{resource}"
206
- end
207
-
208
- # Construct all our tags.
209
- # @return [Array]: All our standard tags and any custom tags.
210
- def allTags
211
- tags = []
212
- MU::MommaCat.listStandardTags.each_pair { |name, value|
213
- tags << {key: name, value: value}
214
- }
215
-
216
- if @config['optional_tags']
217
- MU::MommaCat.listOptionalTags.each_pair { |name, value|
218
- tags << {key: name, value: value}
219
- }
220
- end
221
-
222
- if @config['tags']
223
- @config['tags'].each { |tag|
224
- tags << {key: tag['key'], value: tag['value']}
225
- }
226
- end
227
-
228
- return tags
229
- end
230
-
231
- # Getting the password for the master user, and saving it in a database / cluster specif vault
232
- def getPassword
233
- if @config['password'].nil?
234
- if @config['auth_vault'] && !@config['auth_vault'].empty?
235
- @config['password'] = @groomclass.getSecret(
236
- vault: @config['auth_vault']['vault'],
237
- item: @config['auth_vault']['item'],
238
- field: @config['auth_vault']['password_field']
239
- )
240
- else
241
- # Should we use random instead?
242
- @config['password'] = Password.pronounceable(10..12)
243
- end
244
- end
245
-
246
- creds = {
247
- "username" => @config["master_user"],
248
- "password" => @config["password"]
249
- }
250
- @groomclass.saveSecret(vault: @mu_name, item: "database_credentials", data: creds)
251
- end
252
-
253
- # Create the database described in this instance
254
- # @return [String]: The cloud provider's identifier for this database instance.
255
- def createDb
256
- # Shared configuration elements between most database creation styles
257
- config = {
258
- db_instance_identifier: @config['identifier'],
259
- db_instance_class: @config["size"],
260
- engine: @config["engine"],
261
- auto_minor_version_upgrade: @config["auto_minor_version_upgrade"],
262
- license_model: @config["license_model"],
263
- db_subnet_group_name: @config["subnet_group_name"],
264
- publicly_accessible: @config["publicly_accessible"],
265
- copy_tags_to_snapshot: true,
266
- tags: allTags
267
- }
268
-
269
- unless @config["add_cluster_node"]
270
- config[:storage_type] = @config["storage_type"]
271
- config[:port] = @config["port"] if @config["port"]
272
- config[:iops] = @config["iops"] if @config['storage_type'] == "io1"
273
- config[:multi_az] = @config['multi_az_on_create']
274
- end
275
-
276
- if @config["creation_style"] == "new"
277
- unless @config["add_cluster_node"]
278
- config[:preferred_backup_window] = @config["preferred_backup_window"]
279
- config[:backup_retention_period] = @config["backup_retention_period"]
280
- config[:storage_encrypted] = @config["storage_encrypted"]
281
- config[:allocated_storage] = @config["storage"]
282
- config[:db_name] = @config["db_name"]
283
- config[:master_username] = @config['master_user']
284
- config[:master_user_password] = @config['password']
285
- config[:vpc_security_group_ids] = @config["vpc_security_group_ids"]
286
- end
287
-
288
- config[:engine_version] = @config["engine_version"]
289
- config[:preferred_maintenance_window] = @config["preferred_maintenance_window"] if @config["preferred_maintenance_window"]
290
- config[:db_parameter_group_name] = @config["parameter_group_name"] if @config["parameter_group_name"]
291
- config[:db_cluster_identifier] = @config["cluster_identifier"] if @config["add_cluster_node"]
292
- end
293
-
294
- if %w{existing_snapshot new_snapshot}.include?(@config["creation_style"])
295
- config[:db_snapshot_identifier] = @config["snapshot_id"]
296
- config[:db_cluster_identifier] = @config["cluster_identifier"] if @config["add_cluster_node"]
297
- end
298
-
299
- if @config["creation_style"] == "point_in_time"
300
- point_in_time_config = config
301
- point_in_time_config.delete(:db_instance_identifier)
302
- point_in_time_config[:source_db_instance_identifier] = @config['source_identifier']
303
- point_in_time_config[:target_db_instance_identifier] = @config['identifier']
304
- point_in_time_config[:restore_time] = @config['restore_time'] unless @config["restore_time"] == "latest"
305
- point_in_time_config[:use_latest_restorable_time] = true if @config['restore_time'] == "latest"
306
- end
307
-
308
- if @config["read_replica_of"]# || @config["create_read_replica"]
309
- srcdb = @config['source_identifier']
310
- if @config["read_replica_of"]["region"] and @config['region'] != @config["read_replica_of"]["region"]
311
- srcdb = MU::Cloud::AWS::Database.getARN(@config['source_identifier'], "db", "rds", region: @config["read_replica_of"]["region"], credentials: @config['credentials'])
312
- end
313
- read_replica_struct = {
314
- db_instance_identifier: @config['identifier'],
315
- source_db_instance_identifier: srcdb,
316
- db_instance_class: @config["size"],
317
- auto_minor_version_upgrade: @config["auto_minor_version_upgrade"],
318
- publicly_accessible: @config["publicly_accessible"],
319
- tags: allTags,
320
- db_subnet_group_name: @config["subnet_group_name"],
321
- storage_type: @config["storage_type"]
322
- }
323
-
324
- read_replica_struct[:port] = @config["port"] if @config["port"]
325
- read_replica_struct[:iops] = @config["iops"] if @config['storage_type'] == "io1"
326
- end
327
-
328
- # Creating DB instance
329
- attempts = 0
330
-
331
- begin
332
- if %w{existing_snapshot new_snapshot}.include?(@config["creation_style"])
333
- MU.log "Creating database instance #{@config['identifier']} from snapshot #{@config["snapshot_id"]}"
334
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).restore_db_instance_from_db_snapshot(config)
335
- elsif @config["creation_style"] == "point_in_time"
336
- MU.log "Creating database instance #{@config['identifier']} based on point in time backup #{@config['restore_time']} of #{@config['source_identifier']}"
337
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).restore_db_instance_to_point_in_time(point_in_time_config)
338
- elsif @config["read_replica_of"]
339
- MU.log "Creating read replica database instance #{@config['identifier']} for #{@config['source_identifier']}"
340
- begin
341
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_instance_read_replica(read_replica_struct)
342
- rescue Aws::RDS::Errors::DBSubnetGroupNotAllowedFault => e
343
- MU.log "Being forced to use source database's subnet group: #{e.message}", MU::WARN
344
- read_replica_struct.delete(:db_subnet_group_name)
345
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_instance_read_replica(read_replica_struct)
346
- end
347
- elsif @config["creation_style"] == "new"
348
- MU.log "Creating pristine database instance #{@config['identifier']} (#{@config['name']}) in #{@config['region']}"
349
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_instance(config)
350
- end
351
- rescue Aws::RDS::Errors::InvalidParameterValue => e
352
- if attempts < 5
353
- MU.log "Got #{e.inspect} creating #{@config['identifier']}, will retry a few times in case of transient errors.", MU::WARN, details: config
354
- attempts += 1
355
- sleep 10
356
- retry
357
- else
358
- raise MuError, "Exhausted retries trying to create database instance #{@config['identifier']}: #{e.inspect}"
359
- end
360
- end
361
-
362
- wait_start_time = Time.now
363
- retries = 0
364
-
365
- begin
366
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).wait_until(:db_instance_available, db_instance_identifier: @config['identifier']) do |waiter|
367
- # Does create_db_instance implement wait_until_available ?
368
- waiter.max_attempts = nil
369
- waiter.before_attempt do |w_attempts|
370
- MU.log "Waiting for RDS database #{@config['identifier']} to be ready...", MU::NOTICE if w_attempts % 10 == 0
371
- end
372
- waiter.before_wait do |_attempts, r|
373
- throw :success if r.db_instances.first.db_instance_status == "available"
374
- throw :failure if Time.now - wait_start_time > 3600
375
- end
376
- end
377
- rescue Aws::Waiters::Errors::TooManyAttemptsError => e
378
- raise MuError, "Waited #{(Time.now - wait_start_time).round/60*(retries+1)} minutes for #{@config['identifier']} to become available, giving up. #{e}" if retries > 2
379
- wait_start_time = Time.now
380
- retries += 1
381
- retry
382
- end
383
-
384
- database = MU::Cloud::AWS::Database.getDatabaseById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
385
- MU::Cloud::AWS::DNSZone.genericMuDNSEntry(name: database.db_instance_identifier, target: "#{database.endpoint.address}.", cloudclass: MU::Cloud::Database, sync_wait: @config['dns_sync_wait'])
386
- MU.log "Database #{@config['name']} is at #{database.endpoint.address}", MU::SUMMARY
387
- if @config['auth_vault']
388
- MU.log "knife vault show #{@config['auth_vault']['vault']} #{@config['auth_vault']['item']} for Database #{@config['name']} credentials", MU::SUMMARY
389
- end
390
-
391
- # If referencing an existing DB, insert this deploy's DB security group so it can access db
392
- if @config["creation_style"] == 'existing'
393
- vpc_sg_ids = []
394
- database.vpc_security_groups.each { |vpc_sg|
395
- vpc_sg_ids << vpc_sg.vpc_security_group_id
396
- }
397
-
398
- localdeploy_rule = @deploy.findLitterMate(type: "firewall_rule", name: "database"+@config['name'])
399
- if localdeploy_rule.nil?
400
- raise MU::MuError, "Database #{@config['name']} failed to find its generic security group 'database#{@config['name']}'"
401
- end
402
- MU.log "Found this deploy's DB security group: #{localdeploy_rule.cloud_id}", MU::DEBUG
403
- vpc_sg_ids << localdeploy_rule.cloud_id
404
- mod_config = Hash.new
405
- mod_config[:vpc_security_group_ids] = vpc_sg_ids
406
- mod_config[:db_instance_identifier] = @config["identifier"]
407
-
408
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_instance(mod_config)
409
- MU.log "Modified database #{@config['identifier']} with new security groups: #{mod_config}", MU::NOTICE
410
- end
411
-
412
- # When creating from a snapshot, some of the create arguments aren't
413
- # applicable- but we can apply them after the fact with a modify.
414
- if %w{existing_snapshot new_snapshot point_in_time}.include?(@config["creation_style"]) or @config["read_replica_of"]
415
- mod_config = Hash.new
416
- if !@config["read_replica_of"]
417
- mod_config[:preferred_backup_window] = @config["preferred_backup_window"]
418
- mod_config[:backup_retention_period] = @config["backup_retention_period"]
419
- mod_config[:engine_version] = @config["engine_version"]
420
- mod_config[:allow_major_version_upgrade] = @config["allow_major_version_upgrade"] if @config['allow_major_version_upgrade']
421
- mod_config[:db_parameter_group_name] = @config["parameter_group_name"] if @config["parameter_group_name"]
422
- mod_config[:master_user_password] = @config['password']
423
- mod_config[:allocated_storage] = @config["storage"] if @config["storage"]
424
- end
425
- mod_config[:db_instance_identifier] = database.db_instance_identifier
426
- mod_config[:preferred_maintenance_window] = @config["preferred_maintenance_window"] if @config["preferred_maintenance_window"]
427
- mod_config[:vpc_security_group_ids] = @config["vpc_security_group_ids"]
428
- mod_config[:apply_immediately] = true
429
-
430
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_instance(mod_config)
431
- wait_start_time = Time.now
432
- retries = 0
433
-
434
- begin
435
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).wait_until(:db_instance_available, db_instance_identifier: @config['identifier']) do |waiter|
436
- # Does create_db_instance implement wait_until_available ?
437
- waiter.max_attempts = nil
438
- waiter.before_attempt do |w_attempts|
439
- MU.log "Waiting for RDS database #{@config['identifier'] } to be ready..", MU::NOTICE if w_attempts % 10 == 0
440
- end
441
- waiter.before_wait do |_attempts, r|
442
- throw :success if r.db_instances.first.db_instance_status == "available"
443
- throw :failure if Time.now - wait_start_time > 2400
444
- end
445
- end
446
- rescue Aws::Waiters::Errors::TooManyAttemptsError => e
447
- raise MuError, "Waited #{(Time.now - wait_start_time).round/60*(retries+1)} minutes for #{@config['identifier']} to become available, giving up. #{e}" if retries > 2
448
- wait_start_time = Time.now
449
- retries += 1
450
- retry
451
- end
452
- end
453
-
454
- # Maybe wait for DB instance to be in available state. DB should still be writeable at this state
455
- if @config['allow_major_version_upgrade'] && @config["creation_style"] == "new"
456
- MU.log "Setting major database version upgrade on #{@config['identifier']}'"
457
- database = MU::Cloud::AWS::Database.getDatabaseById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
458
- begin
459
- if database.db_instance_status != "available"
460
- sleep 5
461
- database = MU::Cloud::AWS::Database.getDatabaseById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
462
- end
463
- end while database.db_instance_status != "available"
464
-
465
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_instance(
466
- db_instance_identifier: @config['identifier'],
467
- apply_immediately: true,
468
- allow_major_version_upgrade: true
469
- )
470
- end
471
-
472
- MU.log "Database #{@config['identifier']} is ready to use"
473
- return database.db_instance_identifier
474
- end
475
-
476
- # Create the database cluster described in this instance
477
- # @return [String]: The cloud provider's identifier for this database cluster.
478
- def createDbCluster
479
- cluster_config_struct = {
480
- db_cluster_identifier: @config['identifier'],
481
- # downcasing @config["subnet_group_name"] becuase the API is choking on upper case.
482
- db_subnet_group_name: @config["subnet_group_name"].downcase,
483
- vpc_security_group_ids: @config["vpc_security_group_ids"],
484
- tags: allTags
485
- }
486
- cluster_config_struct[:port] = @config["port"] if @config["port"]
487
-
488
- if @config['cluster_mode']
489
- cluster_config_struct[:engine_mode] = @config['cluster_mode']
490
- if @config['cluster_mode'] == "serverless"
491
- cluster_config_struct[:scaling_configuration] = {
492
- :auto_pause => @config['serverless_scaling']['auto_pause'],
493
- :min_capacity => @config['serverless_scaling']['min_capacity'],
494
- :max_capacity => @config['serverless_scaling']['max_capacity'],
495
- :seconds_until_auto_pause => @config['serverless_scaling']['seconds_until_auto_pause']
496
- }
497
- end
498
- end
499
-
500
- if %w{existing_snapshot new_snapshot}.include?(@config["creation_style"])
501
- cluster_config_struct[:snapshot_identifier] = @config["snapshot_id"]
502
- cluster_config_struct[:engine] = @config["engine"]
503
- cluster_config_struct[:engine_version] = @config["engine_version"]
504
- cluster_config_struct[:database_name] = @config["db_name"]
505
- end
506
-
507
- if @config["creation_style"] == "new"
508
- cluster_config_struct[:backup_retention_period] = @config["backup_retention_period"]
509
- cluster_config_struct[:database_name] = @config["db_name"]
510
- cluster_config_struct[:db_cluster_parameter_group_name] = @config["parameter_group_name"]
511
- cluster_config_struct[:engine] = @config["engine"]
512
- cluster_config_struct[:engine_version] = @config["engine_version"]
513
- cluster_config_struct[:master_username] = @config["master_user"]
514
- cluster_config_struct[:master_user_password] = @config["password"]
515
- cluster_config_struct[:preferred_backup_window] = @config["preferred_backup_window"]
516
- cluster_config_struct[:preferred_maintenance_window] = @config["preferred_maintenance_window"]
517
- end
518
-
519
- if @config["creation_style"] == "point_in_time"
520
- cluster_config_struct[:source_db_cluster_identifier] = @config["source_identifier"]
521
- cluster_config_struct[:restore_to_time] = @config["restore_time"] unless @config["restore_time"] == "latest"
522
- cluster_config_struct[:use_latest_restorable_time] = true if @config["restore_time"] == "latest"
523
- end
524
-
525
- if @config['cloudwatch_logs']
526
- cluster_config_struct[:enable_cloudwatch_logs_exports ] = @config['cloudwatch_logs']
527
- end
528
-
529
- attempts = 0
530
- begin
531
- if @config["creation_style"] == "new"
532
- MU.log "Creating new database cluster #{@config['identifier']}"
533
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_cluster(cluster_config_struct)
534
- elsif %w{existing_snapshot new_snapshot}.include?(@config["creation_style"])
535
- MU.log "Creating new database cluster #{@config['identifier']} from snapshot #{@config["snapshot_id"]}"
536
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).restore_db_cluster_from_snapshot(cluster_config_struct)
537
- elsif @config["creation_style"] == "point_in_time"
538
- MU.log "Creating new database cluster #{@config['identifier']} from point in time backup #{@config["restore_time"]} of #{@config["source_identifier"]}"
539
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).restore_db_cluster_to_point_in_time(cluster_config_struct)
540
- end
541
- rescue Aws::RDS::Errors::InvalidParameterValue => e
542
- if attempts < 5
543
- MU.log "Got #{e.inspect} while creating database cluster #{@config['identifier']}, will retry a few times in case of transient errors.", MU::WARN, details: cluster_config_struct
544
- attempts += 1
545
- sleep 10
546
- retry
547
- else
548
- MU.log "Exhausted retries trying to create database cluster #{@config['identifier']}", MU::ERR, details: e.inspect
549
- raise MuError, "Exhausted retries trying to create database cluster #{@config['identifier']}"
550
- end
551
- end
552
-
553
- attempts = 0
554
- loop do
555
- MU.log "Waiting for #{@config['identifier']} to become available", MU::NOTICE if attempts % 5 == 0
556
- attempts += 1
557
- cluster = MU::Cloud::AWS::Database.getDatabaseClusterById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
558
- break unless cluster.status != "available"
559
- sleep 30
560
- end
561
-
562
- if %w{existing_snapshot new_snapshot point_in_time}.include?(@config["creation_style"])
563
- modify_db_cluster_struct = {
564
- db_cluster_identifier: @config['identifier'],
565
- apply_immediately: true,
566
- backup_retention_period: @config["backup_retention_period"],
567
- db_cluster_parameter_group_name: @config["parameter_group_name"],
568
- master_user_password: @config["password"],
569
- preferred_backup_window: @config["preferred_backup_window"]
570
- }
571
-
572
- modify_db_cluster_struct[:preferred_maintenance_window] = @config["preferred_maintenance_window"] if @config["preferred_maintenance_window"]
573
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_cluster(modify_db_cluster_struct)
574
-
575
- attempts = 0
576
- loop do
577
- MU.log "Waiting for #{@config['identifier']} to become available", MU::NOTICE if attempts % 5 == 0
578
- attempts += 1
579
- cluster = MU::Cloud::AWS::Database.getDatabaseClusterById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
580
- break unless cluster.status != "available"
581
- sleep 30
582
- end
583
- end
584
-
585
- cluster = MU::Cloud::AWS::Database.getDatabaseClusterById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
586
- MU::Cloud::AWS::DNSZone.genericMuDNSEntry(name: cluster.db_cluster_identifier, target: "#{cluster.endpoint}.", cloudclass: MU::Cloud::Database, sync_wait: @config['dns_sync_wait'])
587
- return cluster.db_cluster_identifier
588
- end
589
-
590
- # Create a subnet group for a database.
591
- def createSubnetGroup
592
- # Finding subnets, creating security groups/adding holes, create subnet group
593
- subnet_ids = []
594
- vpc_id = nil
595
- if @config['vpc'] and !@config['vpc'].empty?
596
- raise MuError, "Didn't find the VPC specified in #{@config["vpc"]}" unless @vpc
597
-
598
- vpc_id = @vpc.cloud_id
599
- # Getting subnet IDs
600
- subnets =
601
- if @config["vpc"]["subnets"].empty?
602
- @vpc.subnets
603
- else
604
- subnet_objects= []
605
- @config["vpc"]["subnets"].each { |subnet|
606
- sobj = @vpc.getSubnet(cloud_id: subnet["subnet_id"], name: subnet["subnet_name"])
607
- if sobj.nil?
608
- MU.log "Got nil result from @vpc.getSubnet(cloud_id: #{subnet["subnet_id"]}, name: #{subnet["subnet_name"]})", MU::WARN
609
- else
610
- subnet_objects << sobj
611
- end
612
- }
613
- subnet_objects
614
- end
615
-
616
- subnets.each{ |subnet|
617
- next if subnet.nil?
618
- next if @config["publicly_accessible"] and subnet.private?
619
- subnet_ids << subnet.cloud_id
620
- }
621
- else
622
- # If we didn't specify a VPC try to figure out if the account has a default VPC
623
- vpc_id = nil
624
- subnets = []
625
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_vpcs.vpcs.each { |vpc|
626
- if vpc.is_default
627
- vpc_id = vpc.vpc_id
628
- subnets = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_subnets(
629
- filters: [
630
- {
631
- name: "vpc-id",
632
- values: [vpc_id]
633
- }
634
- ]
635
- ).subnets
636
- break
637
- end
638
- }
639
-
640
- if !subnets.empty?
641
- mu_subnets = []
642
- subnets.each { |subnet|
643
- subnet_ids << subnet.subnet_id
644
- mu_subnets << {"subnet_id" => subnet.subnet_id}
645
- }
646
-
647
- @config['vpc'] = {
648
- "vpc_id" => vpc_id,
649
- "subnets" => mu_subnets
650
- }
651
- # Default VPC has only public subnets by default so setting publicly_accessible = true
652
- @config["publicly_accessible"] = true
653
- MU.log "Using default VPC for cache cluster #{@config['identifier']}"
654
- end
655
- end
656
-
657
- if @config['creation_style'] == "existing"
658
- srcdb = MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).describe_db_instances(
659
- db_instance_identifier: @config['identifier']
660
- )
661
- srcdb_vpc = srcdb.db_instances.first.db_subnet_group.vpc_id
662
- if srcdb_vpc != vpc_id
663
- MU.log "#{self} is deploying into #{vpc_id}, but our source database, #{@config['identifier']}, is in #{srcdb_vpc}", MU::ERR
664
- raise MuError, "Can't use 'existing' to deploy into a different VPC from the source database; try 'new_snapshot' instead"
665
- end
666
- end
667
-
668
- if subnet_ids.empty?
669
- raise MuError, "Couldn't find subnets in #{@vpc} to add to #{@config["subnet_group_name"]}. Make sure the subnets are valid and publicly_accessible is set correctly"
670
- else
671
- # Create subnet group
672
- resp = MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_subnet_group(
673
- db_subnet_group_name: @config["subnet_group_name"],
674
- db_subnet_group_description: @config["subnet_group_name"],
675
- subnet_ids: subnet_ids,
676
- tags: allTags
677
- )
678
- @config["subnet_group_name"] = resp.db_subnet_group.db_subnet_group_name
679
-
680
- if @dependencies.has_key?('firewall_rule')
681
- @config["vpc_security_group_ids"] = []
682
- @dependencies['firewall_rule'].values.each { |sg|
683
- @config["vpc_security_group_ids"] << sg.cloud_id
684
- }
685
- end
686
- end
687
-
688
- # Find NAT and create holes in security groups.
689
- if @config["vpc"]["nat_host_name"] || @config["vpc"]["nat_host_id"] || @config["vpc"]["nat_host_tag"] || @config["vpc"]["nat_host_ip"]
690
- nat = @nat
691
- if nat.is_a?(Struct) && nat.nat_gateway_id && nat.nat_gateway_id.start_with?("nat-")
692
- MU.log "Using NAT Gateway, not modifying security groups"
693
- else
694
- _nat_name, _nat_conf, nat_deploydata = @nat.describe
695
- @deploy.kittens['firewall_rules'].values.each { |acl|
696
- # XXX if a user doesn't set up dependencies correctly, this can die horribly on a NAT that's still in mid-creation. Fix this... possibly in the config parser.
697
- if acl.config["admin"]
698
- acl.addRule([nat_deploydata["private_ip_address"]], proto: "tcp")
699
- acl.addRule([nat_deploydata["private_ip_address"]], proto: "udp")
700
- break
701
- end
702
- }
703
- end
704
- end
705
- end
706
-
707
- # Create a database cluster parameter group.
708
- def createDBClusterParameterGroup
709
- MU.log "Creating a cluster parameter group #{@config["parameter_group_name"]}"
710
-
711
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_cluster_parameter_group(
712
- db_cluster_parameter_group_name: @config["parameter_group_name"],
713
- db_parameter_group_family: @config["parameter_group_family"],
714
- description: "Parameter group for #{@config["parameter_group_family"]}",
715
- tags: allTags
716
- )
717
-
718
- if @config["cluster_parameter_group_parameters"] && !@config["cluster_parameter_group_parameters"].empty?
719
- params = []
720
- @config["cluster_parameter_group_parameters"].each { |item|
721
- params << {parameter_name: item['name'], parameter_value: item['value'], apply_method: item['apply_method']}
722
- }
723
-
724
- MU.log "Modifiying cluster parameter group #{@config["parameter_group_name"]}"
725
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_cluster_parameter_group(
726
- db_cluster_parameter_group_name: @config["parameter_group_name"],
727
- parameters: params
728
- )
729
- end
730
- end
731
-
732
- # Create a database parameter group.
733
- def createDBParameterGroup
734
- MU.log "Creating a database parameter group #{@config["parameter_group_name"]}"
735
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_parameter_group(
736
- db_parameter_group_name: @config["parameter_group_name"],
737
- db_parameter_group_family: @config["parameter_group_family"],
738
- description: "Parameter group for #{@config["parameter_group_family"]}",
739
- tags: allTags
740
- )
741
-
742
- if @config["db_parameter_group_parameters"] && !@config["db_parameter_group_parameters"].empty?
743
- params = []
744
- @config["db_parameter_group_parameters"].each { |item|
745
- params << {parameter_name: item['name'], parameter_value: item['value'], apply_method: item['apply_method']}
746
- }
747
-
748
- MU.log "Modifiying database parameter group #{@config["parameter_group_name"]}"
749
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_parameter_group(
750
- db_parameter_group_name: @config["parameter_group_name"],
751
- parameters: params
752
- )
753
- end
754
- end
755
-
756
- # Retrieve a complete description of a database cluster parameter group.
757
- # @param param_group_id [String]: The cloud provider's identifier for this parameter group.
758
- # @param region [String]: The cloud provider region
759
- # @return [OpenStruct]
760
- def self.getDBClusterParameterGroup(param_group_id, region: MU.curRegion)
761
- MU::Cloud::AWS.rds(region: region).describe_db_cluster_parameter_groups(db_cluster_parameter_group_name: param_group_id).db_cluster_parameter_groups.first
762
- # rescue DBClusterParameterGroupNotFound => e
763
- # Of course the API will return DBParameterGroupNotFound instead of the documented DBClusterParameterGroupNotFound error.
764
- rescue Aws::RDS::Errors::DBParameterGroupNotFound
765
- #we're fine returning nil
766
- end
767
-
768
- # Retrieve a complete description of a database parameter group.
769
- # @param param_group_id [String]: The cloud provider's identifier for this parameter group.
770
- # @param region [String]: The cloud provider region
771
- # @return [OpenStruct]
772
- def self.getDBParameterGroup(param_group_id, region: MU.curRegion)
773
- MU::Cloud::AWS.rds(region: region).describe_db_parameter_groups(db_parameter_group_name: param_group_id).db_parameter_groups.first
774
- rescue Aws::RDS::Errors::DBParameterGroupNotFound
775
- #we're fine returning nil
776
- end
777
-
778
- # Retrieve a complete description of a database subnet group.
779
- # @param subnet_id [String]: The cloud provider's identifier for this subnet group.
780
- # @param region [String]: The cloud provider region
781
- # @return [OpenStruct]
782
- def self.getSubnetGroup(subnet_id, region: MU.curRegion)
783
- MU::Cloud::AWS.rds(region: region).describe_db_subnet_groups(db_subnet_group_name: subnet_id).db_subnet_groups.first
784
- rescue Aws::RDS::Errors::DBSubnetGroupNotFoundFault
785
- #we're fine returning nil
786
- end
787
-
788
- # Called automatically by {MU::Deploy#createResources}
789
- def groom
790
- if @config["create_cluster"]
791
- @config['cluster_node_count'] ||= 1
792
- if @config['cluster_mode'] == "serverless"
793
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_current_db_cluster_capacity(
794
- db_cluster_identifier: @cloud_id,
795
- capacity: @config['cluster_node_count']
796
- )
797
- end
798
- else
799
- database = MU::Cloud::AWS::Database.getDatabaseById(@config['identifier'], region: @config['region'], credentials: @config['credentials'])
800
-
801
- # Run SQL on deploy
802
- if @config['run_sql_on_deploy']
803
- MU.log "Running initial SQL commands on #{@config['name']}", details: @config['run_sql_on_deploy']
804
-
805
- # check if DB is private or public
806
- if !database.publicly_accessible
807
- # This doesn't necessarily mean what we think it does. publicly_accessible = true means resolve to public address.
808
- # publicly_accessible can still be set to true even when only private subnets are included in the subnet group. We try to solve this during creation.
809
- is_private = true
810
- else
811
- is_private = false
812
- end
813
-
814
- #Setting up connection params
815
- ssh_keydir = Etc.getpwuid(Process.uid).dir+"/.ssh"
816
- keypairname, _ssh_private_key, _ssh_public_key = @deploy.SSHKey
817
- if is_private and @vpc
818
- if @config['vpc']['nat_host_name']
819
- begin
820
- gateway = Net::SSH::Gateway.new(
821
- @config['vpc']['nat_host_name'],
822
- @config['vpc']['nat_ssh_user'],
823
- :keys => [ssh_keydir+"/"+keypairname],
824
- :keys_only => true,
825
- :auth_methods => ['publickey'],
826
- # :verbose => :info
827
- )
828
- port = gateway.open(database.endpoint.address, database.endpoint.port)
829
- address = "127.0.0.1"
830
- MU.log "Tunneling #{@config['engine']} connection through #{nat_host_name} via local port #{port}", MU::DEBUG
831
- rescue IOError => e
832
- MU.log "Got #{e.inspect} while connecting to #{@config['identifier']} through NAT #{nat_host_name}", MU::ERR
833
- end
834
- else
835
- MU.log "Can't run initial SQL commands! Database #{@config['identifier']} is not publicly accessible, but we have no NAT host for connecting to it", MU::WARN, details: @config['run_sql_on_deploy']
836
- end
837
- else
838
- port = database.endpoint.port
839
- address = database.endpoint.address
840
- end
841
-
842
- # Running SQL on deploy
843
- if @config['engine'] == "postgres"
844
- autoload :PG, 'pg'
845
- begin
846
- conn = PG::Connection.new(
847
- :host => address,
848
- :port => port,
849
- :user => @config['master_user'],
850
- :dbname => database.db_name,
851
- :password => @config['password']
852
- )
853
- @config['run_sql_on_deploy'].each { |cmd|
854
- MU.log "Running #{cmd} on database #{@config['name']}"
855
- conn.exec(cmd)
856
- }
857
- conn.finish
858
- rescue PG::Error => e
859
- MU.log "Failed to run initial SQL commands on #{@config['name']} via #{address}:#{port}: #{e.inspect}", MU::WARN, details: conn
860
- end
861
- elsif @config['engine'] == "mysql"
862
- autoload :Mysql, 'mysql'
863
- MU.log "Initiating mysql connection to #{address}:#{port} as #{@config['master_user']}"
864
- conn = Mysql.new(address, @config['master_user'], @config['password'], "mysql", port)
865
- @config['run_sql_on_deploy'].each { |cmd|
866
- MU.log "Running #{cmd} on database #{@config['name']}"
867
- conn.query(cmd)
868
- }
869
- conn.close
870
- end
871
-
872
- # close the SQL on deploy sessions
873
- if is_private
874
- begin
875
- gateway.close(port)
876
- rescue IOError => e
877
- MU.log "Failed to close ssh session to NAT after running sql_on_deploy", MU::ERR, details: e.inspect
878
- end
879
- end
880
- end
881
-
882
- # set multi-az on deploy
883
- if @config['multi_az_on_deploy']
884
- if !database.multi_az
885
- MU.log "Setting multi-az on #{@config['identifier']}"
886
- attempts = 0
887
- begin
888
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).modify_db_instance(
889
- db_instance_identifier: @config['identifier'],
890
- apply_immediately: true,
891
- multi_az: true
892
- )
893
- rescue Aws::RDS::Errors::InvalidParameterValue, Aws::RDS::Errors::InvalidDBInstanceState => e
894
- if attempts < 15
895
- MU.log "Got #{e.inspect} while setting Multi-AZ on #{@config['identifier']}, retrying."
896
- attempts += 1
897
- sleep 15
898
- retry
899
- else
900
- MU.log "Couldn't set Multi-AZ on #{@config['identifier']} after several retries, giving up. #{e.inspect}", MU::ERR
901
- end
902
- end
903
- end
904
- end
905
- end
906
- end
907
-
908
- # Generate database user, database identifier, database name based on engine-specific constraints
909
- # @return [String]: Name
910
- def self.getName(basename, type: 'dbname', config: nil)
911
- if type == 'dbname'
912
- # Apply engine-specific db name constraints
913
- if config["engine"].match(/^oracle/)
914
- (MU.seed.downcase+config["name"])[0..7]
915
- elsif config["engine"].match(/^sqlserver/)
916
- nil
917
- elsif config["engine"].match(/^mysql/)
918
- basename[0..63]
919
- elsif config["engine"].match(/^aurora/)
920
- (MU.seed.downcase+config["name"])[0..7]
921
- else
922
- basename
923
- end
924
- elsif type == 'dbuser'
925
- # Apply engine-specific master username constraints
926
- if config["engine"].match(/^oracle/)
927
- basename[0..29].gsub(/[^a-z0-9]/i, "")
928
- elsif config["engine"].match(/^sqlserver/)
929
- basename[0..127].gsub(/[^a-z0-9]/i, "")
930
- elsif config["engine"].match(/^(mysql|maria)/)
931
- basename[0..15].gsub(/[^a-z0-9]/i, "")
932
- elsif config["engine"].match(/^aurora/)
933
- basename[0..15].gsub(/[^a-z0-9]/i, "")
934
- else
935
- basename.gsub(/[^a-z0-9]/i, "")
936
- end
937
- end
938
- end
939
-
940
- # Permit a host to connect to the given database instance.
941
- # @param cidr [String]: The CIDR-formatted IP address or block to allow access.
942
- # @return [void]
943
- def allowHost(cidr)
944
- # If we're an old, Classic-style database with RDS-specific
945
- # authorization, punch holes in that.
946
- if !cloud_desc.db_security_groups.empty?
947
- cloud_desc.db_security_groups.each { |rds_sg|
948
- begin
949
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).authorize_db_security_group_ingress(
950
- db_security_group_name: rds_sg.db_security_group_name,
951
- cidrip: cidr
952
- )
953
- rescue Aws::RDS::Errors::AuthorizationAlreadyExists
954
- MU.log "CIDR #{cidr} already in database instance #{@cloud_id} security group", MU::WARN
955
- end
956
- }
957
- end
958
-
959
- # Otherwise go get our generic EC2 ruleset and punch a hole in it
960
- if @dependencies.has_key?('firewall_rule')
961
- @dependencies['firewall_rule'].values.each { |sg|
962
- sg.addRule([cidr], proto: "tcp", port: cloud_desc.endpoint.port)
963
- break
964
- }
965
- end
966
- end
967
-
968
- # Retrieve the complete cloud provider description of a database instance.
969
- # @param db_id [String]: The cloud provider's identifier for this database.
970
- # @param region [String]: The cloud provider region
971
- # @return [OpenStruct]
972
- def self.getDatabaseById(db_id, region: MU.curRegion, credentials: nil)
973
- raise MuError, "You must provide a db_id" if db_id.nil?
974
- MU::Cloud::AWS.rds(region: region, credentials: credentials).describe_db_instances(db_instance_identifier: db_id).db_instances.first
975
- rescue Aws::RDS::Errors::DBInstanceNotFound
976
- # We're fine with this returning nil when searching for a database instance the doesn't exist.
977
- end
978
-
979
- # Retrieve the complete cloud provider description of a database cluster.
980
- # @param db_cluster_id [String]: The cloud provider's identifier for this database cluster.
981
- # @param region [String]: The cloud provider region
982
- # @return [OpenStruct]
983
- def self.getDatabaseClusterById(db_cluster_id, region: MU.curRegion, credentials: nil)
984
- MU::Cloud::AWS.rds(region: region, credentials: credentials).describe_db_clusters(db_cluster_identifier: db_cluster_id).db_clusters.first
985
- rescue Aws::RDS::Errors::DBClusterNotFoundFault
986
- # We're fine with this returning nil when searching for a database cluster the doesn't exist.
987
- end
988
-
989
- # Register a description of this database instance with this deployment's metadata.
990
- # Register read replicas as separate instances, while we're
991
- # at it.
992
- def notify
993
- my_dbs = [@config]
994
- if @config['read_replica']
995
- @config['read_replica']['creation_style'] = "read_replica"
996
- @config['read_replica']['password'] = @config["password"]
997
- my_dbs << @config['read_replica']
998
- end
999
-
1000
- deploy_struct = {}
1001
- my_dbs.each { |db|
1002
- deploy_struct =
1003
- if db["create_cluster"]
1004
- db["identifier"] = @mu_name.downcase if db["identifier"].nil?
1005
- cluster = MU::Cloud::AWS::Database.getDatabaseClusterById(db["identifier"], region: db['region'], credentials: @config['credentials'])
1006
- # DNS records for the "real" zone should always be registered as late as possible so override_existing only overwrites the records after the resource is ready to use.
1007
- if db['dns_records']
1008
- db['dns_records'].each { |dnsrec|
1009
- dnsrec['name'] = cluster.db_cluster_identifier if !dnsrec.has_key?('name')
1010
- dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/)
1011
- }
1012
- end
1013
- # XXX this should be a call to @deploy.nameKitten
1014
- MU::Cloud::AWS::DNSZone.createRecordsFromConfig(db['dns_records'], target: cluster.endpoint)
1015
-
1016
- vpc_sg_ids = []
1017
- cluster.vpc_security_groups.each { |vpc_sg|
1018
- vpc_sg_ids << vpc_sg.vpc_security_group_id
1019
- }
1020
-
1021
- {
1022
- "allocated_storage" => cluster.allocated_storage,
1023
- "parameter_group" => cluster.db_cluster_parameter_group,
1024
- "subnet_group" => cluster.db_subnet_group,
1025
- "identifier" => cluster.db_cluster_identifier,
1026
- "region" => db['region'],
1027
- "engine" => cluster.engine,
1028
- "engine_version" => cluster.engine_version,
1029
- "backup_retention_period" => cluster.backup_retention_period,
1030
- "preferred_backup_window" => cluster.preferred_backup_window,
1031
- "preferred_maintenance_window" => cluster.preferred_maintenance_window,
1032
- "endpoint" => cluster.endpoint,
1033
- "port" => cluster.port,
1034
- "username" => cluster.master_username,
1035
- "vpc_sgs" => vpc_sg_ids,
1036
- "azs" => cluster.availability_zones,
1037
- "vault_name" => cluster.db_cluster_identifier.upcase,
1038
- "vault_item" => "database_credentials",
1039
- "password_field" => "password",
1040
- "create_style" => db['creation_style'],
1041
- "db_name" => cluster.database_name,
1042
- "db_cluster_members" => cluster.db_cluster_members
1043
- }
1044
- else
1045
- db["identifier"] = @mu_name.downcase if db["identifier"].nil? # Is this still valid if we have read replicas?
1046
- database = MU::Cloud::AWS::Database.getDatabaseById(db["identifier"], region: db['region'])
1047
- # DNS records for the "real" zone should always be registered as late as possible so override_existing only overwrites the records after the resource is ready to use.
1048
- unless db["add_cluster_node"]
1049
- # It isn't necessarily clear what we should do with DNS records of cluster members. Probably need to expose this to the BoK somehow.
1050
- if db['dns_records']
1051
- db['dns_records'].each { |dnsrec|
1052
- dnsrec['name'] = database.db_instance_identifier if !dnsrec.has_key?('name')
1053
- dnsrec['name'] = "#{dnsrec['name']}.#{MU.environment.downcase}" if dnsrec["append_environment_name"] && !dnsrec['name'].match(/\.#{MU.environment.downcase}$/)
1054
- }
1055
- # XXX this should be a call to @deploy.nameKitten
1056
- MU::Cloud::AWS::DNSZone.createRecordsFromConfig(db['dns_records'], target: database.endpoint.address)
1057
- end
1058
- end
1059
-
1060
- database = cloud_desc
1061
-
1062
- vpc_sg_ids = Array.new
1063
- database.vpc_security_groups.each { |vpc_sg|
1064
- vpc_sg_ids << vpc_sg.vpc_security_group_id
1065
- }
1066
-
1067
- rds_sg_ids = Array.new
1068
- database.db_security_groups.each { |rds_sg|
1069
- rds_sg_ids << rds_sg.db_security_group_name
1070
- }
1071
-
1072
- subnet_ids = []
1073
- if database.db_subnet_group and database.db_subnet_group.subnets
1074
- database.db_subnet_group.subnets.each { |subnet|
1075
- subnet_ids << subnet.subnet_identifier
1076
- }
1077
- end
1078
-
1079
- {
1080
- "identifier" => database.db_instance_identifier,
1081
- "region" => db['region'],
1082
- "engine" => database.engine,
1083
- "engine_version" => database.engine_version,
1084
- "backup_retention_period" => database.backup_retention_period,
1085
- "preferred_backup_window" => database.preferred_backup_window,
1086
- "preferred_maintenance_window" => database.preferred_maintenance_window,
1087
- "auto_minor_version_upgrade" => database.auto_minor_version_upgrade,
1088
- "storage_encrypted" => database.storage_encrypted,
1089
- "endpoint" => database.endpoint.address,
1090
- "port" => database.endpoint.port,
1091
- "username" => database.master_username,
1092
- "rds_sgs" => rds_sg_ids,
1093
- "vpc_sgs" => vpc_sg_ids,
1094
- "az" => database.availability_zone,
1095
- "vault_name" => database.db_instance_identifier.upcase,
1096
- "vault_item" => "database_credentials",
1097
- "password_field" => "password",
1098
- "create_style" => db['creation_style'],
1099
- "db_name" => database.db_name,
1100
- "multi_az" => database.multi_az,
1101
- "publicly_accessible" => database.publicly_accessible,
1102
- "ca_certificate_identifier" => database.ca_certificate_identifier,
1103
- "subnets" => subnet_ids,
1104
- "read_replica_source_db" => database.read_replica_source_db_instance_identifier,
1105
- "read_replica_instance_identifiers" => database.read_replica_db_instance_identifiers,
1106
- "cluster_identifier" => database.db_cluster_identifier,
1107
- "size" => database.db_instance_class,
1108
- "storage" => database.allocated_storage
1109
- }
1110
- end
1111
- MU.log "Deploy structure is now #{deploy_struct}", MU::DEBUG
1112
- }
1113
-
1114
- raise MuError, "Can't find any deployment metadata" if deploy_struct.empty?
1115
- return deploy_struct
1116
- end
1117
-
1118
- # Generate a snapshot from the database described in this instance.
1119
- # @return [String]: The cloud provider's identifier for the snapshot.
1120
- def createNewSnapshot
1121
- snap_id = @deploy.getResourceName(@config["name"]) + Time.new.strftime("%M%S").to_s
1122
-
1123
- attempts = 0
1124
- begin
1125
- if @config["create_cluster"]
1126
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_cluster_snapshot(
1127
- db_cluster_snapshot_identifier: snap_id,
1128
- db_cluster_identifier: @config["identifier"],
1129
- tags: allTags
1130
- )
1131
- else
1132
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).create_db_snapshot(
1133
- db_snapshot_identifier: snap_id,
1134
- db_instance_identifier: @config["identifier"],
1135
- tags: allTags
1136
- )
1137
- end
1138
- rescue Aws::RDS::Errors::InvalidDBInstanceState, Aws::RDS::Errors::InvalidDBClusterStateFault => e
1139
- raise MuError, e.inspect if attempts >= 10
1140
- attempts += 1
1141
- sleep 60
1142
- retry
1143
- end
1144
-
1145
- attempts = 0
1146
- loop do
1147
- MU.log "Waiting for RDS snapshot of #{@config["identifier"]} to be ready...", MU::NOTICE if attempts % 20 == 0
1148
- MU.log "Waiting for RDS snapshot of #{@config["identifier"]} to be ready...", MU::DEBUG
1149
- snapshot_resp =
1150
- if @config["create_cluster"]
1151
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).describe_db_cluster_snapshots(db_cluster_snapshot_identifier: snap_id)
1152
- else
1153
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).describe_db_snapshots(db_snapshot_identifier: snap_id)
1154
- end
1155
-
1156
- if @config["create_cluster"]
1157
- break unless snapshot_resp.db_cluster_snapshots.first.status != "available"
1158
- else
1159
- break unless snapshot_resp.db_snapshots.first.status != "available"
1160
- end
1161
- attempts += 1
1162
- sleep 15
1163
- end
1164
-
1165
- return snap_id
1166
- end
1167
-
1168
- # Fetch the latest snapshot of the database described in this instance.
1169
- # @return [String]: The cloud provider's identifier for the snapshot.
1170
- def getExistingSnapshot
1171
- resp =
1172
- if @config["create_cluster"]
1173
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).describe_db_cluster_snapshots(db_cluster_snapshot_identifier: @config["identifier"])
1174
- else
1175
- MU::Cloud::AWS.rds(region: @config['region'], credentials: @config['credentials']).describe_db_snapshots(db_snapshot_identifier: @config["identifier"])
1176
- end
1177
-
1178
- snapshots = @config["create_cluster"] ? resp.db_cluster_snapshots : resp.db_snapshots
1179
-
1180
- if snapshots.empty?
1181
- nil
1182
- else
1183
- sorted_snapshots = snapshots.sort_by { |snap| snap.snapshot_create_time }
1184
- @config["create_cluster"] ? sorted_snapshots.last.db_cluster_snapshot_identifier : sorted_snapshots.last.db_snapshot_identifier
1185
- end
1186
- end
1187
-
1188
- # Does this resource type exist as a global (cloud-wide) artifact, or
1189
- # is it localized to a region/zone?
1190
- # @return [Boolean]
1191
- def self.isGlobal?
1192
- false
1193
- end
1194
-
1195
- # Denote whether this resource implementation is experiment, ready for
1196
- # testing, or ready for production use.
1197
- def self.quality
1198
- MU::Cloud::RELEASE
1199
- end
1200
-
1201
- # Called by {MU::Cleanup}. Locates resources that were created by the
1202
- # currently-loaded deployment, and purges them.
1203
- # @param noop [Boolean]: If true, will only print what would be done
1204
- # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
1205
- # @param region [String]: The cloud provider region in which to operate
1206
- # @return [void]
1207
- def self.cleanup(noop: false, ignoremaster: false, credentials: nil, region: MU.curRegion, flags: {})
1208
- skipsnapshots = flags["skipsnapshots"]
1209
-
1210
- resp = MU::Cloud::AWS.rds(credentials: credentials, region: region).describe_db_instances
1211
- threads = []
1212
-
1213
- resp.db_instances.each { |db|
1214
- arn = MU::Cloud::AWS::Database.getARN(db.db_instance_identifier, "db", "rds", region: region, credentials: credentials)
1215
- tags = MU::Cloud::AWS.rds(credentials: credentials, region: region).list_tags_for_resource(resource_name: arn).tag_list
1216
-
1217
- found_muid = false
1218
- found_master = false
1219
- tags.each { |tag|
1220
- found_muid = true if tag.key == "MU-ID" && tag.value == MU.deploy_id
1221
- found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
1222
- }
1223
- next if !found_muid
1224
-
1225
- delete =
1226
- if ignoremaster && found_muid
1227
- true
1228
- elsif !ignoremaster && found_muid && found_master
1229
- true
1230
- else
1231
- false
1232
- end
1233
-
1234
- if delete
1235
- parent_thread_id = Thread.current.object_id
1236
- threads << Thread.new(db) { |mydb|
1237
- MU.dupGlobals(parent_thread_id)
1238
- Thread.abort_on_exception = true
1239
- terminate_rds_instance(mydb, noop: noop, skipsnapshots: skipsnapshots, region: region, deploy_id: MU.deploy_id, cloud_id: db.db_instance_identifier, mu_name: db.db_instance_identifier.upcase, credentials: credentials)
1240
- }
1241
- end
1242
- }
1243
-
1244
- # Wait for all of the databases to finish cleanup before proceeding
1245
- threads.each { |t|
1246
- t.join
1247
- }
1248
-
1249
- # Cleanup database clusters
1250
- threads = []
1251
- resp = MU::Cloud::AWS.rds(credentials: credentials, region: region).describe_db_clusters
1252
- resp.db_clusters.each { |cluster|
1253
- cluster_id = cluster.db_cluster_identifier
1254
- arn = MU::Cloud::AWS::Database.getARN(cluster_id, "cluster", "rds", region: region, credentials: credentials)
1255
- tags = MU::Cloud::AWS.rds(credentials: credentials, region: region).list_tags_for_resource(resource_name: arn).tag_list
1256
-
1257
- found_muid = false
1258
- found_master = false
1259
- tags.each { |tag|
1260
- found_muid = true if tag.key == "MU-ID" && tag.value == MU.deploy_id
1261
- found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
1262
- }
1263
- next if !found_muid
1264
-
1265
- delete =
1266
- if ignoremaster && found_muid
1267
- true
1268
- elsif !ignoremaster && found_muid && found_master
1269
- true
1270
- else
1271
- false
1272
- end
1273
-
1274
- if delete
1275
- parent_thread_id = Thread.current.object_id
1276
- threads << Thread.new(cluster) { |mydbcluster|
1277
- MU.dupGlobals(parent_thread_id)
1278
- Thread.abort_on_exception = true
1279
- terminate_rds_cluster(mydbcluster, noop: noop, skipsnapshots: skipsnapshots, region: region, deploy_id: MU.deploy_id, cloud_id: cluster_id, mu_name: cluster_id.upcase, credentials: credentials)
1280
- }
1281
- end
1282
- }
1283
-
1284
- # Wait for all of the database clusters to finish cleanup before proceeding
1285
- threads.each { |t|
1286
- t.join
1287
- }
1288
-
1289
- threads = []
1290
- # Cleanup database subnet group
1291
- MU::Cloud::AWS.rds(credentials: credentials, region: region).describe_db_subnet_groups.db_subnet_groups.each { |sub_group|
1292
- sub_group_id = sub_group.db_subnet_group_name
1293
- arn = MU::Cloud::AWS::Database.getARN(sub_group_id, "subgrp", "rds", region: region, credentials: credentials)
1294
- tags = MU::Cloud::AWS.rds(credentials: credentials, region: region).list_tags_for_resource(resource_name: arn).tag_list
1295
-
1296
- found_muid = false
1297
- found_master = false
1298
- tags.each { |tag|
1299
- found_muid = true if tag.key == "MU-ID" && tag.value == MU.deploy_id
1300
- found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
1301
- }
1302
- next if !found_muid
1303
-
1304
- delete =
1305
- if ignoremaster && found_muid
1306
- true
1307
- elsif !ignoremaster && found_muid && found_master
1308
- true
1309
- else
1310
- false
1311
- end
1312
-
1313
- if delete
1314
- parent_thread_id = Thread.current.object_id
1315
- threads << Thread.new(sub_group_id) { |mysubgroup|
1316
- MU.dupGlobals(parent_thread_id)
1317
- Thread.abort_on_exception = true
1318
- delete_subnet_group(mysubgroup, region: region) unless noop
1319
- }
1320
- end
1321
- }
1322
-
1323
- # Cleanup database parameter group
1324
- MU::Cloud::AWS.rds(credentials: credentials, region: region).describe_db_parameter_groups.db_parameter_groups.each { |param_group|
1325
- param_group_id = param_group.db_parameter_group_name
1326
- arn = MU::Cloud::AWS::Database.getARN(param_group_id, "pg", "rds", region: region, credentials: credentials)
1327
- tags = MU::Cloud::AWS.rds(credentials: credentials, region: region).list_tags_for_resource(resource_name: arn).tag_list
1328
-
1329
- found_muid = false
1330
- found_master = false
1331
- tags.each { |tag|
1332
- found_muid = true if tag.key == "MU-ID" && tag.value == MU.deploy_id
1333
- found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
1334
- }
1335
- next if !found_muid
1336
-
1337
- delete =
1338
- if ignoremaster && found_muid
1339
- true
1340
- elsif !ignoremaster && found_muid && found_master
1341
- true
1342
- else
1343
- false
1344
- end
1345
-
1346
- if delete
1347
- parent_thread_id = Thread.current.object_id
1348
- threads << Thread.new(param_group_id) { |myparamgroup|
1349
- MU.dupGlobals(parent_thread_id)
1350
- Thread.abort_on_exception = true
1351
- delete_db_parameter_group(myparamgroup, region: region) unless noop
1352
- }
1353
- end
1354
- }
1355
-
1356
- # Cleanup database cluster parameter group
1357
- MU::Cloud::AWS.rds(credentials: credentials, region: region).describe_db_cluster_parameter_groups.db_cluster_parameter_groups.each { |param_group|
1358
- param_group_id = param_group.db_cluster_parameter_group_name
1359
- arn = MU::Cloud::AWS::Database.getARN(param_group_id, "cluster-pg", "rds", region: region, credentials: credentials)
1360
- tags = MU::Cloud::AWS.rds(credentials: credentials, region: region).list_tags_for_resource(resource_name: arn).tag_list
1361
-
1362
- found_muid = false
1363
- found_master = false
1364
- tags.each { |tag|
1365
- found_muid = true if tag.key == "MU-ID" && tag.value == MU.deploy_id
1366
- found_master = true if tag.key == "MU-MASTER-IP" && tag.value == MU.mu_public_ip
1367
- }
1368
- next if !found_muid
1369
-
1370
- delete =
1371
- if ignoremaster && found_muid
1372
- true
1373
- elsif !ignoremaster && found_muid && found_master
1374
- true
1375
- else
1376
- false
1377
- end
1378
-
1379
- if delete
1380
- parent_thread_id = Thread.current.object_id
1381
- threads << Thread.new(param_group_id) { |myparamgroup|
1382
- MU.dupGlobals(parent_thread_id)
1383
- Thread.abort_on_exception = true
1384
- delete_db_cluster_parameter_group(myparamgroup, region: region) unless noop
1385
- }
1386
- end
1387
- }
1388
-
1389
- # Wait for all of the databases subnet/parameter groups to finish cleanup before proceeding
1390
- threads.each { |t|
1391
- t.join
1392
- }
1393
- end
1394
-
1395
- # Cloud-specific configuration properties.
1396
- # @param _config [MU::Config]: The calling MU::Config object
1397
- # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
1398
- def self.schema(_config)
1399
- toplevel_required = []
1400
- rds_parameters_primitive = {
1401
- "type" => "array",
1402
- "minItems" => 1,
1403
- "items" => {
1404
- "description" => "The database parameter group parameter to change and when to apply the change.",
1405
- "type" => "object",
1406
- "title" => "Database Parameter",
1407
- "required" => ["name", "value"],
1408
- "additionalProperties" => false,
1409
- "properties" => {
1410
- "name" => {
1411
- "type" => "string"
1412
- },
1413
- "value" => {
1414
- "type" => "string"
1415
- },
1416
- "apply_method" => {
1417
- "enum" => ["pending-reboot", "immediate"],
1418
- "default" => "immediate",
1419
- "type" => "string"
1420
- }
1421
- }
1422
- }
1423
- }
1424
-
1425
-
1426
- schema = {
1427
- "db_parameter_group_parameters" => rds_parameters_primitive,
1428
- "cluster_parameter_group_parameters" => rds_parameters_primitive,
1429
- "parameter_group_family" => {
1430
- "type" => "String",
1431
- "description" => "An RDS parameter group family. See also https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithParamGroups.html"
1432
- },
1433
- "cluster_mode" => {
1434
- "type" => "string",
1435
- "description" => "The DB engine mode of the DB cluster",
1436
- "enum" => ["provisioned", "serverless", "parallelquery", "global"],
1437
- "default" => "provisioned"
1438
- },
1439
- "cloudwatch_logs" => {
1440
- "type" => "array",
1441
- "default" => ["error"],
1442
- "items" => {
1443
- "type" => "string",
1444
- "enum" => ["error", "general", "audit", "slow_query"],
1445
- }
1446
- },
1447
- "serverless_scaling" => {
1448
- "type" => "object",
1449
- "description" => "Scaling configuration for a +serverless+ Aurora cluster",
1450
- "default" => {
1451
- "auto_pause" => false,
1452
- "min_capacity" => 2,
1453
- "max_capacity" => 2
1454
- },
1455
- "properties" => {
1456
- "auto_pause" => {
1457
- "type" => "boolean",
1458
- "description" => "A value that specifies whether to allow or disallow automatic pause for an Aurora DB cluster in serverless DB engine mode",
1459
- "default" => false
1460
- },
1461
- "min_capacity" => {
1462
- "type" => "integer",
1463
- "description" => "The minimum capacity for an Aurora DB cluster in serverless DB engine mode.",
1464
- "default" => 2,
1465
- "enum" => [2, 4, 8, 16, 32, 64, 128, 256]
1466
- },
1467
- "max_capacity" => {
1468
- "type" => "integer",
1469
- "description" => "The maximum capacity for an Aurora DB cluster in serverless DB engine mode.",
1470
- "default" => 2,
1471
- "enum" => [2, 4, 8, 16, 32, 64, 128, 256]
1472
- },
1473
- "seconds_until_auto_pause" => {
1474
- "type" => "integer",
1475
- "description" => "A DB cluster can be paused only when it's idle (it has no connections). If a DB cluster is paused for more than seven days, the DB cluster might be backed up with a snapshot. In this case, the DB cluster is restored when there is a request to connect to it.",
1476
- "default" => 86400
1477
- }
1478
- }
1479
- },
1480
- "license_model" => {
1481
- "type" => "string",
1482
- "enum" => ["license-included", "bring-your-own-license", "general-public-license", "postgresql-license"]
1483
- },
1484
- "ingress_rules" => {
1485
- "items" => {
1486
- "properties" => {
1487
- "sgs" => {
1488
- "type" => "array",
1489
- "items" => {
1490
- "description" => "Other AWS Security Groups; resources that are associated with this group will have this rule applied to their traffic",
1491
- "type" => "string"
1492
- }
1493
- },
1494
- "lbs" => {
1495
- "type" => "array",
1496
- "items" => {
1497
- "description" => "AWS Load Balancers which will have this rule applied to their traffic",
1498
- "type" => "string"
1499
- }
1500
- }
1501
- }
1502
- }
1503
- }
1504
- }
1505
- [toplevel_required, schema]
1506
- end
1507
-
1508
- # Cloud-specific pre-processing of {MU::Config::BasketofKittens::databases}, bare and unvalidated.
1509
- # @param db [Hash]: The resource to process and validate
1510
- # @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
1511
- # @return [Boolean]: True if validation succeeded, False otherwise
1512
- def self.validateConfig(db, _configurator)
1513
- ok = true
1514
-
1515
- if db['creation_style'] == "existing_snapshot" and
1516
- !db['create_cluster'] and
1517
- db['identifier'] and db['identifier'].match(/:cluster-snapshot:/)
1518
- MU.log "Database #{db['name']}: Existing snapshot #{db['identifier']} looks like a cluster snapshot, but create_cluster is not set. Add 'create_cluster: true' if you're building an RDS cluster.", MU::ERR
1519
- ok = false
1520
- end
1521
-
1522
- pgroup_families = []
1523
- engines = {}
1524
-
1525
- marker = nil
1526
- begin
1527
- resp = MU::Cloud::AWS.rds(credentials: db['credentials'], region: db['region']).describe_db_engine_versions(marker: marker)
1528
- marker = resp.marker
1529
-
1530
- if resp and resp.db_engine_versions
1531
- resp.db_engine_versions.each { |version|
1532
- engines[version.engine] ||= {
1533
- "versions" => [],
1534
- "families" => []
1535
- }
1536
- engines[version.engine]['versions'] << version.engine_version
1537
- engines[version.engine]['families'] << version.db_parameter_group_family
1538
-
1539
- }
1540
- engines.keys.each { |engine|
1541
- engines[engine]["versions"].uniq!
1542
- engines[engine]["families"].uniq!
1543
- }
1544
-
1545
- else
1546
- MU.log "Failed to get list of valid RDS engine versions in #{db['region']}, proceeding without proper validation", MU::WARN
1547
- end
1548
- end while !marker.nil?
1549
-
1550
- if db['create_cluster'] or db['engine'] == "aurora" or db["member_of_cluster"]
1551
- case db['engine']
1552
- when "mysql", "aurora", "aurora-mysql"
1553
- if db["engine_version"].match(/^5\.6/) or db["cluster_mode"] == "serverless"
1554
- db["engine"] = "aurora"
1555
- else
1556
- db["engine"] = "aurora-mysql"
1557
- end
1558
- when "postgres", "postgresql", "postgresql-mysql"
1559
- db["engine"] = "aurora-postgresql"
1560
- else
1561
- ok = false
1562
- MU.log "Database #{db['name']}: Requested a clustered database, but engine #{db['engine']} is not supported for clustering", MU::ERR
1563
- end
1564
- end
1565
-
1566
- if db['engine'] == "aurora-postgresql"
1567
- db.delete('cloudwatch_logs')
1568
- end
1569
-
1570
- if db['engine'].match(/^aurora/) and !db['create_cluster'] and !db['add_cluster_node']
1571
- MU.log "Database #{db['name']}: #{db['engine']} looks like a cluster engine, but create_cluster is not set. Add 'create_cluster: true' if you're building an RDS cluster.", MU::ERR
1572
- ok = false
1573
- end
1574
-
1575
- if engines.size > 0
1576
- if !engines[db['engine']]
1577
- MU.log "RDS engine #{db['engine']} is not supported in #{db['region']}", MU::ERR, details: engines.keys.sort
1578
- ok = false
1579
- else
1580
- if db["engine_version"] and
1581
- engines[db['engine']]['versions'].size > 0 and
1582
- !engines[db['engine']]['versions'].include?(db['engine_version']) and
1583
- !engines[db['engine']]['versions'].grep(/^#{Regexp.quote(db["engine_version"])}.+/)
1584
- MU.log "RDS engine '#{db['engine']}' version '#{db['engine_version']}' is not supported in #{db['region']}", MU::ERR, details: { "Known-good versions:" => engines[db['engine']]['versions'].uniq.sort }
1585
- ok = false
1586
- end
1587
- if db["parameter_group_family"] and
1588
- engines[db['engine']]['families'].size > 0 and
1589
- !engines[db['engine']]['families'].include?(db['parameter_group_family'])
1590
- MU.log "RDS engine '#{db['engine']}' parameter group family '#{db['parameter_group_family']}' is not supported in #{db['region']}", MU::ERR, details: { "Valid parameter families:" => engines[db['engine']]['families'].uniq.sort }
1591
- ok = false
1592
- end
1593
- end
1594
- end
1595
-
1596
- if db['parameter_group_family'] and pgroup_families.size > 0 and
1597
- !pgroup_families.include?(db['parameter_group_family'])
1598
- end
1599
-
1600
- db["license_model"] ||=
1601
- if ["postgres", "postgresql", "aurora-postgresql"].include?(db["engine"])
1602
- "postgresql-license"
1603
- elsif ["mysql", "mariadb"].include?(db["engine"])
1604
- "general-public-license"
1605
- else
1606
- "license-included"
1607
- end
1608
-
1609
- if db["create_read_replica"] or db['read_replica_of']
1610
- if !["postgres", "postgresql", "mysql", "aurora-mysql", "aurora-postgresql", "mariadb"].include?(db["engine"])
1611
- MU.log "Read replica(s) database instances not supported for #{db["engine"]}.", MU::ERR
1612
- ok = false
1613
- end
1614
- end
1615
-
1616
- if db["creation_style"] == "existing"
1617
- begin
1618
- MU::Cloud::AWS.rds(region: db['region']).describe_db_instances(
1619
- db_instance_identifier: db['identifier']
1620
- )
1621
- rescue Aws::RDS::Errors::DBInstanceNotFound
1622
- MU.log "Source database #{db['identifier']} was specified for #{db['name']}, but no such database exists in #{db['region']}", MU::ERR
1623
- ok = false
1624
- end
1625
- end
1626
-
1627
- if !db['password'].nil? and (db['password'].length < 8 or db['password'].match(/[\/\\@\s]/))
1628
- MU.log "Database password '#{db['password']}' doesn't meet RDS requirements. Must be > 8 chars and have only ASCII characters other than /, @, \", or [space].", MU::ERR
1629
- ok = false
1630
- end
1631
- if db["multi_az_on_create"] and db["multi_az_on_deploy"]
1632
- MU.log "Both of multi_az_on_create and multi_az_on_deploy cannot be true", MU::ERR
1633
- ok = false
1634
- end
1635
- if db.has_key?("db_parameter_group_parameters") || db.has_key?("cluster_parameter_group_parameters")
1636
- if db["parameter_group_family"].nil?
1637
- MU.log "parameter_group_family must be set when setting db_parameter_group_parameters", MU::ERR
1638
- ok = false
1639
- end
1640
- end
1641
- # Adding rules for Database instance storage. This varies depending on storage type and database type.
1642
- if !db["storage"].nil? and (db["storage_type"] == "standard" or db["storage_type"] == "gp2")
1643
- if db["engine"] == "postgres" or db["engine"] == "mysql"
1644
- if !(5..6144).include? db["storage"]
1645
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 5 to 6144 GB for #{db["storage_type"]} volume types", MU::ERR
1646
- ok = false
1647
- end
1648
- elsif %w{oracle-se1 oracle-se oracle-ee}.include? db["engine"]
1649
- if !(10..6144).include? db["storage"]
1650
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 10 to 6144 GB for #{db["storage_type"]} volume types", MU::ERR
1651
- ok = false
1652
- end
1653
- elsif %w{sqlserver-ex sqlserver-web}.include? db["engine"]
1654
- if !(20..4096).include? db["storage"]
1655
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 20 to 4096 GB for #{db["storage_type"]} volume types", MU::ERR
1656
- ok = false
1657
- end
1658
- elsif %w{sqlserver-ee sqlserver-se}.include? db["engine"]
1659
- if !(200..4096).include? db["storage"]
1660
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 200 to 4096 GB for #{db["storage_type"]} volume types", MU::ERR
1661
- ok = false
1662
- end
1663
- end
1664
- elsif db["storage_type"] == "io1"
1665
- if %w{postgres mysql oracle-se1 oracle-se oracle-ee}.include? db["engine"]
1666
- if !(100..6144).include? db["storage"]
1667
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 100 to 6144 GB for #{db["storage_type"]} volume types", MU::ERR
1668
- ok = false
1669
- end
1670
- elsif %w{sqlserver-ex sqlserver-web}.include? db["engine"]
1671
- if !(100..4096).include? db["storage"]
1672
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 100 to 4096 GB for #{db["storage_type"]} volume types", MU::ERR
1673
- ok = false
1674
- end
1675
- elsif %w{sqlserver-ee sqlserver-se}.include? db["engine"]
1676
- if !(200..4096).include? db["storage"]
1677
- MU.log "Database storage size is set to #{db["storage"]}. #{db["engine"]} only supports storage sizes between 200 to 4096 GB #{db["storage_type"]} volume types", MU::ERR
1678
- ok = false
1679
- end
1680
- end
1681
- end
1682
-
1683
- if db["vpc"]
1684
- if db["vpc"]["subnet_pref"] == "all_public" and !db['publicly_accessible'] and (db["vpc"]['subnets'].nil? or db["vpc"]['subnets'].empty?)
1685
- MU.log "Setting publicly_accessible to true on database '#{db['name']}', since deploying into public subnets.", MU::WARN
1686
- db['publicly_accessible'] = true
1687
- elsif db["vpc"]["subnet_pref"] == "all_private" and db['publicly_accessible']
1688
- MU.log "Setting publicly_accessible to false on database '#{db['name']}', since deploying into private subnets.", MU::NOTICE
1689
- db['publicly_accessible'] = false
1690
- end
1691
- end
1692
-
1693
- ok
1694
- end
1695
-
1696
- private
1697
-
1698
- # Remove an RDS database and associated artifacts
1699
- # @param db [OpenStruct]: The cloud provider's description of the database artifact
1700
- # @return [void]
1701
- def self.terminate_rds_instance(db, noop: false, skipsnapshots: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil, cloud_id: nil, credentials: nil)
1702
- raise MuError, "terminate_rds_instance requires a non-nil database descriptor" if db.nil?
1703
- db_id = db.db_instance_identifier
1704
-
1705
- database_obj = MU::MommaCat.findStray(
1706
- "AWS",
1707
- "database",
1708
- region: region,
1709
- deploy_id: deploy_id,
1710
- cloud_id: cloud_id,
1711
- mu_name: mu_name
1712
- ).first
1713
-
1714
- rdssecgroups = Array.new
1715
- begin
1716
- secgroup = MU::Cloud::AWS.rds(region: region).describe_db_security_groups(db_security_group_name: db_id)
1717
- rescue Aws::RDS::Errors::DBSecurityGroupNotFound
1718
- # this is normal in VPC world
1719
- end
1720
-
1721
- rdssecgroups << db_id if !secgroup.nil?
1722
-
1723
- # We can use an AWS waiter for this.
1724
- unless db.db_instance_status == "available"
1725
- loop do
1726
- MU.log "Waiting for #{db_id} to be in a removable state...", MU::NOTICE
1727
- db = MU::Cloud::AWS::Database.getDatabaseById(db_id, region: region)
1728
- return if db.nil?
1729
- break unless %w{creating modifying backing-up}.include?(db.db_instance_status)
1730
- sleep 60
1731
- end
1732
- end
1733
-
1734
- MU::Cloud::AWS::DNSZone.genericMuDNSEntry(name: db_id, target: db.endpoint.address, cloudclass: MU::Cloud::Database, delete: true)
1735
-
1736
- if %w{deleting deleted}.include?(db.db_instance_status)
1737
- MU.log "#{db_id} has already been terminated", MU::WARN
1738
- else
1739
- def self.dbSkipSnap(db_id, region, credentials)
1740
- # We're calling this several times so lets declare it once
1741
- MU.log "Terminating #{db_id} (not saving final snapshot)"
1742
- MU::Cloud::AWS.rds(region: region, credentials: credentials).delete_db_instance(db_instance_identifier: db_id, skip_final_snapshot: true)
1743
- end
1744
-
1745
- def self.dbCreateSnap(db_id, region, credentials)
1746
- MU.log "Terminating #{db_id} (final snapshot: #{db_id}-mufinal)"
1747
- MU::Cloud::AWS.rds(region: region, credentials: credentials).delete_db_instance(db_instance_identifier: db_id, final_db_snapshot_identifier: "#{db_id}-mufinal", skip_final_snapshot: false)
1748
- end
1749
-
1750
- if !noop
1751
- retries = 0
1752
- begin
1753
- if db.db_cluster_identifier || db.read_replica_source_db_instance_identifier
1754
- # make sure we don't create final snapshot for a database instance that is part of a cluster, or if it's a read replica database instance
1755
- dbSkipSnap(db_id, region, credentials)
1756
- else
1757
- skipsnapshots ? dbSkipSnap(db_id, region, credentials) : dbCreateSnap(db_id, region, credentials)
1758
- end
1759
- rescue Aws::RDS::Errors::InvalidDBInstanceState => e
1760
- if retries < 5
1761
- MU.log "#{db_id} is not in a removable state, retrying several times #{e.inspect}", MU::WARN
1762
- retries += 1
1763
- sleep 30
1764
- retry
1765
- else
1766
- MU.log "#{db_id} is not in a removable state after several retries, giving up. #{e.inspect}", MU::ERR
1767
- end
1768
- rescue Aws::RDS::Errors::DBSnapshotAlreadyExists
1769
- dbSkipSnap(db_id, region, credentials)
1770
- MU.log "Snapshot of #{db_id} already exists", MU::WARN
1771
- rescue Aws::RDS::Errors::SnapshotQuotaExceeded
1772
- dbSkipSnap(db_id, region, credentials)
1773
- MU.log "Snapshot quota exceeded while deleting #{db_id}", MU::ERR
1774
- end
1775
- end
1776
- end
1777
-
1778
- begin
1779
- attempts = 0
1780
- loop do
1781
- MU.log "Waiting for #{db_id} termination to complete", MU::NOTICE if attempts % 6 == 0
1782
- del_db = MU::Cloud::AWS::Database.getDatabaseById(db_id, region: region)
1783
- break if del_db.nil? || del_db.db_instance_status == "deleted"
1784
- sleep 10
1785
- attempts += 1
1786
- end
1787
- rescue Aws::RDS::Errors::DBInstanceNotFound
1788
- # we are ok with this
1789
- end
1790
-
1791
- # RDS security groups can depend on EC2 security groups, do these last
1792
- begin
1793
- rdssecgroups.each { |sg|
1794
- MU.log "Removing RDS Security Group #{sg}"
1795
- MU::Cloud::AWS.rds(region: region).delete_db_security_group(db_security_group_name: sg) if !noop
1796
- }
1797
- rescue Aws::RDS::Errors::DBSecurityGroupNotFound
1798
- MU.log "RDS Security Group #{sg} disappeared before we could remove it", MU::WARN
1799
- end
1800
-
1801
- # Cleanup the database vault
1802
- groomer =
1803
- if database_obj
1804
- database_obj.config.has_key?("groomer") ? database_obj.config["groomer"] : MU::Config.defaultGroomer
1805
- else
1806
- MU::Config.defaultGroomer
1807
- end
1808
-
1809
- groomclass = MU::Groomer.loadGroomer(groomer)
1810
- groomclass.deleteSecret(vault: db_id.upcase) if !noop
1811
- MU.log "#{db_id} has been terminated"
1812
- end
1813
- private_class_method :terminate_rds_instance
1814
-
1815
- # Remove an RDS database cluster and associated artifacts
1816
- # @param cluster [OpenStruct]: The cloud provider's description of the database artifact
1817
- # @return [void]
1818
- def self.terminate_rds_cluster(cluster, noop: false, skipsnapshots: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil, cloud_id: nil, credentials: nil)
1819
- raise MuError, "terminate_rds_cluster requires a non-nil database cluster descriptor" if cluster.nil?
1820
- cluster_id = cluster.db_cluster_identifier
1821
-
1822
- cluster_obj = MU::MommaCat.findStray(
1823
- "AWS",
1824
- "database",
1825
- region: region,
1826
- deploy_id: deploy_id,
1827
- cloud_id: cloud_id,
1828
- credentials: credentials,
1829
- mu_name: mu_name
1830
- ).first
1831
-
1832
- # We can use an AWS waiter for this.
1833
- unless cluster.status == "available"
1834
- loop do
1835
- MU.log "Waiting for #{cluster_id} to be in a removable state...", MU::NOTICE
1836
- cluster = MU::Cloud::AWS::Database.getDatabaseClusterById(cluster_id, region: region, credentials: credentials)
1837
- break unless %w{creating modifying backing-up}.include?(cluster.status)
1838
- sleep 60
1839
- end
1840
- end
1841
-
1842
- MU::Cloud::AWS::DNSZone.genericMuDNSEntry(name: cluster_id, target: cluster.endpoint, cloudclass: MU::Cloud::Database, delete: true)
1843
-
1844
- if %w{deleting deleted}.include?(cluster.status)
1845
- MU.log "#{cluster_id} has already been terminated", MU::WARN
1846
- else
1847
- unless noop
1848
- def self.clusterSkipSnap(cluster_id, region, credentials)
1849
- # We're calling this several times so lets declare it once
1850
- MU.log "Terminating #{cluster_id}. Not saving final snapshot"
1851
- MU::Cloud::AWS.rds(region: region, credentials: credentials).delete_db_cluster(db_cluster_identifier: cluster_id, skip_final_snapshot: true)
1852
- end
1853
-
1854
- def self.clusterCreateSnap(cluster_id, region, credentials)
1855
- MU.log "Terminating #{cluster_id}. Saving final snapshot: #{cluster_id}-mufinal"
1856
- MU::Cloud::AWS.rds(region: region, credentials: credentials).delete_db_cluster(db_cluster_identifier: cluster_id, skip_final_snapshot: false, final_db_snapshot_identifier: "#{cluster_id}-mufinal")
1857
- end
1858
-
1859
- retries = 0
1860
- begin
1861
- skipsnapshots ? clusterSkipSnap(cluster_id, region, credentials) : clusterCreateSnap(cluster_id, region, credentials)
1862
- rescue Aws::RDS::Errors::InvalidDBClusterStateFault => e
1863
- if retries < 5
1864
- MU.log "#{cluster_id} is not in a removable state, retrying several times", MU::WARN
1865
- retries += 1
1866
- sleep 30
1867
- retry
1868
- else
1869
- MU.log "#{cluster_id} is not in a removable state after several retries, giving up. #{e.inspect}", MU::ERR
1870
- end
1871
- rescue Aws::RDS::Errors::DBClusterSnapshotAlreadyExistsFault
1872
- clusterSkipSnap(cluster_id, region, credentials)
1873
- MU.log "Snapshot of #{cluster_id} already exists", MU::WARN
1874
- rescue Aws::RDS::Errors::DBClusterQuotaExceeded
1875
- clusterSkipSnap(cluster_id, region, credentials)
1876
- MU.log "Snapshot quota exceeded while deleting #{cluster_id}", MU::ERR
1877
- end
1878
- end
1879
- end
1880
-
1881
- # We're wating until getDatabaseClusterById returns nil. This assumes the database cluster object doesn't linger around in "deleted" state for a while.
1882
- loop do
1883
- MU.log "Waiting for #{cluster_id} to terminate", MU::NOTICE
1884
- cluster = MU::Cloud::AWS::Database.getDatabaseClusterById(cluster_id, region: region, credentials: credentials)
1885
- break unless cluster
1886
- sleep 30
1887
- end
1888
-
1889
- # Cleanup the cluster vault
1890
- groomer =
1891
- if cluster_obj
1892
- cluster_obj.config.has_key?("groomer") ? cluster_obj.config["groomer"] : MU::Config.defaultGroomer
1893
- else
1894
- MU::Config.defaultGroomer
1895
- end
1896
-
1897
- groomclass = MU::Groomer.loadGroomer(groomer)
1898
- groomclass.deleteSecret(vault: cluster_id.upcase) if !noop
1899
-
1900
- MU.log "#{cluster_id} has been terminated"
1901
- end
1902
- private_class_method :terminate_rds_cluster
1903
-
1904
- # Remove a database subnet group.
1905
- # @param subnet_group_id [string]: The cloud provider's ID of the database subnet group.
1906
- # @param region [String]: The cloud provider's region in which to operate.
1907
- # @return [void]
1908
- def self.delete_subnet_group(subnet_group_id, region: MU.curRegion)
1909
- retries ||= 0
1910
- MU.log "Deleting DB subnet group #{subnet_group_id}"
1911
- MU::Cloud::AWS.rds(region: region).delete_db_subnet_group(db_subnet_group_name: subnet_group_id)
1912
- rescue Aws::RDS::Errors::DBSubnetGroupNotFoundFault => e
1913
- MU.log "DB subnet group #{subnet_group_id} disappeared before we could remove it", MU::WARN
1914
- rescue Aws::RDS::Errors::InvalidDBSubnetGroupStateFault=> e
1915
- if retries < 5
1916
- MU.log "DB subnet group #{subnet_group_id} is not in a removable state, retrying", MU::WARN
1917
- retries += 1
1918
- sleep 30
1919
- retry
1920
- else
1921
- MU.log "#{subnet_group_id} is not in a removable state after several retries, giving up. #{e.inspect}", MU::ERR
1922
- end
1923
- end
1924
- private_class_method :delete_subnet_group
1925
-
1926
- # Remove a database parameter group.
1927
- # @param parameter_group_id [string]: The cloud provider's ID of the database parameter group.
1928
- # @param region [String]: The cloud provider's region in which to operate.
1929
- # @return [void]
1930
- def self.delete_db_parameter_group(parameter_group_id, region: MU.curRegion)
1931
- retries ||= 0
1932
- MU.log "Deleting DB parameter group #{parameter_group_id}"
1933
- MU::Cloud::AWS.rds(region: region).delete_db_parameter_group(db_parameter_group_name: parameter_group_id)
1934
- rescue Aws::RDS::Errors::DBParameterGroupNotFound
1935
- MU.log "DB parameter group #{parameter_group_id} disappeared before we could remove it", MU::WARN
1936
- rescue Aws::RDS::Errors::InvalidDBParameterGroupState => e
1937
- if retries < 5
1938
- MU.log "DB parameter group #{parameter_group_id} is not in a removable state, retrying", MU::WARN
1939
- retries += 1
1940
- sleep 30
1941
- retry
1942
- else
1943
- MU.log "DB parameter group #{parameter_group_id} is not in a removable state after several retries, giving up. #{e.inspect}", MU::ERR
1944
- end
1945
- end
1946
- private_class_method :delete_db_parameter_group
1947
-
1948
- # Remove a database cluster parameter group.
1949
- # @param parameter_group_id [string]: The cloud provider's ID of the database cluster parameter group.
1950
- # @param region [String]: The cloud provider's region in which to operate.
1951
- # @return [void]
1952
- def self.delete_db_cluster_parameter_group(parameter_group_id, region: MU.curRegion)
1953
- retries ||= 0
1954
- MU.log "Deleting cluster parameter group #{parameter_group_id}"
1955
- MU::Cloud::AWS.rds(region: region).delete_db_cluster_parameter_group(db_cluster_parameter_group_name: parameter_group_id)
1956
- # AWS API sucks. instead of returning the documented error DBClusterParameterGroupNotFoundFault it errors out with DBParameterGroupNotFound.
1957
- rescue Aws::RDS::Errors::DBParameterGroupNotFound
1958
- MU.log "Cluster parameter group #{parameter_group_id} disappeared before we could remove it", MU::WARN
1959
- rescue Aws::RDS::Errors::InvalidDBParameterGroupState => e
1960
- if retries < 5
1961
- MU.log "Cluster parameter group #{parameter_group_id} is not in a removable state, retrying", MU::WARN
1962
- retries += 1
1963
- sleep 30
1964
- retry
1965
- else
1966
- MU.log "Cluster parameter group #{parameter_group_id} is not in a removable state after several retries, giving up. #{e.inspect}", MU::ERR
1967
- end
1968
- end
1969
- private_class_method :delete_db_cluster_parameter_group
1970
-
1971
- end #class
1972
- end #class
1973
- end
1974
- end #module