cloud-mu 3.1.3 → 3.3.0

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