Class: Cheftacular::Action

Inherits:
Object
  • Object
show all
Defined in:
lib/cheftacular/action.rb,
lib/cheftacular/actions/log.rb,
lib/cheftacular/actions/run.rb,
lib/cheftacular/actions/tail.rb,
lib/cheftacular/actions/check.rb,
lib/cheftacular/actions/scale.rb,
lib/cheftacular/actions/deploy.rb,
lib/cheftacular/actions/console.rb,
lib/cheftacular/actions/migrate.rb,
lib/cheftacular/actions/db_console.rb

Instance Method Summary collapse

Constructor Details

#initialize(options, config) ⇒ Action

Returns a new instance of Action.



10
11
12
# File 'lib/cheftacular/action.rb', line 10

def initialize options, config
  @options, @config = options, config
end

Instance Method Details

#check(commit_hash = {}, have_revisions = false, have_changed_orgs = false) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/cheftacular/actions/check.rb', line 18

def check commit_hash={}, have_revisions=false, have_changed_orgs=false
  @config['filesystem'].cleanup_file_caches('current-nodes')
  
  nodes = @config['getter'].get_true_node_objects

  #this must always precede on () calls so they have the instance variables they need
  options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars

  on ( nodes.map { |n| @config['cheftacular']['deploy_user'] + "@" + n.public_ipaddress } ), in: :parallel do |host|
    n = get_node_from_address(nodes, host.hostname)

    puts("Beginning commit check run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']

    commit_hash[n.name]                      = start_commit_check( n.name, n.public_ipaddress, options, locs, cheftacular)

    if n.normal_attributes.has_key?(options['repository'])
      commit_hash[n.name]['branch']       = n.normal_attributes[options['repository']]['repo_branch'] if n.normal_attributes[options['repository']].has_key?('repo_branch')
      commit_hash[n.name]['organization'] = n.normal_attributes[options['repository']]['repo_group']  if n.normal_attributes[options['repository']].has_key?('repo_group')
    end

    have_revisions    = true if commit_hash[n.name].has_key?('branch')
    have_changed_orgs = true if commit_hash[n.name].has_key?('organization')
  end

  puts "\n#{ 'name'.ljust(21) }#{ 'deployed_on'.ljust(22) } #{ 'commit'.ljust(40) } #{'revision'.ljust(29) if have_revisions } #{'organization'.ljust(30) if have_changed_orgs }"
  nodes.each do |n|
    unless commit_hash[n.name]['name'].blank?
      out  = []
      out << n.name.ljust(20, '_')
      out << commit_hash[n.name]['time'].ljust(21)
      out << commit_hash[n.name]['name'].ljust(39)
      out << commit_hash[n.name]['branch'].ljust(29)       if commit_hash[n.name].has_key?('branch')
      out << commit_hash[n.name]['organization'].ljust(30) if commit_hash[n.name].has_key?('organization') 

      puts out.join(' ')
    end
  end
end

#consoleObject



17
18
19
# File 'lib/cheftacular/actions/console.rb', line 17

def console
  self.send("console_#{ @config['getter'].get_current_stack }")
end

#console_allObject



42
43
44
# File 'lib/cheftacular/actions/console.rb', line 42

def console_all
  raise "You attempted to create a console for the all role, this is not possible."
end

#console_nodejsObject



34
35
36
# File 'lib/cheftacular/actions/console.rb', line 34

def console_nodejs
  raise "Not yet implemented"
end

#console_ruby_on_railsObject



21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/cheftacular/actions/console.rb', line 21

def console_ruby_on_rails
  nodes = @config['getter'].get_true_node_objects

  #must have rails stack to run migrations and not be a db, only want ONE node
  nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }, { unless: 'role[rails]' }], true )

  nodes.each do |n|
    puts("Beginning console run for #{ n.name } (#{ n.public_ipaddress }) on role #{ @options['role'] }") unless @options['quiet']

    start_console_ruby_on_rails(n.public_ipaddress, n.run_list)
  end
