module AnsibleSpec

Reference github.com/serverspec/serverspec/blob/master/lib/serverspec/setup.rb Reference License (MIT) github.com/serverspec/serverspec/blob/master/LICENSE.txt

Constants

VERSION

Public Class Methods

find_group_vars_file(hosts_childrens, hosts) click to toggle source
# File lib/ansible_spec/load_ansible.rb, line 472
def self.find_group_vars_file(hosts_childrens, hosts)
    target_host = hosts_childrens.select { |key, value|
      value["children"].include?(hosts)
    }
    target_host.keys[0]
end
flatten_role(roles) click to toggle source

flatten roles (Issue 29) param: Array

e.g. ["nginx"]
e.g. [{"role"=>"nginx"}]
e.g. [{"role"=>"nginx", "dir"=>"/opt/b", "port"=>5001}]

return: Array

e.g.["nginx"]
# File lib/ansible_spec/load_ansible.rb, line 283
def self.flatten_role(roles)
  ret = Array.new
  if roles
    roles.each do |role|
      if role.is_a?(String)
        ret << role
      elsif role.is_a?(Hash)
        ret << role["role"] if role.has_key?("role")
      end
    end
  end
  return ret
end
get_dynamic_inventory(file) click to toggle source

param filename

{"databases":{"hosts":["aaa.com","bbb.com"],"vars":{"a":true}}}
{"webservers":["aaa.com","bbb.com"]}

return: Hash {“databases”=>[{“uri” => “aaa.com”, “port” => 22}, {“uri” => “bbb.com”, “port” => 22}]}

# File lib/ansible_spec/load_ansible.rb, line 122
def self.get_dynamic_inventory(file)
  if file[0] == "/"
    file_path = file
  else
    file_path = "./#{file}"
  end
  res = Hash.new
  so, se, st = Open3.capture3(file_path)
  dyn_inv = Oj.load(so.to_s)

  res["hosts_childrens"] = dyn_inv.select do |property, value|
    value.instance_of?(Hash) && value.has_key?("children")
  end

  if dyn_inv.key?('_meta')
    # assume we have an ec2.py created dynamic inventory
    dyn_inv = dyn_inv.tap{ |h| h.delete("_meta") }
  end
  dyn_inv.each{|k,v|
    res["#{k.to_s}"] = Array.new unless res.has_key?("#{k.to_s}")
    if v.is_a?(Array)
      # {"webservers":["aaa.com","bbb.com"]}
      v.each {|host|
        res["#{k.to_s}"] << {"uri"=> host, "port"=> 22}
      }
    elsif v.has_key?("hosts") && v['hosts'].is_a?(Array)
      v['hosts'].each {|host|
        res["#{k.to_s}"] << {"uri"=> host, "port"=> 22}
      }
    end
  }
  return res
end
get_hash_behaviour() click to toggle source

param: none return: hash_behaviour

# File lib/ansible_spec/load_ansible.rb, line 311
def self.get_hash_behaviour()
  f = '.ansiblespec'
  y = nil
  if File.exist?(f)
    y = YAML.load_file(f)
  end
  hash_behaviour = 'replace'
  if ENV["HASH_BEHAVIOUR"]
    hash_behaviour = ENV["HASH_BEHAVIOUR"]
  elsif y.is_a?(Array) && y[0]['hash_behaviour']
    hash_behaviour = y[0]['hash_behaviour']
  end
  if !['replace','merge'].include?(hash_behaviour)
    puts "Error: hash_behaviour '" + hash_behaviour + "' should be 'replace' or 'merge' See https://github.com/volanja/ansible_spec"
    exit 1
  end
  return hash_behaviour
end
get_inventory_param(line) click to toggle source

param ansible_ssh_port=22 return: hash

# File lib/ansible_spec/load_ansible.rb, line 158
def self.get_inventory_param(line)
  host = Hash.new
  # 初期値
  host['name'] = line
  host['port'] = 22
  host['connection'] = "ssh"
  if line.include?(":") # 192.168.0.1:22
    host['uri']  = line.split(":")[0]
    host['port'] = line.split(":")[1].to_i
    return host
  end
  # 192.168.0.1 ansible_ssh_port=22
  line.split.each{|v|
    unless v.include?("=")
      host['uri'] = v
    else
      key,value = v.split("=")
      host['port'] = value.to_i if key == "ansible_ssh_port" or key == "ansible_port"
      host['private_key'] = value if key == "ansible_ssh_private_key_file"
      host['user'] = value if key == "ansible_ssh_user" or key == "ansible_user"
      host['uri'] = value if key == "ansible_ssh_host" or key == "ansible_host"
      host['pass'] = value if key == "ansible_ssh_pass"
      host['connection'] = value if key == "ansible_connection"
    end
  }
  return host
