class Spiceweasel::Nodes

manages parsing of Nodes

Constants

PROVIDERS

Attributes

create[R]
delete[R]

Public Class Methods

new(nodes, cookbooks, environments, roles, knifecommands, rootoptions) click to toggle source
# File lib/spiceweasel/nodes.rb, line 29
def initialize(nodes, cookbooks, environments, roles, knifecommands, rootoptions) # rubocop:disable CyclomaticComplexity
  @create = []
  @delete = []
  chefclient = []
  create_command_options = {}

  return unless nodes

  Spiceweasel::Log.debug("nodes: #{nodes}")
  nodes.each do |node|
    name = node.keys.first
    names = name.split
    Spiceweasel::Log.debug("node: '#{name}' '#{node[name]}'")
    # get the node's run_list and options
    if node[name]
      run_list = process_run_list(node[name]['run_list'])
      Spiceweasel::Log.debug("node: '#{name}' run_list: '#{run_list}'")
      validate_run_list(name, run_list, cookbooks, roles) unless Spiceweasel::Config[:novalidation]
      options = ((node[name]['options'] || '') + ' ' + (rootoptions || '')).rstrip
      Spiceweasel::Log.debug("node: '#{name}' options: '#{options}'")
      validate_options(name, options, environments) unless Spiceweasel::Config[:novalidation]
      %w(allow_create_failure timeout).each do |key|
        if node[name].key?(key)
          create_command_options[key] = node[name][key]
        end
      end
      additional_commands = node[name]['additional_commands'] || []
    end
    if Spiceweasel::Config[:chefclient]
      chefclient.push(process_chef_client(names, options, run_list))
    elsif Spiceweasel::Config[:node_only]
      process_nodes_only(names, options, run_list, create_command_options)
    else # create/delete
      # provider support
      if PROVIDERS.member?(names[0])
        count = names.length == 2 ? names[1] : 1
        process_providers(names, count, node[name]['name'], options, run_list, create_command_options, knifecommands)
      elsif names[0].start_with?('windows_')
        # windows node bootstrap support
        protocol = names.shift.split('_') # split on 'windows_ssh' etc
        names.each do |server|
          servercommand = "knife bootstrap #{protocol[0]} #{protocol[1]}#{Spiceweasel::Config[:knife_options]} #{server} #{options}"
          servercommand += " -r '#{run_list}'" unless run_list.empty?
          create_command(servercommand, create_command_options)
          delete_command("knife node#{Spiceweasel::Config[:knife_options]} delete #{server} -y")
          delete_command("knife client#{Spiceweasel::Config[:knife_options]} delete #{server} -y")
        end
      else
        # node bootstrap support
        name.split.each_with_index do |server, i|
          servercommand = node_numerate("knife bootstrap#{Spiceweasel::Config[:knife_options]} #{server} #{options}", i + 1, count)
          servercommand += " -r '#{run_list}'" unless run_list.empty?
          create_command(servercommand, create_command_options)
          delete_command("knife node#{Spiceweasel::Config[:knife_options]} delete #{server} -y")
          delete_command("knife client#{Spiceweasel::Config[:knife_options]} delete #{server} -y")
        end
      end
      unless additional_commands.empty?
        additional_commands.each do |cmd|
          create_command(cmd, create_command_options)
        end
      end
    end
  end
  if Spiceweasel::Config[:bulkdelete]
    delete_command("knife node#{Spiceweasel::Config[:knife_options]} bulk delete .* -y")
  end
  # remove repeats in chefclient and push into create_command
  chefclient.flatten.each_with_index { |x, i| create_command(x, create_command_options) unless x.eql?(chefclient[i - 1]) } if Spiceweasel::Config[:chefclient]
  # nodeonly
end

Public Instance Methods