end

#console_wordpressObject



38
39
40
# File 'lib/cheftacular/actions/console.rb', line 38

def console_wordpress
  raise "Not yet implemented"
end

#db_consoleObject



21
22
23
# File 'lib/cheftacular/actions/db_console.rb', line 21

def db_console
  self.send("db_console_#{ @config['getter'].get_current_database }")
end

#db_console_Object



63
64
65
66
67
# File 'lib/cheftacular/actions/db_console.rb', line 63

def db_console_
  puts "db_console method tried to create a db_console for the role \"#{ @options['role'] }\" but it doesn't appear to have a repository set! Skipping..."

  return false
end

#db_console_mongodbObject Also known as: mongo



42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/cheftacular/actions/db_console.rb', line 42

def db_console_mongodb
  nodes = @config['getter'].get_true_node_objects(true)

  #must have mongo db, only want ONE node
  mongoable_nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }, { if: { not_env: @options['env'] } }], true )

  mongoable_nodes.each do |n|
    puts("Beginning database console run for #{ n.name } (#{ n.public_ipaddress }) on role #{ @options['role'] }") unless @options['quiet']

    start_console_mongodb(n.public_ipaddress)
  end
end

#db_console_mysqlObject



55
56
57
# File 'lib/cheftacular/actions/db_console.rb', line 55

def db_console_mysql
  raise "Not yet implemented"
end

#db_console_noneObject



59
60
61
# File 'lib/cheftacular/actions/db_console.rb', line 59

def db_console_none
  raise "You attempted to create a database console for a role that had no database type attached to it, this is not possible."
end

#db_console_postgresqlObject Also known as: psql



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/cheftacular/actions/db_console.rb', line 25

def db_console_postgresql
  nodes = @config['getter'].get_true_node_objects(true)

  #must have rails stack to run migrations and not be a db, only want ONE node
  psqlable_nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }, { unless: 'role[rails]' }, { if: { not_env: @options['env'] } }], true )

  database_host  = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @config['getter'].get_current_repo_config['db_primary_host_role'] }]"}, { if: { not_env: @options['env'] } }], true).first

  private_database_host_address = @config['getter'].get_address_hash(database_host.name)[database_host.name]['priv']

  psqlable_nodes.each do |n|
    puts("Beginning database console run for #{ n.name } (#{ n.public_ipaddress }) on role #{ @options['role'] }") unless @options['quiet']

    start_console_postgresql(n.public_ipaddress, private_database_host_address )
  end
end

#deployObject



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/cheftacular/actions/deploy.rb', line 29

def deploy
  nodes = @config['getter'].get_true_node_objects false, true #when this is run in scaling we'll need to make sure we deploy to new nodes

  nodes = @config['parser'].exclude_nodes( nodes, [{ if: "role[#{ @options['negative_role'] }]" }]) if @options['negative_role']
  
  #this must always precede on () calls so they have the instance variables they need
  options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars

  #on is namespaced to SSHKit::Backend::Netssh.on 
  on ( nodes.map { |n| @config['cheftacular']['deploy_user'] + "@" + n.public_ipaddress } ), in: :groups, limit: 10, wait: 5 do |host|
    n = get_node_from_address(nodes, host.hostname)

    puts("Beginning chef client run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']

    log_data, timestamp, exit_status = start_deploy( n.name, n.public_ipaddress, options, locs, passwords)

    logs_bag_hash["#{ n.name }-#{ __method__ }"] = { "text" => log_data.scrub_pretty_text, "timestamp" => timestamp, "exit_status" => exit_status }
  end

  @config['helper'].send_log_bag_hash_slack_notification(logs_bag_hash, __method__)
  
  @config['ChefDataBag'].save_logs_bag unless @options['debug'] #We don't really need to store entire chef runs in the logs bag

  migrate(nodes) if @config['getter'].get_current_repo_config['database'] != 'none' && !@options['run_migration_already']

  split_nodes_hash = {}

  if @config['cheftacular']['run_list_environments'].has_key?(@options['env'])
    @config['cheftacular']['run_list_environments'][@options['env']].each_key do |role_name|
      split_nodes_hash[role_name] = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ role_name }]" }])
    end

    split_nodes_hash.each_pair do |role, split_nodes|
      next if split_nodes.empty?

      unless @options["run_#{ role }_migrations_already"]
        @options["run_#{ role }_migrations_already"] = true
        
        if @config['getter'].get_current_repo_config['database'] != 'none'
          puts("Running migration on split environment #{ role }...") if !@options['quiet']
          
          migrate(split_nodes)
        end
      end
    end
  end