end
get_parent(hash,search,k) click to toggle source

param hash {“server”=>, “databases”=>, “pg:children”=>[“server”, “databases”]} param search “:children” param k “pg:children” return {“server”=>, “databases”=>, “pg”=>[“192.168.0.103”, “192.168.0.104”]}

# File lib/ansible_spec/load_ansible.rb, line 107
def self.get_parent(hash,search,k)
  k_parent = k.gsub(search,'')
  arry = Array.new
  hash["#{k}"].each{|group|
    arry = arry + hash["#{group}"]
  }
  h = Hash.new
  h["#{k_parent}"] = arry
  return h
end
get_properties() click to toggle source

return: json {“name”=>“Ansible-Sample-TDD”, “hosts”=>, “user”=>“root”, “roles”=>[“nginx”, “mariadb”]}

# File lib/ansible_spec/load_ansible.rb, line 420
def self.get_properties()
  playbook, inventoryfile = load_ansiblespec

  # load inventory file and playbook hosts mapping
  hosts = load_targets(inventoryfile)
  properties = load_playbook(playbook)
  properties.each do |var|
    var["hosts_childrens"] = hosts["hosts_childrens"]
    var["group"] = var["hosts"]
    if var["hosts"].to_s == "all"
      var["hosts"] = hosts.values.flatten
    elsif hosts.has_key?("#{var["hosts"]}")
      var["hosts"] = hosts["#{var["hosts"]}"]
    elsif var["hosts"].instance_of?(Array)
      tmp_host = var["hosts"]
      var["hosts"] = []
      tmp_host.each do |v|
        if hosts.has_key?("#{v}")
          hosts["#{v}"].map {|target_server| target_server["hosts"] = v}
          var["hosts"].concat hosts["#{v}"]
        end
      end
      if var["hosts"].size == 0
        properties = properties.compact.reject{|e| e["hosts"].length == 0}
        #puts "#{var["name"]} roles no hosts matched for #{var["hosts"]}"
      end
    else
      puts "no hosts matched for #{var["hosts"]}"
      var["hosts"] = []
    end
  end
  return properties
end
get_ssh_config_file() click to toggle source

param: none return: file path

# File lib/ansible_spec/load_ansible.rb, line 332
def self.get_ssh_config_file()
  ssh_config_file = nil

  cfg = AnsibleSpec::AnsibleCfg.new
  ssh_args = cfg.get('ssh_connection', 'ssh_args')
  if ssh_args
    array = ssh_args.split(" ")
    if array.index("-F") && array[array.index("-F") + 1]
      ssh_config_file = array[array.index("-F") + 1]
    end
  end

  if ENV["SSH_CONFIG_FILE"]
    ssh_config_file = ENV["SSH_CONFIG_FILE"]
  end

  return nil if ssh_config_file.nil?

  if File.exist?(ssh_config_file)
    return ssh_config_file
  else
    return nil
  end
end
get_variables(host, group_idx, hosts=nil) click to toggle source
# File lib/ansible_spec/load_ansible.rb, line 516
def self.get_variables(host, group_idx, hosts=nil)
  vars = {}
  p = self.get_properties.compact.reject{|e| e["hosts"].length == 0}

  # roles default
  p[group_idx]['roles'].each do |role|
    vars = load_vars_file(vars ,"roles/#{role}/defaults/main.yml")
  end

  # get parent directory of group_vars and host_vars directories
  vars_dirs_path = get_vars_dirs_path
  if vars_dirs_path != ''
    vars_dirs_path = "#{vars_dirs_path}/"
  end

  # all group
  vars = load_vars_file(vars ,"#{vars_dirs_path}group_vars/all", true)

  # each group vars
  if p[group_idx].has_key?('group')
    # get groups parent child relationships
    playbook, inventoryfile = load_ansiblespec
    groups_rels = load_targets(inventoryfile, return_type='groups_parent_child_relationships')
    # get parental lineage
    g = p[group_idx]['group']
    groups_stack = Array.new
    groups_stack << g
    groups_rels.keys.each{|k|
      groups_stack << k if (groups_rels[k].include?(g))
    }
    # get vars from parents groups then child group
    groups_parents_then_child = groups_stack.reverse.flatten
    groups_parents_then_child.each{|group|
      vars = load_vars_file(vars ,"#{vars_dirs_path}group_vars/#{group}", true)
    }
  end

  # each host vars
  vars = load_vars_file(vars ,"#{vars_dirs_path}host_vars/#{host}", true)

  # site vars
  if p[group_idx].has_key?('vars')
    vars = merge_variables(vars, p[group_idx]['vars'])
  end

  # roles vars
  p[group_idx]['roles'].each do |role|
    vars = load_vars_file(vars ,"roles/#{role}/vars/main.yml")
  end

  # multiple host and children dependencies group vars
  unless hosts.nil? || p[group_idx]["hosts_childrens"].nil?
    hosts_childrens = p[group_idx]["hosts_childrens"]
    next_find_target = hosts
    while(!next_find_target.nil? && hosts_childrens.size > 0)
      vars = load_vars_file(vars ,"#{vars_dirs_path}group_vars/#{next_find_target}", true)
      group_vars_file = find_group_vars_file(hosts_childrens,next_find_target)
      next_find_target = group_vars_file
      hosts_childrens.delete(group_vars_file)
    end
  end

  return resolve_variables(vars)