determine_cloud_provider(count, create_command_options, name, options, provider, run_list) click to toggle source
# File lib/spiceweasel/nodes.rb, line 222
def determine_cloud_provider(count, create_command_options, name, options, provider, run_list)
  count.to_i.times do |i|
    if provider.eql?('vsphere')
      server = node_numerate("knife #{provider}#{Spiceweasel::Config[:knife_options]} vm clone #{options}", i + 1, count)
    elsif provider.eql?('kvm')
      server = node_numerate("knife #{provider}#{Spiceweasel::Config[:knife_options]} vm create #{options}", i + 1, count)
    elsif provider.eql?('digital_ocean')
      server = node_numerate("knife #{provider}#{Spiceweasel::Config[:knife_options]} droplet create #{options}", i + 1, count)
    elsif provider.eql?('google')
      server = node_numerate("knife #{provider}#{Spiceweasel::Config[:knife_options]} server create #{name} #{options}", i + 1, count)
    else
      server = node_numerate("knife #{provider}#{Spiceweasel::Config[:knife_options]} server create #{options}", i + 1, count)
    end
    server += " -r '#{run_list}'" unless run_list.empty?
    create_command(server, create_command_options)
  end
end
do_bulk_delete(provider) click to toggle source
# File lib/spiceweasel/nodes.rb, line 261
def do_bulk_delete(provider)
  if ['kvm', 'vsphere'].member?(provider)
    if bundler?
      delete_command("knife node#{Spiceweasel::Config[:knife_options]} list | xargs bundle exec knife #{provider} vm delete -y")
    else
      delete_command("knife node#{Spiceweasel::Config[:knife_options]} list | xargs knife #{provider} vm delete -y")
    end
  elsif ['digital_ocean'].member?(provider)
    if bundler?
      delete_command("knife node#{Spiceweasel::Config[:knife_options]} list | xargs bundle exec knife #{provider} droplet destroy -y")
    else
      delete_command("knife node#{Spiceweasel::Config[:knife_options]} list | xargs knife #{provider} droplet destroy -y")
    end
  else
    if bundler?
      delete_command("knife node#{Spiceweasel::Config[:knife_options]} list | xargs bundle exec knife #{provider} server delete -y")
    else
      delete_command("knife node#{Spiceweasel::Config[:knife_options]} list | xargs knife #{provider} server delete -y")
    end
  end
end
do_google_numeric_provider(create_command_options, names, options, provided_names, run_list) click to toggle source
# File lib/spiceweasel/nodes.rb, line 240
def do_google_numeric_provider(create_command_options, names, options, provided_names, run_list)
  names[1..-1].each do |gname|
    server = "knife google#{Spiceweasel::Config[:knife_options]} server create #{gname} #{options}"
    server += " -r '#{run_list}'" unless run_list.empty?
    create_command(server, create_command_options)
    provided_names << gname
  end
end
do_provided_names(p_name, provider) click to toggle source
# File lib/spiceweasel/nodes.rb, line 249
def do_provided_names(p_name, provider)
  if ['kvm', 'vsphere'].member?(provider)
    delete_command("knife #{provider} vm delete #{p_name} -y")
  elsif ['digital_ocean'].member?(provider)
    delete_command("knife #{provider} droplet destroy #{p_name} -y")
  else
    delete_command("knife #{provider} server delete #{p_name} -y")
  end
  delete_command("knife node#{Spiceweasel::Config[:knife_options]} delete #{p_name} -y")
  delete_command("knife client#{Spiceweasel::Config[:knife_options]} delete #{p_name} -y")
end
do_provider_members(count, nodenames, options) click to toggle source
# File lib/spiceweasel/nodes.rb, line 152
def do_provider_members(count, nodenames, options)
  options.split.each do |opt|
    if opt =~ /^-N|^--node-name/
      optname = opt.sub(/-N|--node-name/, '').lstrip
      optname = options.split[options.split.find_index(opt) + 1] if optname.empty?
      count.to_i.times do |i|
        nodenames.push(node_numerate(optname, i + 1, count))
      end
    end
  end
end
node_names_flatten(create_command_options, node, run_list) click to toggle source
# File lib/spiceweasel/nodes.rb, line 164
def node_names_flatten(create_command_options, node, run_list)
  if File.directory?('nodes/')
    if File.exist?("nodes/#{node}.json")
      validate_node_file(node) unless Spiceweasel::Config[:novalidation]
      servercommand = "knife node from file #{node}.json #{Spiceweasel::Config[:knife_options]}".rstrip
    else
      STDERR.puts "'nodes/#{node}.json' not found, unable to validate or load node. Using 'knife node create' instead."
      servercommand = "knife node create -d #{node} #{Spiceweasel::Config[:knife_options]}".rstrip
    end
  else
    STDERR.puts "'nodes' directory not found, unable to validate or load nodes. Using 'knife node create' instead."
    servercommand = "knife node create -d #{node} #{Spiceweasel::Config[:knife_options]}".rstrip
  end
  create_command(servercommand, create_command_options)
  create_command("knife node run_list set #{node} '#{run_list}'", create_command_options) unless run_list.empty?
  delete_command("knife node#{Spiceweasel::Config[:knife_options]} delete #{node} -y")
  delete_command("knife client#{Spiceweasel::Config[:knife_options]} delete #{node} -y")