end

#logObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/cheftacular/actions/log.rb', line 27

def log
  nodes = @config['getter'].get_true_node_objects

  nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }] )

  nodes = @config['parser'].exclude_nodes( nodes, [{ if: "role[#{ @options['negative_role'] }]" }]) if @options['negative_role']
  
  #this must always precede on () calls so they have the instance variables they need
  options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars

  getter = @config['getter']

  on ( nodes.map { |n| @config['cheftacular']['deploy_user'] + "@" + n.public_ipaddress } ), in: :parallel do |host|
    n = get_node_from_address(nodes, host.hostname)

    puts("Beginning log fetch run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']

    if has_run_list_in_role_map?(n.run_list, cheftacular['role_maps'])
      start_log_role_map( n.name, n.public_ipaddress, getter.get_current_role_map(n.run_list)['log_location'], options, locs, cheftacular, passwords)
    else
      self.send("start_log_fetch_#{ getter.get_current_stack }", n.name, n.public_ipaddress, n.run_list, options, locs, cheftacular, passwords)
    end
  end
end

#migrate(nodes = []) ⇒ Object



19
20
21
# File 'lib/cheftacular/actions/migrate.rb', line 19

def migrate nodes=[]
  self.send("migrate_#{ @config['getter'].get_current_stack }", nodes)
end

#migrate_(nodes = []) ⇒ Object



70
71
72
73
74
# File 'lib/cheftacular/actions/migrate.rb', line 70

def migrate_ nodes=[]
  puts "Migrate method tried to migrate the role \"#{ @options['role'] }\" but it doesn't appear to have a repository set! Skipping..."

  return false
end

#migrate_all(nodes = []) ⇒ Object



66
67
68
# File 'lib/cheftacular/actions/migrate.rb', line 66

def migrate_all nodes=[]
  raise "You attempted to migrate the all role, this is not possible."
end

#migrate_nodejs(nodes = []) ⇒ Object



60
61
62
63
64
# File 'lib/cheftacular/actions/migrate.rb', line 60

def migrate_nodejs nodes=[]
  puts "Method #{ __method__ } is not yet implemented"

  exit
end

#migrate_ruby_on_rails(nodes = []) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/cheftacular/actions/migrate.rb', line 23

def migrate_ruby_on_rails nodes=[]
  nodes = @config['getter'].get_true_node_objects if nodes.empty?

  #must have rails stack to run migrations, only want ONE node
  nodes = @config['parser'].exclude_nodes(nodes, [{ unless: "role[#{ @options['role'] }]" }, { unless: 'role[rails]' }], true)

  log_data = ""

  #this must always precede on () calls so they have the instance variables they need
  options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars

  on ( nodes.map { |n| @config['cheftacular']['deploy_user'] + "@" + n.public_ipaddress } ) do |host|
    n = get_node_from_address(nodes, host.hostname)

    puts("Beginning migration run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']

    log_data, timestamp, exit_status = start_task( n.name, n.public_ipaddress, n.run_list, "#{ bundle_command } exec rake db:migrate", options, locs, cheftacular)

    logs_bag_hash["#{ n.name }-#{ __method__ }"] = { "text" => log_data.scrub_pretty_text, "timestamp" => timestamp, "exit_status" => exit_status }
  end

  @config['ChefDataBag'].save_logs_bag

  @config['helper'].send_log_bag_hash_slack_notification(logs_bag_hash, __method__, 'Failing migration detected, please fix this and deploy again, exiting...')

  @options['run_migration_already'] = true

  #restart the servers again after a deploy with a migration just in case
  deploy if !log_data.empty? && log_data != @config['cheftacular']['repositories'][@options['role']]['not_a_migration_message']
end

#migrate_wordpress(nodes = []) ⇒ Object



54
55
56
57
58
# File 'lib/cheftacular/actions/migrate.rb', line 54

def migrate_wordpress nodes=[]
  puts "Method #{ __method__ } is not yet implemented"

  exit
end

#runObject



28
29
30
# File 'lib/cheftacular/actions/run.rb', line 28

def run
  self.send("run_#{ @config['getter'].get_current_stack }")
end

#run_Object



71
72
73
74
75
# File 'lib/cheftacular/actions/run.rb', line 71

def run_
  puts "Run method tried to run a command for the role \"#{ @options['role'] }\" but it doesn't appear to have a repository set! Skipping..."

  return false
end

#run_allObject



67
68
69
# File 'lib/cheftacular/actions/run.rb', line 67

def run_all
  raise "You attempted to run a command for the all role, this is not possible."
end

#run_nodejsObject



59
60
61
# File 'lib/cheftacular/actions/run.rb', line 59

def run_nodejs
  raise "Not yet implemented"
end

#run_ruby_on_railsObject



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/cheftacular/actions/run.rb', line 32

def run_ruby_on_rails
  nodes   = @config['getter'].get_true_node_objects
  command = @config['parser'].parse_runtime_arguments 0, 'range'

  #must have rails stack to run migrations and not be a db, only want ONE node
  nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }], !@options['run_on_all'] )

  nodes = @config['parser'].exclude_nodes( nodes, [{ if: "role[#{ @options['negative_role'] }]" }]) if @options['negative_role']

  #this must always precede on () calls so they have the instance variables they need
  options, locs, ridley, logs_bag_hash, pass_bag_hash, bundle_command, cheftacular, passwords = @config['helper'].set_local_instance_vars

  on ( nodes.map { |n| @config['cheftacular']['deploy_user'] + "@" + n.public_ipaddress } ) do |host|
    n = get_node_from_address(nodes, host.hostname)

    puts("Beginning task run for #{ n.name } (#{ n.public_ipaddress }) on role #{ options['role'] }") unless options['quiet']

    log_data, timestamp, exit_status = start_task( n.name, n.public_ipaddress, n.run_list, "#{ bundle_command } exec #{ command }", options, locs, cheftacular)

    logs_bag_hash["#{ n.name }-#{ __method__ }"] = { "text" => log_data.scrub_pretty_text, "timestamp" => timestamp, "exit_status" => exit_status }
  end

  @config['ChefDataBag'].save_logs_bag

  @config['helper'].send_log_bag_hash_slack_notification(logs_bag_hash, __method__, 'Failing command detected, exiting...')