end
get_vars_dirs_path() click to toggle source

param: none return: vars_dirs_path

# File lib/ansible_spec/load_ansible.rb, line 456
def self.get_vars_dirs_path()
  f = '.ansiblespec'
  y = nil
  if File.exist?(f)
    y = YAML.load_file(f)
  end
  if ENV["VARS_DIRS_PATH"]
    vars_dirs_path = ENV["VARS_DIRS_PATH"]
  elsif y.is_a?(Array) && y[0]['vars_dirs_path']
    vars_dirs_path = y[0]['vars_dirs_path']
  else
    vars_dirs_path = ''
  end
  return vars_dirs_path
end
load_ansiblespec() click to toggle source

param: none return: playbook, inventoryfile

# File lib/ansible_spec/load_ansible.rb, line 188
def self.load_ansiblespec()
  f = '.ansiblespec'
  y = nil
  if File.exist?(f)
    y = YAML.load_file(f)
  end
  if ENV["PLAYBOOK"]
    playbook = ENV["PLAYBOOK"]
  elsif y.is_a?(Array) && y[0]['playbook']
    playbook = y[0]['playbook']
  else
    playbook = 'site.yml'
  end
  if ENV["INVENTORY"]
    inventoryfile = ENV["INVENTORY"]
  elsif y.is_a?(Array) && y[0]['inventory']
    inventoryfile = y[0]['inventory']
  else
    inventoryfile = 'hosts'
  end
  if File.exist?(playbook) == false
    puts 'Error: ' + playbook + ' is not Found. create site.yml or ./.ansiblespec  See https://github.com/volanja/ansible_spec'
    exit 1
  elsif File.exist?(inventoryfile) == false
    puts 'Error: ' + inventoryfile + ' is not Found. create hosts or ./.ansiblespec  See https://github.com/volanja/ansible_spec'
    exit 1
  end
  return playbook, inventoryfile
end
load_dependencies(role, rolepath='roles') click to toggle source

param: role return: [“role1”, “role2”]

# File lib/ansible_spec/load_ansible.rb, line 220
def self.load_dependencies(role, rolepath='roles')
  role_queue = [role]
  deps = []
  until role_queue.empty?
    role = role_queue.pop()
    path = File.join(rolepath, role, "meta", "main.yml")

    if File.exist?(path)
      dependencies = YAML.load_file(path).fetch("dependencies", [])
      unless dependencies.nil?
        new_deps = dependencies.map { |h|
          h["role"] || h
        }
        role_queue.concat(new_deps)
        deps.concat(new_deps)
      end
    end
  end
  return deps
end
load_encrypted_file(vars_file) click to toggle source

param: variable file return: be merged hash

# File lib/ansible_spec/load_ansible.rb, line 394
def self.load_encrypted_file(vars_file)
  cfg = AnsibleSpec::AnsibleCfg.new
  vault_password_file = cfg.get('defaults', 'vault_password_file')
  if vault_password_file
    vault_password = File.open(vault_password_file).read.chomp
    yaml = YAML.load(Ansible::Vault.read(path: vars_file, password: vault_password))
  end
  return yaml
end
load_playbook(f) click to toggle source

param: playbook return: json

