module QB::CLI
Definitions
¶ ↑
Definitions
¶ ↑
Definitions
¶ ↑
Definitions
¶ ↑
Constants
- DEBUG_ARGS
CLI
args that common to all commands that enable debug output@return [Array<String>]
- DEFAULT_TERMINAL_WIDTH
Default terminal line width to use if we can't figure it out dynamically.
@return [Fixnum]
Public Class Methods
@todo Document ask method.
@param [type] arg_name
@todo Add name param description.
@return [return_type]
@todo Document return value.
# File lib/qb/cli.rb, line 68 def self.ask name:, description: nil, type:, default: NRSER::NO_ARG puts value = loop do puts "Enter value for #{ name }" if description puts description.indent end puts "TYPE #{ type.to_s }".indent if default puts "DEFAULT #{ default.to_s }".indent end $stdout.write '> ' value = gets.chomp QB.debug "User input", value if value == '' && default != NRSER::NO_ARG puts <<-END.dedent Using default value #{ default.to_s } END return default end begin type.from_s value rescue TypeError => e puts <<-END.dedent Input value #{ value.inspect } failed to satisfy type #{ type.to_s } END else break value end end # loop puts "Using value #{ value.inspect }" return value end
# File lib/qb/cli.rb, line 126 def self.ask_for_option role:, option: default = if role.defaults.key?(option.var_name) role.defaults[option.var_name] elsif option.required? NRSER::NO_ARG else nil end
# File lib/qb/cli/dev.rb, line 112 def self.dev cmd, *args case cmd when 'serve', 'server' Dev.serve *args.rest when 'req' Dev.req *args.rest else raise "bad .dev subcmd: #{ cmd }" end end
Show the help message.
@todo
We should have more types of help.
@return [1]
Error exit status - we don't want `qb ... && ...` to move on to the second command when we end up falling back to `help`.
# File lib/qb/cli/help.rb, line 27 def self.help args = [] metadata = if QB.gemspec.metadata && !QB.gemspec.metadata.empty? "metadata:\n" + QB.gemspec.metadata.map {|key, value| " #{ key }: #{ value }" }.join("\n") end 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 return 1 end
List available roles.
@example
qb list --user qb list -u qb list --local qb list -l qb list --system qb list -s qb list --path=:system qb list --path=./roles qb list -p ./roles qb list gem
@todo
We should have more types of help.
@return [1]
Error exit status - we don't want `qb ... && ...` to move on to the second command when we end up falling back to `help`.
# File lib/qb/cli/list.rb, line 34 def self.list pattern = nil roles = if pattern QB::Role.matches pattern else QB::Role.available end name_col_width = roles.map { |r| r.display_name.length }.max + 2 roles.each { |role| summary = role.summary.truncate QB::CLI.terminal_width - name_col_width puts ("%-#{ name_col_width }s" % role.display_name) + summary } puts return 0 end
Play an Ansible
playbook (like `state.yml`) in the QB
environment (sets up path env vars, IO streams, etc.).
@param [Array<String>] args
CLI arguments to use.
@return [Fixnum]
The `ansible-playbook` command exit code.
# File lib/qb/cli/play.rb, line 28 def self.play args if args.empty? raise "Need path to playbook in first arg." end playbook_path = QB::Util.resolve args[0] unless playbook_path.file? raise "Can't find Ansible playbook at `#{ playbook_path.to_s }`" end # By default, we won't change directories to run the command. chdir = nil # See if there is an Ansible config in the parent directories ansible_cfg_path = QB::Util.find_up \ QB::Ansible::ConfigFile::FILE_NAME, playbook_path.dirname, raise_on_not_found: false # If we did find an Ansible config, we're going to want to run in that # directory and add it to the role search path so that we merge it's # values into our env vars (otherwise they would override the config # values). unless ansible_cfg_path.nil? QB::Role::PATH.unshift ansible_cfg_path.dirname chdir = ansible_cfg_path.dirname end cmd = QB::Ansible::Cmd::Playbook.new \ chdir: chdir, playbook_path: playbook_path status = cmd.stream if status != 0 $stderr.puts "ERROR ansible-playbook failed." end exit status end
Run a QB
role.
@param [Array<String>] args
CLI args to work with.
@return [Fixnum]
Exit status code from `ansible-playbook` command, unless we invoked help or error'd out in another way before the run (in which case `1` is returned).
# File lib/qb/cli/run.rb, line 28 def self.run args role_arg = args.shift QB.debug "role arg" => 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 return help rescue QB::Role::MultipleMatchesError => e puts "ERROR - #{ e.message }\n\n" return 1 end role.check_requirements options = QB::Options.new role, args QB.debug "Role options set on cli", role: options.role_options.reject { |k, o| o.value.nil? } QB.debug "QB options", options.qb.dup QB.debug "Ansible options", options.ansible.dup cwd = Dir.getwd dir = nil if role.has_dir_arg? # 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: # if options.ask? default = begin role.default_dir cwd, options.role_options rescue QB::UserInputError => e NRSER::NO_ARG end QB::CLI.ask name: "target directory (`qb_dir`)", type: t.non_empty_str, default: default else role.default_dir cwd, options.role_options end 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 QB.debug "input_dir", dir # normalize to expanded path (has no trailing slash) dir = File.expand_path dir QB.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| QB.debug "found saved options", saved_options } else QB.debug "no saved options" {} end if saved_options.key? role.options_key role_saved_options = saved_options[role.options_key] QB.debug "found saved options for role", role_saved_options role_saved_options.each do |option_cli_name, value| option = options.role_options[option_cli_name] if option.value.nil? QB.debug "setting from saved options", option: option, value: value option.value = value end end end end # unless default_dir == false # Interactive Input # ===================================================================== if options.ask? # Incomplete raise "COMING SOON!!!...?" QB::CLI.ask_for_options role: role, options: options end # Validation # ===================================================================== # # Should have already been taken care of if we used interactive input. # # check that required options are present missing = options.role_options.values.select {|option| option.required? && option.value.nil? } unless missing.empty? puts "ERROR: options #{ missing.map {|o| o.cli_name } } are required." return 1 end set_options = options.role_options.select {|k, o| !o.value.nil?} QB.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, 'qb_user_roles_dir' => QB::USER_ROLES_DIR.to_s, } set_options.values.each do |option| playbook_role[option.var_name] = option.value_data end play = { 'hosts' => options.qb['hosts'], 'vars' => playbook_vars, # 'gather_subset' => ['!all'], 'gather_facts' => options.qb['facts'], 'pre_tasks' => [ { 'qb_facts' => { 'qb_dir' => dir, } }, ], 'roles' => [ 'nrser.blockinfile', ], } if role.meta['call_role'] logger.debug "Calling role through qb/call..." play['tasks'] = [ { 'include_role' => { 'name' => 'qb/call', }, 'vars' => { 'role' => role.name, 'args' => set_options.map { |option| [option.var_name, option.value_data] }.to_h, } } ] env = QB::Ansible::Env::Devel.new exe = [ QB::Python.bin, (QB::Ansible::Env::Devel::ANSIBLE_HOME / 'bin' / 'ansible-playbook') ].join " " else play['roles'] << playbook_role env = QB::Ansible::Env.new exe = "ansible-playbook" end if options.qb['user'] play['become'] = true play['become_user'] = options.qb['user'] end playbook = [play] logger.debug "playbook", playbook # stick the role path in front to make sure we get **that** role env.roles_path.unshift role.path.expand_path.dirname cmd = QB::Ansible::Cmd::Playbook.new \ env: env, playbook: playbook, role_options: options, chdir: (File.exists?('./ansible/ansible.cfg') ? './ansible' : nil), exe: exe # print # ===== # # print useful stuff for debugging / running outside of qb # if options.qb['print'].include? 'options' puts "SET OPTIONS:\n\n#{ YAML.dump set_options }\n\n" end if options.qb['print'].include? 'env' puts "ENV:\n\n#{ YAML.dump cmd.env.to_h }\n\n" end if options.qb['print'].include? 'cmd' puts "COMMAND:\n\n#{ cmd.prepare }\n\n" end if options.qb['print'].include? 'playbook' puts "PLAYBOOK:\n\n#{ YAML.dump playbook }\n\n" end # stop here if we're not supposed to run exit 0 if !options.qb['run'] # run # === # # stuff below here does stuff # # save the options back if ( dir && # 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 logger.debug "Command prepared, running...", command: cmd, prepared: cmd.prepare status = cmd.stream if status != 0 $stderr.puts "ERROR ansible-playbook failed." end # exit status status end
Run a setup playbook.
The path to the setup playbook can be given as the first of `args`, or `setup.qb.{yaml,yml}` will be searched in `$REPO_ROOT/dev/` and `$REPO_ROOT/`, where `$REPO_ROOT` is the Git root for the current directory.
@todo
1. While it works, this system of finding the setup files feels kind-of wonky. 2. Any additional entries in `args` after the first seem to be silently ignored. Seems like we should do something with them (run all of them?) or error.
@param [Array<String>] args
Either: 1. Empty, in which case we search for the setup playbook as detailed above. 2. Contains a single path to the setup playbook.
@return [Fixnum]
The `ansible-playbook` command exit code.
# File lib/qb/cli/setup.rb, line 49 def self.setup args = [] # Figure out project root and setup playbook path case args[0] when String, Pathname # The playbook path has been provided, use that to find the project root playbook_path = QB::Util.resolve args[0] project_root = NRSER.git_root playbook_path when nil # Figure the project root out from the current directory, then # form the playbook path from that project_root = NRSER.git_root '.' playbook_path = Util.find_yaml_file! \ dirs: [ project_root.join( 'dev' ), project_root, ], basename: 'setup.qb' else raise TypeError.new binding.erb <<-END First entry of `args` must be nil, String or Pathname, found: <%= args[0].pretty_inspect %> args: <%= args.pretty_inspect %> END end unless playbook_path.file? raise "Can't find QB setup playbook at `#{ playbook_path.to_s }`" end cmd = QB::Ansible::Cmd::Playbook.new \ chdir: project_root, extra_vars: { project_root: project_root, qb_dir: project_root, qb_cwd: Pathname.getwd, qb_user_roles_dir: QB::USER_ROLES_DIR, }, playbook_path: playbook_path puts cmd.prepare status = cmd.stream if status != 0 $stderr.puts "ERROR QB setup failed." end exit status end