end
node_numerate(name, num, count) click to toggle source

replace the {{n}} with the zero padding number

# File lib/spiceweasel/nodes.rb, line 496
def node_numerate(name, num, count)
  digits = count.to_s.length + 1
  pad = sprintf("%0#{digits}i", num)
  name.gsub(/\{\{n\}\}/, pad)
end
process_chef_client(names, options, run_list) click to toggle source
# File lib/spiceweasel/nodes.rb, line 335
def process_chef_client(names, options, run_list) # rubocop:disable CyclomaticComplexity
  commands = []
  environment = nil
  protocol = 'ssh'
  protooptions = ''
  # protocol options
  sudo = nil
  value = nil # store last option for space-separated values
  options.split.each do |opt|
    sudo = 'sudo ' if opt =~ /^--sudo$/
    protooptions += '--no-host-key-verify ' if opt =~ /^--no-host-key-verify$/
    # SSH identity file used for authentication
    if value =~ /^-i$|^--identity-file$/
      protooptions += "-i #{opt} "
      value = nil
    end
    if opt =~ /^-i|^--identity-file/
      if opt =~ /^-i$|^--identity-file$/
        value = '-i'
      else
        opt.sub!(/-i/, '') if opt =~ /^-i/
        opt.sub!(/--identity-file/, '') if opt =~ /^--identity-file/
        protooptions += "-i #{opt} "
        value = nil
      end
    end
    # ssh gateway
    if value =~ /^-G$|^--ssh-gateway$/
      protooptions += "-G #{opt} "
      value = nil
    end
    if opt =~ /^-G|^--ssh-gateway/
      if opt =~ /^-G$|^--ssh-gateway$/
        value = '-G'
      else
        opt.sub!(/-G/, '') if opt =~ /^-G/
        opt.sub!(/--ssh-gateway/, '') if opt =~ /^--ssh-gateway/
        protooptions += "-G #{opt} "
        value = nil
      end
    end
    # ssh password
    if value =~ /^-P$|^--ssh-password$/
      protooptions += "-P #{opt} "
      value = nil
    end
    if opt =~ /^-P|^--ssh-password/
      if opt =~ /^-P$|^--ssh-password$/
        value = '-P'
      else
        opt.sub!(/-P/, '') if opt =~ /^-P/
        opt.sub!(/--ssh-password/, '') if opt =~ /^--ssh-password/
        protooptions += "-P #{opt} "
        value = nil
      end
    end
    # ssh port
    if value =~ /^-p$|^--ssh-port$/
      protooptions += "-p #{opt} "
      value = nil
    end
    if opt =~ /^-p|^--ssh-port/
      if opt =~ /^-p$|^--ssh-port$/
        value = '-p'
      else
        opt.sub!(/-p/, '') if opt =~ /^-p/
        opt.sub!(/--ssh-port/, '') if opt =~ /^--ssh-port/
        protooptions += "-p #{opt} "
        value = nil
      end
    end
    # ssh username
    if value =~ /^-x$|^--ssh-user$/
      protooptions += "-x #{opt} "
      sudo = 'sudo ' unless opt.eql?('root')
      value = nil
    end
    if opt =~ /^-x|^--ssh-user/
      if opt =~ /^-x$|^--ssh-user$/
        value = '-x'
      else
        opt.sub!(/-x/, '') if opt =~ /^-x/
        opt.sub!(/--ssh-user/, '') if opt =~ /^--ssh-user/
        protooptions += "-x #{opt} "
        sudo = 'sudo ' unless opt.eql?('root')
        value = nil
      end
    end
    # environment
    if value =~ /^-E$|^--environment$/
      environment = opt
      value = nil
    end
    if opt =~ /^-E|^--environment/
      if opt =~ /^-E$|^--environment$/
        value = '-E'
      else
        opt.sub!(/-E/, '') if opt =~ /^-E/
        opt.sub!(/--environment/, '') if opt =~ /^--environment/
        environment = opt
        value = nil
      end
    end
    # nodename
    if value =~ /^-N$|^--node-name$/
      names = [opt.gsub(/{{n}}/, '*')]
      value = nil
    end
    if opt =~ /^-N|^--node-name/
      if opt =~ /^-N$|^--node-name$/
        value = '-N'
      else
        opt.sub!(/-N|--node-name/, '') if opt =~ /^-N|^--node-name/
        names = [opt.gsub(/{{n}}/, '*')]
        value = nil
      end
    end
  end
  if names[0].start_with?('windows_')
    # windows node bootstrap support
    protocol = names.shift.split('_')[1] # split on 'windows_ssh' etc
    sudo = nil # no sudo for Windows even if ssh is used
  end
  names = [] if PROVIDERS.member?(names[0])
  # check options for -N, override name
  protooptions  += "-a #{Spiceweasel::Config[:attribute]}" if Spiceweasel::Config[:attribute]
  if names.empty?
    search = chef_client_search(nil, run_list, environment)
    commands.push("knife #{protocol} '#{search}' '#{sudo}chef-client' #{protooptions} #{Spiceweasel::Config[:knife_options]}")
  else
    names.each do |name|
      search = chef_client_search(name, run_list, environment)
      commands.push("knife #{protocol} '#{search}' '#{sudo}chef-client' #{protooptions} #{Spiceweasel::Config[:knife_options]}")
    end
  end
  commands