{"name"=>"Ansible-Sample-TDD", "hosts"=>"server", "user"=>"root", "roles"=>["nginx", "mariadb"]}
# File lib/ansible_spec/load_ansible.rb, line 244
def self.load_playbook(f)
  playbook = YAML.load_file(f)

  # e.g. comment-out
  if playbook === false
    puts "Error: No data in #{f}"
    exit
  end
  properties = Array.new
  playbook.each do |site|
    if site.has_key?("include")
        YAML.load_file(site["include"]).each { |site|
          properties.push site
        }
    elsif site.has_key?("import_playbook")
        YAML.load_file(site["import_playbook"]).each { |site|
          properties.push site
        }
    else
      properties.push site
    end
  end
  properties.each do |property|
    property["roles"] = flatten_role(property["roles"])
  end
  if name_exist?(properties)
    return properties
  else
    fail "Please insert name on playbook '#{f}'"
  end
end
load_targets(file, return_type = 'groups') click to toggle source

param: inventory file of Ansible param: return_type 'groups' or 'groups_parent_child_relationships' return: Hash {“group” => [“192.168.0.1”,“192.168.0.2”]} return: Hash {“group” => [{“name” => “192.168.0.1”,“uri” => “192.168.0.1”, “port” => 22},…]} return: Hash {“pg” => [“server”, “databases”]}

# File lib/ansible_spec/load_ansible.rb, line 17
def self.load_targets(file, return_type = 'groups')
  if not ['groups', 'groups_parent_child_relationships'].include?(return_type)
    raise ArgumentError, "Variable return_type must be value 'groups' or 'groups_parent_child_relationships'"
  end

  if File.executable?(file)
    return get_dynamic_inventory(file)
  end
  f = File.open(file).read
  groups = Hash.new
  group = ''
  hosts = Hash.new
  hosts.default = Hash.new
  f.each_line{|line|
    line = line.strip
    # skip
    next if line.start_with?('#') #comment
    next if line.empty? == true   #null

    # get group
    if line.start_with?('[') && line.end_with?(']')
      group = line.gsub('[','').gsub(']','')
      groups["#{group}"] = Array.new
      next
    end

    # get host
    host_name = line.split[0]
    if group.empty? == false
      if groups.has_key?(line)
        groups["#{group}"] << line
        next
      elsif host_name.include?("[") && host_name.include?("]")
        # www[01:50].example.com
        # db-[a:f].example.com
        hostlist_expression(line,":").each{|h|
          host = hosts[h.split[0]]
          groups["#{group}"] << get_inventory_param(h).merge(host)
        }
        next
      else
        # 1つのみ、かつ:を含まない場合
        # 192.168.0.1
        # 192.168.0.1 ansible_ssh_host=127.0.0.1 ...
        host = hosts[host_name]
        groups["#{group}"] << get_inventory_param(line).merge(host)
        next
      end
    else
      if host_name.include?("[") && host_name.include?("]")
        hostlist_expression(line, ":").each{|h|
          hosts[h.split[0]] = get_inventory_param(h)
        }
      else
        hosts[host_name] = get_inventory_param(line)
      end
    end
  }

  # parse children [group:children]
  search = Regexp.new(":children".to_s)
  groups_parent_child_relationships = Hash.new
  groups.keys.each{|k|
    unless (k =~ search).nil?
      # get parent child relationships
      k_parent = k.gsub(search,'')
      groups_parent_child_relationships["#{k_parent}"] = groups["#{k}"]
      # get group parent & merge parent
      groups.merge!(get_parent(groups,search,k))
      # delete group children
      if groups.has_key?("#{k}") && groups.has_key?("#{k.gsub(search,'')}")
        groups.delete("#{k}")
      end
    end
  }

  return_value = groups # default
  if return_type == 'groups'
    return_value = groups
  elsif return_type == 'groups_parent_child_relationships'
    return_value = groups_parent_child_relationships
  end

  return return_value
end
load_vars_file(vars, path, check_no_ext = false) click to toggle source

param: hash param: variable file param: flag to extention

true:  .yml extension is optional
false: must have .yml extention
# File lib/ansible_spec/load_ansible.rb, line 362
def self.load_vars_file(vars, path, check_no_ext = false)
  vars_file = path
  if check_no_ext && !File.exist?(vars_file)
    vars_file = path+".yml"
  end
  if File.exist?(vars_file)
    if File.directory?(vars_file)
      Dir.glob(File.join(vars_file, '*')).each { |f|
        vars = load_vars_file(vars, f)
      }
    else
      # you can use Ansible::Vault when use ruby 2.1.0 and higher.
      # Ansible::Vault support Ruby 2.1.0 and higher.
      if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1')
        if Ansible::Vault.encrypted?(vars_file)
          yaml = load_encrypted_file(vars_file)
        else
          yaml = YAML.load_file(vars_file)
        end
        vars = merge_variables(vars, yaml)
      else
        # Ruby 1.9 and 2.0
        yaml = YAML.load_file(vars_file)
        vars = merge_variables(vars, yaml)
      end
    end
  end
  return vars
