Class: Stax::Cmd::Rds

Inherits:
SubCommand show all
Defined in:
lib/stax/mixin/rds.rb

Constant Summary collapse

COLORS =
{
  available: :green,
  'in-sync': :green,
  Complete:  :green,
  Active:    :green,
  COMPLETED: :green,
  AVAILABLE: :green,
  SWITCHOVER_COMPLETED: :green,
}

Instance Method Summary collapse

Methods inherited from SubCommand

#info, stax_info, stax_info_tasks

Instance Method Details

#clustersObject



73
74
75
76
77
78
# File 'lib/stax/mixin/rds.rb', line 73

def clusters
  debug("RDS DB clusters for #{my.stack_name}")
  print_table stack_rds_clusters.map { |c|
    [c.db_cluster_identifier, c.engine, c.engine_version, color(c.status), c.cluster_create_time]
  }
end

#create_upgrade_candidateObject



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/stax/mixin/rds.rb', line 143

def create_upgrade_candidate
  target_engine_version = options[:target_engine_version]
  target_cluster_parameter_group = options[:target_cluster_parameter_group]
  target_instance_parameter_group = options[:target_instance_parameter_group]
  target_db_instance_class = options[:target_db_instance_class]

  unless stack_rds_clusters.is_a?(Array) && stack_rds_clusters.any?
    say("No DB clusters associated with #{my.stack_name}", :red)
    return
  end        

  # get cluster source_arn from stack
  if stack_rds_clusters.length == 1
    source_arn = stack_rds_clusters[0].db_cluster_arn
  else
    say("Multiple DB Clusters associated with #{my.stack_name}. Cannot determine which cluster to use as source.", :red)
    return
  end

  # Specify the required blue/green deployment parameters
  deployment_params = {
    blue_green_deployment_name: "#{my.stack_name}-next-#{SecureRandom.alphanumeric(12)}",
    source: source_arn,
  }

  # Check if optional values are set and add them to the deployment parameters
  if !target_engine_version.empty?
    deployment_params[:target_engine_version] = target_engine_version
  end

  if !target_cluster_parameter_group.empty?
    deployment_params[:target_db_cluster_parameter_group_name] = target_cluster_parameter_group
  end

  if !target_instance_parameter_group.empty?
    deployment_params[:target_db_parameter_group_name] = target_instance_parameter_group
  end

  if !target_db_instance_class.empty?
    deployment_params[:target_db_instance_class] = target_db_instance_class
  end

  say("Creating blue/green deployment #{deployment_params[:blue_green_deployment_name]} for #{source_arn}", :yellow)
  resp = Aws::Rds.client.create_blue_green_deployment(deployment_params)
  if resp.blue_green_deployment.status != "PROVISIONING"
    say("Failed to create blue/green deployment #{deployment_params[:blue_green_deployment_name]}", :red)
    puts resp.to_h
    return
  end

  invoke(:tail_upgrade_candidate, [], id: resp.blue_green_deployment.blue_green_deployment_identifier)
end

#delete_upgrade_candidateObject



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/stax/mixin/rds.rb', line 199

def delete_upgrade_candidate
  deployment_identifier = options[:id]
  delete_target = options[:delete_target]

  # Future TODO: Even though the RDS API doesn't allow for target resources to be deleted 
  # post-switchover (likely because these resources are the old DB resources), we could
  # allow for an automatic clean up the leftover resources by deleting these resources
  # with specific RDS API calls.
  if delete_target && Aws::Rds.client.describe_blue_green_deployments({ blue_green_deployment_identifier: deployment_identifier }).blue_green_deployments[0].status == "SWITCHOVER_COMPLETED"
    say("You can't specify --delete-target if the blue/green deployment status is SWITCHOVER_COMPLETED", :red)
    return
  end

  if yes?("Really delete blue/green deployment #{deployment_identifier}?", :yellow)
    say("Deleting blue/green deployment #{deployment_identifier}", :red)
    resp = Aws::Rds.client.delete_blue_green_deployment({
      blue_green_deployment_identifier: deployment_identifier,
      delete_target: delete_target,
    })

    if resp.blue_green_deployment.status != "DELETING"
      say("Failed to delete blue/green deployment #{deployment_identifier}", :red)
      puts resp.to_h
      return
    end

    # tail the blue/green deployment until it is deleted
    begin
      invoke(:tail_upgrade_candidate, [], id: deployment_identifier)
    rescue ::Aws::RDS::Errors::BlueGreenDeploymentNotFoundFault
      say("Deleted blue/green deployment #{deployment_identifier}", :green)
    end
  end