end
process_nodes_only(names, options, run_list, create_command_options) click to toggle source

handle –nodes-only

# File lib/spiceweasel/nodes.rb, line 137
def process_nodes_only(names, options, run_list, create_command_options) # rubocop:disable CyclomaticComplexity
  nodenames = []
  if PROVIDERS.member?(names[0])
    count = names.length == 2 ? names[1] : 1
    do_provider_members(count, nodenames, options)
  elsif names[0].start_with?('windows_')
    nodenames.push(names[1..-1])
  else # standard nodes
    nodenames.push(names)
  end
  nodenames.flatten.each do |node|
    node_names_flatten(create_command_options, node, run_list)
  end
end
process_parallel(count, create_command_options, name, options, provider, run_list) click to toggle source
# File lib/spiceweasel/nodes.rb, line 283
def process_parallel(count, create_command_options, name, options, provider, run_list)
  parallel = "seq #{count} | parallel -u -j 0 -v -- "
  if provider.eql?('vsphere')
    if bundler?
      parallel += "bundle exec knife #{provider}#{Spiceweasel::Config[:knife_options]} vm clone #{options}".gsub(/\{\{n\}\}/, '{}')
    else
      parallel += "knife #{provider}#{Spiceweasel::Config[:knife_options]} vm clone #{options}".gsub(/\{\{n\}\}/, '{}')
    end
  elsif provider.eql?('kvm')
    if bundler?
      parallel += "bundle exec knife #{provider}#{Spiceweasel::Config[:knife_options]} vm create #{options}".gsub(/\{\{n\}\}/, '{}')
    else
      parallel += "knife #{provider}#{Spiceweasel::Config[:knife_options]} vm create #{options}".gsub(/\{\{n\}\}/, '{}')
    end
  elsif provider.eql?('digital_ocean')
    if bundler?
      parallel += "bundle exec knife #{provider}#{Spiceweasel::Config[:knife_options]} droplet create #{options}".gsub(/\{\{n\}\}/, '{}')
    else
      parallel += "knife #{provider}#{Spiceweasel::Config[:knife_options]} droplet create #{options}".gsub(/\{\{n\}\}/, '{}')
    end
  elsif provider.eql?('google')
    if bundler?
      parallel += "bundle exec knife #{provider}#{Spiceweasel::Config[:knife_options]} server create #{name} #{options}".gsub(/\{\{n\}\}/, '{}')
    else
      parallel += "knife #{provider}#{Spiceweasel::Config[:knife_options]} server create #{name} #{options}".gsub(/\{\{n\}\}/, '{}')
    end
  else
    if bundler?
      parallel += "bundle exec knife #{provider}#{Spiceweasel::Config[:knife_options]} server create #{options}".gsub(/\{\{n\}\}/, '{}')
    else
      parallel += "knife #{provider}#{Spiceweasel::Config[:knife_options]} server create #{options}".gsub(/\{\{n\}\}/, '{}')
    end
  end
  parallel += " -r '#{run_list}'" unless run_list.empty?
  create_command(parallel, create_command_options)
