#!/usr/bin/env ruby require 'pathname' require 'pp' require 'yaml' require 'json' require 'fileutils' require 'cmds' require 'qb' # constants # ========= ROOT = QB::ROOT ROLES_DIR = QB::ROLES_DIR ROLES = Pathname.glob(ROLES_DIR + 'qb.*').map {|path| path.basename.to_s} DEBUG_ARGS = ['-D', '--debug'] TMP_DIR = ROOT + 'tmp' # globals # ======= # @api util # *pure* # # format a debug message with optional key / values to print # # @param msg [String] message to print. # @param dump [Hash] optional hash of keys and values to dump. def format msg, dump = {} unless dump.empty? msg += "\n" + dump.map {|k, v| " #{ k }: #{ v.inspect }" }.join("\n") end msg end def debug *args QB.debug *args end def set_debug! args if DEBUG_ARGS.any? {|arg| args.include? arg} QB.debug = true debug "ON" DEBUG_ARGS.each {|arg| args.delete arg} end end # needed for to clean the env if using bundler (like in dev) def with_clean_env &block if defined? Bundler Bundler.with_clean_env &block else block.call end end def metadata if QB.gemspec.metadata && !QB.gemspec.metadata.empty? "metadata:\n" + QB.gemspec.metadata.map {|key, value| " #{ key }: #{ value }" }.join("\n") + "\n" end end def help puts <<-END version: #{ QB::VERSION } #{ metadata } syntax: qb ROLE [OPTIONS] DIRECTORY use `qb ROLE -h` for role options. available roles: END puts QB::Role.available puts exit 1 end def main args set_debug! args debug args: args role_arg = args.shift debug "role arg" => role_arg help if role_arg.nil? || ['-h', '--help', 'help'].include?(role_arg) begin role = QB::Role.require role_arg rescue QB::Role::NoMatchesError => e puts "ERROR - #{ e.message }\n\n" # exits with status code 1 help rescue QB::Role::MultipleMatchesError => e puts "ERROR - #{ e.message }\n\n" exit 1 end options, qb_options = QB::Options.parse! role, args debug "options set on cli", options.select {|k, o| !o.value.nil?} debug "qb options", qb_options cwd = Dir.getwd # get the target dir dir = case args.length when 0 # in this case, a dir has not been provided # # in some cases (like projects) the dir can be figured out in other ways: # QB.get_default_dir role, cwd, options when 1 # there is a single positional arg, which is used as dir args[0] else # there are multiple positional args, which is not allowed raise "can't supply more than one argument: #{ args.inspect }" end debug "input_dir", dir # normalize to expanded path (has no trailing slash) dir = File.expand_path dir debug "normalized_dir", dir # create the dir if it doesn't exist (so don't have to cover this in # every role) if role.mkdir FileUtils.mkdir_p dir unless File.exists? dir end saved_options_path = Pathname.new(dir) + '.qb-options.yml' saved_options = if saved_options_path.exist? # convert old _ separated names to - separated YAML.load(saved_options_path.read).map {|role_options_key, role_options| [ role_options_key, role_options.map {|name, value| [QB::Options.cli_ize_name(name), value] }.to_h ] }.to_h.tap {|saved_options| debug "found saved options", saved_options } else debug "no saved options" {} end if saved_options.key? role.options_key role_saved_options = saved_options[role.options_key] debug "found saved options for role", role_saved_options role_saved_options.each do |option_cli_name, value| option = options[option_cli_name] if option.value.nil? debug "setting from saved options", option: option, value: value option.value = value end end end set_options = options.select {|k, o| !o.value.nil?} debug "set options", set_options playbook_role = {'role' => role.name} playbook_vars = { 'qb_dir' => dir, # depreciated due to mass potential for conflict 'dir' => dir, 'qb_cwd' => cwd, } set_options.values.each do |option| playbook_role[option.var_name] = option.value end play = { 'hosts' => qb_options['hosts'], 'vars' => playbook_vars, 'pre_tasks' => [ # need ansible 2.1.2.0 at least to run { 'assert' => { 'that' => "'ansible 2.1.2' in lookup('pipe', 'ansible --version')", }, }, { 'qb_facts' => { 'qb_dir' => dir, } }, ], 'roles' => [ 'nrser.blockinfile', playbook_role ], } if qb_options['user'] play['become'] = true play['become_user'] = qb_options['user'] end playbook = [play] debug "playbook", playbook playbook_path = ROOT + '.qb-playbook.yml' debug playbook_path: playbook_path.to_s playbook_path.open('w') do |f| f.write YAML.dump(playbook) end # save the options back if ( # we set some options that we can save set_options.values.select {|o| o.save? }.length > 0 && # the role says to save options role.save_options ) saved_options[role.options_key] = set_options.select{|key, option| option.save? }.map {|key, option| [key, option.value] }.to_h unless saved_options_path.dirname.exist? FileUtils.mkdir_p saved_options_path.dirname end saved_options_path.open('w') do |f| f.write YAML.dump(saved_options) end end tmp_roles_path = QB::ROOT + 'tmp' + 'roles' ansible_roles_path = ( [tmp_roles_path] + QB::Role.search_path ).join(':') Dir.chdir ROOT do # install requirements # unless (TMP_DIR + 'roles').directory? # with_clean_env do # Cmds.stream! "ANSIBLE_ROLES_PATH=<%= roles_path %> ansible-galaxy install --ignore-errors -r <%= req_path%>", # req_path: (ROOT + 'requirements.yml'), # roles_path: tmp_roles_path.to_s # end # end # the `gem` ansible module doesn't work right when the bunlder env vars # are set... so we need to clear them. with_clean_env do Cmds.stream! "ANSIBLE_ROLES_PATH=<%= roles_path %> ansible-playbook <%= playbook_path %>", roles_path: ansible_roles_path, playbook_path: playbook_path.to_s end end end main(ARGV) # if __FILE__ == $0 # doesn't work with gem stub or something?