end

#endpointsObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/stax/mixin/rds.rb', line 100

def endpoints
  stack_rds_clusters.each do |c|
    debug("RDS DB endpoints for cluster #{c.db_cluster_identifier}")
    print_table [
      ['writer', c.endpoint,        c.port, c.hosted_zone_id],
      ['reader', c.reader_endpoint, c.port, c.hosted_zone_id],
    ]
  end

  debug("RDS DB instance endpoints for #{my.stack_name}")
  print_table stack_rds_instances.map { |i|
    [i.db_instance_identifier, i.endpoint&.address, i.endpoint&.port, i.endpoint&.hosted_zone_id]
  }
end

#eventsObject



351
352
353
354
355
356
357
358
359
360
361
# File 'lib/stax/mixin/rds.rb', line 351

def events
  stack_db_clusters.map(&:physical_resource_id).each do |id|
    debug("Events for cluster #{id}")
    print_rds_events(duration: options[:duration], source_type: 'db-cluster', source_identifier: id)
  end

  stack_db_instances.map(&:physical_resource_id).each do |id|
    debug("Events for instance #{id}")
    print_rds_events(duration: options[:duration], source_type: 'db-instance', source_identifier: id)
  end
end

#failoverObject



129
130
131
132
133
134
135
136
# File 'lib/stax/mixin/rds.rb', line 129

def failover
  stack_rds_clusters.each do |c|
    if yes?("Failover #{c.db_cluster_identifier}?", :yellow)
      resp = Aws::Rds.client.failover_db_cluster(db_cluster_identifier: c.db_cluster_identifier, target_db_instance_identifier: options[:target])
      puts "failing over #{resp.db_cluster.db_cluster_identifier}"
    end
  end
end

#instancesObject



92
93
94
95
96
97
# File 'lib/stax/mixin/rds.rb', line 92

def instances
  debug("RDS DB instances for #{my.stack_name}")
  print_table stack_rds_instances.map { |i|
    [i.db_instance_identifier, i.engine, i.engine_version, color(i.db_instance_status), i.db_instance_class, i.db_subnet_group&.vpc_id, i.availability_zone]
  }
end

#lsObject



57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/stax/mixin/rds.rb', line 57

def ls
  debug("RDS databases for #{my.stack_name}")
  stack_rds_clusters.map do |c|
    cluster = [ c.db_cluster_identifier, 'cluster', color(c.status), c.engine ]
    instances = c.db_cluster_members.map do |m|
      role = m.is_cluster_writer ? 'writer' : 'reader'
      i = Aws::Rds.instances(filters: [ { name: 'db-instance-id', values: [ m.db_instance_identifier ] } ]).first
      [ '- ' + i.db_instance_identifier, role, color(i.db_instance_status), i.engine, i.availability_zone, i.db_instance_class ]
    end
    [ cluster ] + instances
  end.flatten(1).tap do |list|
    print_table list
  end
end

#membersObject



81
82
83
84
85
86
87
88
89
# File 'lib/stax/mixin/rds.rb', line 81

def members
  stack_rds_clusters.each do |c|
    debug("RDS DB members for cluster #{c.db_cluster_identifier}")
    print_table c.db_cluster_members.map { |m|
      role = m.is_cluster_writer ? 'writer' : 'reader'
      [m.db_instance_identifier, role, m.db_cluster_parameter_group_status]
    }
  end
end

#snapshotsObject



338
339
340
341
342
343
344
345
346
347
# File 'lib/stax/mixin/rds.rb', line 338

def snapshots
  stack_db_clusters.map(&:physical_resource_id).each do |id|
    debug("Snapshots for cluster #{id}")
    Aws::Rds.client.describe_db_cluster_snapshots(db_cluster_identifier: id).map(&:db_cluster_snapshots).flatten.map do |s|
      [ s.db_cluster_snapshot_identifier, s.snapshot_create_time, "#{s.allocated_storage}G", color(s.status), s.snapshot_type ]
    end.tap do |list|
      print_table list
    end
  end
end

#subnetsObject



116
117
118
119
120
121
122
123
124
125
# File 'lib/stax/mixin/rds.rb', line 116

def subnets
  stack_db_subnet_groups.map do |r|
    Aws::Rds.subnet_groups(db_subnet_group_name: r.physical_resource_id)
  end.flatten.each do |g|
    debug("Subnets for group #{g.db_subnet_group_name}")
    print_table g.subnets.map { |s|
      [s&.subnet_availability_zone&.name, s&.subnet_identifier, color(s&.subnet_status)]
    }
  end