end

#run_wordpressObject



63
64
65
# File 'lib/cheftacular/actions/run.rb', line 63

def run_wordpress
  raise "Not yet implemented"
end

#scale(type = "up", num_to_scale = 1) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/cheftacular/actions/scale.rb', line 23

def scale type="up", num_to_scale=1
  type         = ARGV[1] if ARGV[1]
  num_to_scale = ARGV[2] if ARGV[2]

  raise "Unknown type for scaling: #{ type }" unless (type =~ /up|down/) == 0
  raise "Unknown scaling: #{ num_to_scale }"  unless num_to_scale.is_a?(Fixnum) && num_to_scale >= 1

  nodes = @config['getter'].get_true_node_objects

  base_node_names = {}

  nodes = @config['parser'].exclude_nodes( nodes, [{ unless: 'role[scalable]' }] )

  nodes.each do |n|
    #names are stored alphabetically so this will always put the result hash in the form of {base_name => highest N}
    base_node_names[n.name.gsub(/\d/,'')] ||= {}
    base_node_names[n.name.gsub(/\d/,'')][n.name]        = n.name.gsub(/[^\d]/,'')

    if base_node_names[n.name.gsub(/\d/,'')][n.name] > base_node_names[n.name.gsub(/\d/,'')]['highest_val'] || base_node_names[n.name.gsub(/\d/,'')]['highest_val'].nil?
      base_node_names[n.name.gsub(/\d/,'')]['highest_val'] = n.name.gsub(/[^\d]/,'').to_i 
    end
  end

  base_node_names.each_pair do |base_name, nodes_under_name_hash|
    raise "Cannot scale lower than 1" if (nodes_under_name_hash.keys-1).count <= num_to_scale && type == 'down'
  end

  if base_node_names.empty?
    puts("There are no nodes for #{ @options['role'] } in env #{ @options['env'] } that have scaling enabled.") unless options['quiet']
  end

  @options['force_yes']  = true
  @options['in_scaling'] = true

  scaling_node_defaults = @config['cheftacular']['scaling_nodes']

  (1..num_to_scale).each do |i|
    case type
    when 'up'
      base_node_names.each_pair do |base_name, nodes_under_name_hash|
        @options['node_name']   = base_name + ( node_under_name_hash['highest_val'] + i ).to_s.rjust(2, '0')

        if scaling_node_defaults.has_key?(base_name)
          @options['flavor_name'] = scaling_node_defaults[base_name].has_key?('flavor') ? scaling_node_defaults[base_name]['flavor'] : @config['cheftacular']['default_flavor_name']
          @options['descriptor']  = scaling_node_defaults[base_name].has_key?('descriptor') ? scaling_node_defaults[base_name]['descriptor'] : @options['node_name']
        else
          @options['flavor_name'] = @config['cheftacular']['default_flavor_name']
          @options['descriptor']  = @options['node_name']
        end

        puts("Preparing to scale #{ type } server #{ @options['node_name'] } on role #{ @options['role'] }!") unless @options['quiet']

        @config['stateless_action'].cloud_bootstrap
      end
    when 'down'
      base_node_names.each_pair do |base_name, nodes_under_name_hash|
        @options['node_name']   = base_name + ( node_under_name_hash['highest_val'] + i ).to_s.rjust(2, '0')

        puts("Preparing to scale #{ type } server #{ @options['node_name'] } on role #{ @options['role'] }!") unless @options['quiet']

        remove_client true
      end
    end

    sleep 15 if num_to_scale > 1
  end

  @config['ChefDataBag'].save_server_passwords_bag #we must save the auth bag here and not in the individual rax_bootstrap runs so they don't corrupt the bags

  @options['node_name'] = nil #if this is not nil deploy_role will try to deploy to a single server instead of the group

  @config['action'].deploy
end

#tail(pattern_to_match = '') ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/cheftacular/actions/tail.rb', line 25

def tail pattern_to_match=''
  pattern_to_match = ARGV[1] if pattern_to_match.blank?

  nodes = @config['getter'].get_true_node_objects

  nodes = @config['parser'].exclude_nodes( nodes, [{ unless: "role[#{ @options['role'] }]" }], true )

  nodes = @config['parser'].exclude_nodes( nodes, [{ if: "role[#{ @options['negative_role'] }]" }], true) if @options['negative_role']

  nodes.each do |n|
    puts("Beginning tail run for #{ n.name } (#{ n.public_ipaddress }) on role #{ @options['role'] }") unless @options['quiet']

    if @config['dummy_sshkit'].has_run_list_in_role_map?(n.run_list, @config['cheftacular']['role_maps'])
      start_tail_role_map( n.public_ipaddress, n.run_list, pattern_to_match )
    else
      self.send("start_tail_#{ @config['getter'].get_current_stack }", n.public_ipaddress, n.run_list, pattern_to_match )
    end
  end
end