end
main() click to toggle source
# File lib/ansible_spec.rb, line 12
def self.main()
  safe_create_spec_helper
  safe_create_rakefile
  safe_create_ansiblespec
  safe_create_rspec
end
merge_variables(vars, hash) click to toggle source

param: target hash param: be merged hash

# File lib/ansible_spec/load_ansible.rb, line 406
def self.merge_variables(vars, hash)
  hash_behaviour = get_hash_behaviour()
  if hash.kind_of?(Hash)
    if hash_behaviour=="merge"
      vars.deep_merge!(hash)
    else
      vars.merge!(hash)
    end
  end
  return vars
end
name_exist?(array) click to toggle source

Issue 27 param: array return: boolean

true: name is exist on playbook
false: name is not exist on playbook
# File lib/ansible_spec/load_ansible.rb, line 303
def self.name_exist?(array)
  array.all? do |site|
    site.has_key?("name")
  end
end
resolve_variables(vars, max_level=100) click to toggle source

query replace jinja2 templates with target values param: hash (cf. result self.get_variables) param: number of iterations if found_template return: hash

# File lib/ansible_spec/load_ansible.rb, line 483
def self.resolve_variables(vars, max_level=100)
  vars_yaml = vars.to_yaml
  level = 0
  begin
    found_template = false
    level += 1

    # query replace jinja2 templates in yaml
    # replace in-place (gsub!)
    # use non-greedy regex (.*?)
    vars_yaml.gsub!(/{{.*?}}/) do |template|

      # grab target variable
      # ignore whitespaces (\s*)
      # use non-greedy regex (.*?)
      target = template.gsub(/{{\s*(.*?)\s*}}/, '\1')
      
      # lookup value of target variable
      value = vars[target]
      
      # return lookup value if it exists
      # or leave template alone
      if value.nil? 
        template
      else 
        found_template = true
        value
      end
    end
  end while found_template and level <= max_level
  return YAML.load(vars_yaml)
end
safe_create_ansiblespec() click to toggle source
# File lib/ansible_spec.rb, line 38
def self.safe_create_ansiblespec
  content = File.open(File.dirname(__FILE__) + "/../lib/src/.ansiblespec").read
  safe_touch(".ansiblespec")
  File.open(".ansiblespec", 'w') do |f|
    f.puts content
  end
end
safe_create_rakefile() click to toggle source
# File lib/ansible_spec.rb, line 30
def self.safe_create_rakefile
  content = File.open(File.dirname(__FILE__) + "/../lib/src/Rakefile").read
  safe_touch("Rakefile")
  File.open("Rakefile", 'w') do |f|
    f.puts content
  end
end
safe_create_rspec() click to toggle source
# File lib/ansible_spec.rb, line 46
def self.safe_create_rspec
  content = File.open(File.dirname(__FILE__) + "/../lib/src/.rspec").read
  safe_touch(".rspec")
  File.open(".rspec", 'w') do |f|
    f.puts content
  end
end
safe_create_spec_helper() click to toggle source
# File lib/ansible_spec.rb, line 20
def self.safe_create_spec_helper
  content = File.open(File.dirname(__FILE__) + "/../lib/src/spec/spec_helper.rb").read
  safe_mkdir("spec")
  safe_touch("spec/spec_helper.rb")
  File.open("spec/spec_helper.rb", 'w') do |f|
    f.puts content
  end

end
safe_mkdir(dir) click to toggle source
# File lib/ansible_spec.rb, line 54
def self.safe_mkdir(dir)
  unless FileTest.exist?("#{dir}")
    FileUtils.mkdir_p("#{dir}")
    TermColor.green
    puts "\t\tcreate\t#{dir}"
    TermColor.reset
  else
    TermColor.red
    puts "\t\texists\t#{dir}"
    TermColor.reset
  end
end
safe_touch(file) click to toggle source
# File lib/ansible_spec.rb, line 67
def self.safe_touch(file)
  unless File.exists? "#{file}"
    File.open("#{file}", 'w') do |f|
        #f.puts content
    end
    TermColor.green
    puts "\t\tcreate\t#{file}"
    TermColor.reset
  else 
    TermColor.red
    puts "\t\texists\t#{file}"
    TermColor.reset
  end
end