end
process_providers(names, count, name, options, run_list, create_command_options, knifecommands) click to toggle source

manage all the provider logic

# File lib/spiceweasel/nodes.rb, line 196
def process_providers(names, count, name, options, run_list, create_command_options, knifecommands) # rubocop:disable CyclomaticComplexity
  provider = names[0]
  validate_provider(provider, names, count, options, knifecommands) unless Spiceweasel::Config[:novalidation]
  provided_names = []
  if name.nil? && options.split.index('-N') # pull this out for deletes
    name = options.split[options.split.index('-N') + 1]
    count.to_i.times { |i| provided_names << node_numerate(name, i + 1, count) } if name
  end

  # google can have names or numbers
  if provider.eql?('google') && names[1].to_i == 0
    do_google_numeric_provider(create_command_options, names, options, provided_names, run_list)
  elsif Spiceweasel::Config[:parallel]
    process_parallel(count, create_command_options, name, options, provider, run_list)
  else
    determine_cloud_provider(count, create_command_options, name, options, provider, run_list)
  end
  if Spiceweasel::Config[:bulkdelete] && provided_names.empty?
    do_bulk_delete(provider)
  else
    provided_names.each do |p_name|
      do_provided_names(p_name, provider)
    end
  end
end
process_run_list(run_list) click to toggle source

standardize the node run_list formatting

# File lib/spiceweasel/nodes.rb, line 488
def process_run_list(run_list)
  return '' if run_list.nil?
  run_list.gsub!(/ /, ',')
  run_list.gsub!(/,+/, ',')
  run_list
end
validate_node_file(name) click to toggle source

validate individual node files

# File lib/spiceweasel/nodes.rb, line 184
def validate_node_file(name)
  # read in the file
  node = Chef::JSONCompat.from_json(IO.read("nodes/#{name}.json"))

  # check the node name vs. contents of the file
  return unless node['name'] != name

  STDERR.puts "ERROR: Node '#{name}' listed in the manifest does not match the name '#{node['name']}' within the nodes/#{name}.json file."
  exit(-1)
end
validate_options(node, options, environments) click to toggle source

for now, just check that -E is legit

# File lib/spiceweasel/nodes.rb, line 126
def validate_options(node, options, environments)
  if options =~ /-E/ # check for environments
    env = options.split('-E')[1].split[0]
    unless environments.member?(env)
      STDERR.puts "ERROR: '#{node}' environment '#{env}' is missing from the list of environments in the manifest."
      exit(-1)
    end
  end
end
validate_provider(provider, names, _count, options, knifecommands) click to toggle source

check that the knife plugin is installed

# File lib/spiceweasel/nodes.rb, line 321
def validate_provider(provider, names, _count, options, knifecommands)
  unless knifecommands.index { |x| x.start_with?("knife #{provider}") }
    STDERR.puts "ERROR: 'knife #{provider}' is not a currently installed plugin for knife."
    exit(-1)
  end

  return unless provider.eql?('google')

  return unless names[1].to_i != 0 && !options.split.member?('-N')

  STDERR.puts "ERROR: 'knife google' currently requires providing a name. Please use -N within the options."
  exit(-1)
end
validate_run_list(node, run_list, cookbooks, roles) click to toggle source

ensure run_list contents are listed previously.

# File lib/spiceweasel/nodes.rb, line 102
def validate_run_list(node, run_list, cookbooks, roles)
  run_list.split(',').each do |item|
    if item.start_with?('recipe[')
      # recipe[foo] or recipe[foo::bar]
      cb = item.split(/\[|\]/)[1].split(':')[0]
      unless cookbooks.member?(cb)
        STDERR.puts "ERROR: '#{node}' run list cookbook '#{cb}' is missing from the list of cookbooks in the manifest."
        exit(-1)
      end
    elsif item.start_with?('role[')
      # role[blah]
      role = item.split(/\[|\]/)[1]
      unless roles.member?(role)
        STDERR.puts "ERROR: '#{node}' run list role '#{role}' is missing from the list of roles in the manifest."
        exit(-1)
      end
    else
      STDERR.puts "ERROR: '#{node}' run list '#{item}' is an invalid run list entry in the manifest."
      exit(-1)
    end
  end
end