end

#switchover_upgrade_candidateObject



237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/stax/mixin/rds.rb', line 237

def switchover_upgrade_candidate
  deployment_identifier = options[:id]
  timeout = options[:timeout]

  if yes?("Really switchover blue/green deployment #{deployment_identifier}?", :yellow)
    say("Switchover blue/green deployment #{deployment_identifier}", :yellow)
    resp = Aws::Rds.client.switchover_blue_green_deployment({
      blue_green_deployment_identifier: deployment_identifier,
      switchover_timeout: timeout,
    })

    if resp.blue_green_deployment.status != "SWITCHOVER_IN_PROGRESS"
      say("Failed to switchover blue/green deployment #{deployment_identifier}", :red)
      puts resp.to_h
      return
    end

    # tail the blue/green deployment until it is complete
    invoke(:tail_upgrade_candidate, [], id: deployment_identifier)
  end
end

#tail_upgrade_candidateObject



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/stax/mixin/rds.rb', line 261

def tail_upgrade_candidate
  deployment_identifier = options[:id]

  previous_status = nil
  previous_switchover_details = []
  previous_tasks = []

  loop do
    resp = Aws::Rds.client.describe_blue_green_deployments({
      blue_green_deployment_identifier: deployment_identifier, 
    })

    if resp[:blue_green_deployments].empty?
      say("Deployment not found: #{deployment_identifier}", :red)
      return
    end

    deployment = resp[:blue_green_deployments][0]

    current_status = color(deployment[:status])
    current_switchover_details = deployment[:switchover_details]
    current_tasks = deployment[:tasks]

    if previous_status.nil?
      say("Deployment Name: #{deployment[:blue_green_deployment_name]}", :blue)
      say("Deployment ID: #{deployment_identifier}", :white)
      say("Create Time: #{deployment[:create_time]}", :green)
    end
      
    if previous_status.nil? || previous_status != current_status
      print_table [[Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC'), "Deployment Status", current_status]]
    end

    if !current_switchover_details.nil?
      current_switchover_details.each do |current_item|
        previous_item = previous_switchover_details.find { |item| item[:source_member] == current_item[:source_member] }
        
        if previous_item.nil? || current_item != previous_item
          if current_item[:target_member].nil?
            prefix = "#{set_color('Pending DB Resource', :cyan)}"
          else
            prefix = current_item[:target_member].include?(':cluster:') ? "#{set_color('DB Cluster', :cyan)}" : "#{set_color('DB Instance', :cyan)}"
          end
          print_table [[Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC'), prefix, current_item[:target_member], color(current_item[:status] || :CREATING)]]
        end
      end
    end

    if !current_tasks.nil?
      current_tasks.each do |current_item|
        previous_item = previous_tasks.find { |item| item[:name] == current_item[:name] }
  
        if previous_item.nil? || current_item != previous_item
          print_table [[Time.now.utc.strftime('%Y-%m-%d %H:%M:%S UTC'), "#{set_color('Task', :magenta)}", current_item[:name], color(current_item[:status])]]
        end
      end
    end

    if deployment[:status] == "AVAILABLE"
      say("Deployment is complete.", :green)
      break
    end

    if deployment[:status] == "SWITCHOVER_COMPLETED"
      say("Deployment switchover is complete.", :green)
      break
    end

    previous_status = current_status
    previous_switchover_details = current_switchover_details
    previous_tasks = current_tasks

    sleep 10  # Wait for 10 seconds before polling again
  end
end

#write_forwardingObject



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/stax/mixin/rds.rb', line 366

def write_forwarding
  stack_db_clusters.map(&:physical_resource_id).each do |cluster|
    if options[:enable]
      puts "#{cluster} enabling write-forwarding"
      Aws::Rds.client.modify_db_cluster(db_cluster_identifier: cluster, enable_global_write_forwarding: true)
    elsif options[:disable]
      puts "#{cluster} disabling write-forwarding"
      Aws::Rds.client.modify_db_cluster(db_cluster_identifier: cluster, enable_global_write_forwarding: false)
    else
      print_table Aws::Rds.client.describe_db_clusters(db_cluster_identifier: cluster).db_clusters.map { |c|
        [ c.db_cluster_identifier, c.global_write_forwarding_status || '-' ]
      }
    end
  end
end