class Dyna::DynamoDBWrapper::Table

Public Class Methods

new(ddb, table, options) click to toggle source
# File lib/dyna/wrapper/table.rb, line 12
def initialize(ddb, table, options)
  @ddb = ddb
  @table = table
  @options = options
end

Public Instance Methods

definition() click to toggle source
# File lib/dyna/wrapper/table.rb, line 51
def definition
  Exporter.table_definition(@table).symbolize_keys
end
delete() click to toggle source
# File lib/dyna/wrapper/table.rb, line 42
def delete
  log(:info, 'Delete Table', :red, "#{table_name}")
  
  unless @options.dry_run
    @ddb.delete_table(table_name: @table.table_name)
    @options.updated = true
  end
end
eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 18
def eql?(dsl)
  definition_eql?(dsl)
end
update(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 22
def update(dsl)
  unless billing_mode_eql?(dsl) && provisioned_throughput_eql?(dsl)
    update_table(dsl)
  end
  unless global_secondary_indexes_eql?(dsl)
    wait_until_table_is_active
    update_table_index(dsl, dsl_global_secondary_index_updates(dsl))
  end
  unless stream_specification_eql?(dsl)
    wait_until_table_is_active
    update_stream_specification(dsl_stream_specification(dsl))
  end
  unless time_to_live_eql?(dsl)
    update_time_to_live(dsl)
  end
  unless auto_scaling_eql?(dsl)
    update_auto_scaling(dsl)
  end
end
wait_until_table_is_active() click to toggle source
# File lib/dyna/wrapper/table.rb, line 55
def wait_until_table_is_active
  log(:info, "waiting table #{@table.table_name} to be ACTIVE or deleted..", false)
  loop do
    begin
      desc = @ddb.describe_table(table_name: table_name).table
    rescue => e
      break
    end
    status = desc.table_status
    log(:info, "status... #{status}", false)
    break if desc.table_status == 'ACTIVE'
    sleep 3
  end
end

Private Instance Methods

auto_scaling_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 88
def auto_scaling_eql?(dsl)
  scalable_targets_eql?(dsl) && scaling_policies_eql?(dsl)
end
aws_time_to_live() click to toggle source
# File lib/dyna/wrapper/table.rb, line 72
def aws_time_to_live
  @ttl ||= @ddb.describe_time_to_live(table_name: @table.table_name).time_to_live_description
end
billing_mode_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 128
def billing_mode_eql?(dsl)
  if definition[:billing_mode] == dsl[:billing_mode]
    return true
  end

  definition[:billing_mode].nil? && dsl[:billing_mode].to_s == 'PROVISIONED'
end
definition_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 117
def definition_eql?(dsl)
  definition == dsl.definition
end
dsl_global_secondary_index_updates(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 156
def dsl_global_secondary_index_updates(dsl)
  actual_by_name = (self_global_secondary_indexes || {}).group_by { |index| index[:index_name] }.each_with_object({}) do |(k, v), h|
    h[k] = v.first
  end
  expect_by_name = (dsl_global_secondary_indexes(dsl) || {}).group_by { |index| index[:index_name] }.each_with_object({}) do |(k, v), h|
    h[k] = v.first
  end
  params = []
  expect_by_name.each do |index_name, expect_index|
    actual_index = actual_by_name[index_name]
    unless actual_index
      unless params.empty?
        log(:warn, 'Can not add multiple GSI at once', :yellow, index_name)
        next
      end
      params << {create: expect_index}
    end
  end

  expect_by_name.each do |index_name, expect_index|
    actual_index = actual_by_name.delete(index_name)
    if actual_index != nil &&
      actual_index[:provisioned_throughput] != expect_index[:provisioned_throughput]
      if params.any? { |param| param[:update] }
        log(:warn, 'Can not update multiple GSI at once', :yellow, index_name)
        next
      end
      params << {update: {
        index_name: index_name,
        provisioned_throughput: expect_index[:provisioned_throughput]
      }}
    end
  end

  actual_by_name.each do |index_name, actual_index|
    if params.any? { |param| param[:delete] }
        log(:warn, 'Can not delete multiple GSI at once', :yellow, index_name)
        next
    end
    params << {delete: { index_name: index_name }}
  end

  params
end
dsl_global_secondary_indexes(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 152
def dsl_global_secondary_indexes(dsl)
  dsl.symbolize_keys[:global_secondary_indexes]
end
dsl_provisioned_throughput(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 140
def dsl_provisioned_throughput(dsl)
  dsl.symbolize_keys.select {|k,v| k == :provisioned_throughput}
end
dsl_stream_specification(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 215
def dsl_stream_specification(dsl)
  dsl.symbolize_keys.select {|k,v| k == :stream_specification}
end
global_secondary_indexes_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 144
def global_secondary_indexes_eql?(dsl)
  self_global_secondary_indexes == dsl_global_secondary_indexes(dsl)
end
provisioned_throughput_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 121
def provisioned_throughput_eql?(dsl)
  if definition[:billing_mode] == 'PROVISIONED' && billing_mode_eql?(dsl)
    return true
  end
  self_provisioned_throughput == dsl_provisioned_throughput(dsl)
end
scalable_targets_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 92
def scalable_targets_eql?(dsl)
  df = definition[:scalable_targets].map do |target|
    cmp = target.to_h
    cmp.delete(:creation_time)
    cmp.delete(:role_arn)
    Dyna::Utils.normalize_hash(cmp)
  end
  df.sort_by {|s| s[:scalable_dimension] } == dsl[:scalable_targets].map { |target| Dyna::Utils.normalize_hash(target) }.sort_by {|s| s[:scalable_dimension] }
end
scaling_policies_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 113
def scaling_policies_eql?(dsl)
  scaling_policies_for_diff == dsl.scaling_policies.map { |policy| Dyna::Utils.normalize_hash(policy) }.sort_by {|s| s[:scalable_dimension] }
end
scaling_policies_for_diff() click to toggle source
# File lib/dyna/wrapper/table.rb, line 102
def scaling_policies_for_diff
  definition[:scaling_policies].map { |policy|
    #Dyna::Utils.normalize_hash({target_tracking_scaling_policy_configuration: {target_value: policy.target_tracking_scaling_policy_configuration.target_value} })
    cmp = policy.to_h
    cmp.delete(:alarms)
    cmp.delete(:creation_time)
    cmp.delete(:policy_arn)
    Dyna::Utils.normalize_hash(cmp)
  }.sort_by {|s| s[:scalable_dimension] }
end
self_global_secondary_indexes() click to toggle source
# File lib/dyna/wrapper/table.rb, line 148
def self_global_secondary_indexes
  definition[:global_secondary_indexes]
end
self_provisioned_throughput() click to toggle source
# File lib/dyna/wrapper/table.rb, line 136
def self_provisioned_throughput
  definition.select {|k,v| k == :provisioned_throughput}
end
self_stream_specification() click to toggle source
# File lib/dyna/wrapper/table.rb, line 211
def self_stream_specification
  definition.select {|k,v| k == :stream_specification}
end
stream_specification_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 201
def stream_specification_eql?(dsl)
  actual = self_stream_specification
  expect = dsl_stream_specification(dsl)
  if (actual == nil || actual[:stream_specification] == nil) &&
     (expect == nil || expect[:stream_specification] == nil || expect[:stream_specification][:stream_enabled] == false)
    return true
  end
  actual == expect
end
time_to_live_eql?(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 76
def time_to_live_eql?(dsl)
  wait_until_table_is_active
  ttl = aws_time_to_live
  unless %w/ENABLED DISABLED/.include?(ttl.time_to_live_status)
    raise "time to live status is #{ttl.time_to_live_status} and must be ENABLED or DISABLED to apply"
  end
  same_status = dsl.time_to_live_specification.enabled.to_s == 'false' && ttl.time_to_live_status == 'DISABLED' || dsl.time_to_live_specification.enabled.to_s == 'true' && ttl.time_to_live_status == 'ENABLED'
  same_name = dsl.time_to_live_specification.attribute_name.to_s == ttl.attribute_name.to_s

  same_status && same_name
end
update_auto_scaling(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 284
def update_auto_scaling(dsl)
  unless @options.aas
    log(:warn, "auto scalings can not be used in local mode.".yellow, false)
    return
  end

  has_change = false
  unless scalable_targets_eql?(dsl)
    has_change = true
    df_cmp = definition[:scalable_targets].sort_by { |target| target[:scalable_dimension] }.map do |target|
      h = target.to_h
      h.delete(:creation_time)
      h.delete(:role_arn)
      Dyna::Utils.normalize_hash(h)
    end
    dsl_cmp = dsl.scalable_targets.sort_by { |target| target[:scalable_dimension] }.map { |target| Dyna::Utils.normalize_hash(target) }
    log(:info, "  table: #{@table.table_name}(update scalable targets)\n".green + Dyna::Utils.diff(df_cmp, dsl_cmp, :color => @options.color, :indent => '    '), false)
  end

  unless scaling_policies_eql?(dsl)
    has_change = true
    dsl_cmp = dsl.scaling_policies.map { |policy| Dyna::Utils.normalize_hash(policy) }.sort_by {|s| s[:scalable_dimension] }
    log(:info, "  table: #{@table.table_name}(update scaling policies)\n".green + Dyna::Utils.diff(scaling_policies_for_diff, dsl_cmp, :color => @options.color, :indent => '    '), false)
  end

  unless @options.dry_run
    if has_change
      definition[:scalable_targets].each do |target|
        @options.aas.deregister_scalable_target(
          service_namespace: 'dynamodb',
          resource_id: target.resource_id,
          scalable_dimension: target.scalable_dimension,
        )
      end

      dsl.scalable_targets.each do |target|
        @options.aas.register_scalable_target(target)
      end

      dsl.scaling_policies.each do |policy|
        @options.aas.put_scaling_policy(policy)
      end
    end
    @options.updated = true
  end
end
update_stream_specification(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 219
def update_stream_specification(dsl)
  dsl = dsl.dup
  unless dsl[:stream_specification]
    dsl[:stream_specification] = { stream_enabled: false }
  end

  log(:info, "  table: #{@table.table_name}(update stream spec)\n".green + Dyna::Utils.diff(self_stream_specification, dsl, :color => @options.color, :indent => '    '), false)
  unless @options.dry_run
    params = { table_name: @table.table_name }.merge(dsl)
    @ddb.update_table(params)
    @options.updated = true
  end
end
update_table(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 233
def update_table(dsl)
  params = {}
  df_params = {}
  unless billing_mode_eql?(dsl)
    params[:billing_mode] = dsl[:billing_mode]
    df_params[:billing_mode] = definition[:billing_mode]
  end

  if dsl[:provisioned_throughput] && provisioned_throughput_eql?(dsl) == false && dsl[:scalable_targets].empty?
    params[:provisioned_throughput] = dsl[:provisioned_throughput].symbolize_keys
    df_params[:provisioned_throughput] = self_provisioned_throughput[:provisioned_throughput]
  end

  return if params.empty?
  log(:info, "  table: #{@table.table_name}\n".green + Dyna::Utils.diff(df_params, params, :color => @options.color, :indent => '    '), false)
  unless @options.dry_run
    wait_until_table_is_active
    params[:table_name] = @table.table_name
    @ddb.update_table(params.symbolize_keys)
    @options.updated = true
  end
end
update_table_index(dsl, index_params) click to toggle source
# File lib/dyna/wrapper/table.rb, line 256
def update_table_index(dsl, index_params)
  log(:info, "  table: #{@table.table_name}(update GSI)".green, false)
  index_params.each do |index_param|
    if index_param[:create]
      log(:info, "  index: #{index_param[:create][:index_name]}(create GSI)".cyan, false)
      log(:info, "    => #{index_param[:create]}".cyan, false)
    end
    if index_param[:update]
      log(:info, "  index: #{index_param[:update][:index_name]}(update GSI)".green, false)
      log(:info, "    => #{index_param[:update]}".green, false)
    end
    if index_param[:delete]
      log(:info, "  index: #{index_param[:delete][:index_name]}(delete GSI)".red, false)
      log(:info, "    => #{index_param[:delete]}".red, false)
    end
  end

  unless @options.dry_run
    params = {
      table_name: @table.table_name,
      attribute_definitions: dsl.symbolize_keys[:attribute_definitions],
      global_secondary_index_updates: index_params,
    }
    @ddb.update_table(params)
    @options.updated = true
  end
end
update_time_to_live(dsl) click to toggle source
# File lib/dyna/wrapper/table.rb, line 331
def update_time_to_live(dsl)
  params = { table_name: @table.table_name }
  if dsl.time_to_live_specification.enabled.to_s == 'true'
    params[:time_to_live_specification] = {
      enabled: dsl.time_to_live_specification.enabled,
      attribute_name: dsl.time_to_live_specification.attribute_name,
    }
  else
    params[:time_to_live_specification] = {
      enabled: false,
      attribute_name: aws_time_to_live.attribute_name,
    }
  end

  log(:info, "  table: #{@table.table_name}(update time to live)".green, false)
  log(:info, "    => enabled: #{params[:time_to_live_specification][:enabled]}".cyan, false)
  log(:info, "    => attribute_name: #{params[:time_to_live_specification][:attribute_name]}".cyan, false)

  unless @options.dry_run
    log(:debug, params, false)
    @ddb.update_time_to_live(params)
    @options.updated = true
  end
end