Module: QB::CLI
- Includes:
- NRSER::Log::Mixin
- Defined in:
- lib/qb/cli/dev.rb,
lib/qb/cli.rb,
lib/qb/cli/run.rb,
lib/qb/cli/help.rb,
lib/qb/cli/list.rb,
lib/qb/cli/play.rb,
lib/qb/cli/setup.rb
Overview
Definitions
Defined Under Namespace
Modules: Dev
Constant Summary collapse
- DEBUG_ARGS =
CLI args that common to all commands that enable debug output
['-D', '--DEBUG'].freeze
- DEFAULT_TERMINAL_WIDTH =
Default terminal line width to use if we can't figure it out dynamically.
80
Class Method Summary collapse
- .ask(name:, description: nil, type:, default: NRSER::NO_ARG) ⇒ return_type
- .ask_for_option(role:, option:) ⇒ Object
- .ask_for_options(role:, options:) ⇒ Object
- .dev(cmd, *args) ⇒ Object
-
.dynamic_width ⇒ Object
Calculate the dynamic width of the terminal.
- .dynamic_width_stty ⇒ Object
- .dynamic_width_tput ⇒ Object
-
.help(args = []) ⇒ 1
Show the help message.
-
.list(pattern = nil) ⇒ 1
List available roles.
-
.play(args) ⇒ Fixnum
Play an Ansible playbook (like
state.yml
) in the QB environment (sets up path env vars, IO streams, etc.). -
.run(args) ⇒ Fixnum
Run a QB role.
-
.set_debug!(args) ⇒ Object
Module (Static) Methods ============================================================================.
-
.setup(args = []) ⇒ Fixnum
Run a setup playbook.
-
.terminal_width ⇒ Object
This code was copied from Rake, available under MIT-LICENSE Copyright (c) 2003, 2004 Jim Weirich.
- .unix? ⇒ Boolean
Class Method Details
.ask(name:, description: nil, type:, default: NRSER::NO_ARG) ⇒ return_type
Document ask method.
Returns @todo Document return value.
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# 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 " \n Using default value \#{ default.to_s }\n \n END\n \n return default\n end\n \n begin\n type.from_s value\n rescue TypeError => e\n puts <<-END.dedent\n Input value \#{ value.inspect } failed to satisfy type\n \n \#{ type.to_s }\n \n END\n else\n break value\n end\n \n end # loop\n \n puts \"Using value \#{ value.inspect }\"\n \n return value\n \nend\n".dedent |
.ask_for_option(role:, option:) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# 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 ask name: option.name, description: option.description, default: default # type: end |
.ask_for_options(role:, options:) ⇒ Object
142 143 144 145 146 |
# File 'lib/qb/cli.rb', line 142 def self. role:, options: .select { |opt| opt.value.nil? }.each { |option| ask_for_option role: role, option: option } end |
.dev(cmd, *args) ⇒ Object
112 113 114 115 116 117 118 119 120 121 |
# 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 |
.dynamic_width ⇒ Object
Calculate the dynamic width of the terminal
172 173 174 |
# File 'lib/qb/cli.rb', line 172 def self.dynamic_width @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) end |
.dynamic_width_stty ⇒ Object
177 178 179 |
# File 'lib/qb/cli.rb', line 177 def self.dynamic_width_stty `stty size 2>/dev/null`.split[1].to_i end |
.dynamic_width_tput ⇒ Object
182 183 184 |
# File 'lib/qb/cli.rb', line 182 def self.dynamic_width_tput `tput cols 2>/dev/null`.to_i end |
.help(args = []) ⇒ 1
We should have more types of help.
Show the help message.
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/qb/cli/help.rb', line 27 def self.help args = [] = if QB.gemspec. && !QB.gemspec..empty? "metadata:\n" + QB.gemspec..map {|key, value| " #{ key }: #{ value }" }.join("\n") end puts "version: \#{ QB::VERSION }\n\n\#{ metadata }\n\nsyntax:\n\nqb ROLE [OPTIONS] DIRECTORY\n\nuse `qb ROLE -h` for role options.\n\navailable roles:\n\n END\n puts QB::Role.available\n puts\n \n return 1\nend\n" |
.list(pattern = nil) ⇒ 1
We should have more types of help.
List available roles.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# 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(args) ⇒ Fixnum
Play an Ansible playbook (like state.yml
) in the QB environment
(sets up path env vars, IO streams, etc.).
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 |
# 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(args) ⇒ Fixnum
Run a QB role.
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# 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 = QB::Options.new role, args QB.debug "Role options set on cli", role: ..reject { |k, o| o.value.nil? } QB.debug "QB options", .qb.dup QB.debug "Ansible 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 .ask? default = begin role.default_dir cwd, . 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, . 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. 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 = Pathname.new(dir) + '.qb-options.yml' = if .exist? # convert old _ separated names to - separated YAML.load(.read).map {|, | [ , .map {|name, value| [QB::Options.cli_ize_name(name), value] }.to_h ] }.to_h.tap {|| QB.debug "found saved options", } else QB.debug "no saved options" {} end if .key? role. = [role.] QB.debug "found saved options for role", .each do |option_cli_name, value| option = .[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 .ask? # Incomplete raise "COMING SOON!!!...?" QB::CLI. role: role, options: end # Validation # ===================================================================== # # Should have already been taken care of if we used interactive input. # # check that required options are present missing = ..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 = ..select {|k, o| !o.value.nil?} QB.debug "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, } .values.each do |option| playbook_role[option.var_name] = option.value_data end play = { 'hosts' => .qb['hosts'], 'vars' => playbook_vars, # 'gather_subset' => ['!all'], 'gather_facts' => .qb['facts'], 'pre_tasks' => [ { 'qb_facts' => { 'qb_dir' => dir, } }, ], 'roles' => [ 'nrser.blockinfile', ], } if role.['call_role'] logger.debug "Calling role through qb/call..." play['tasks'] = [ { 'include_role' => { 'name' => 'qb/call', }, 'vars' => { 'role' => role.name, 'args' => .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 .qb['user'] play['become'] = true play['become_user'] = .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..dirname cmd = QB::Ansible::Cmd::Playbook.new \ env: env, playbook: playbook, role_options: , chdir: (File.exists?('./ansible/ansible.cfg') ? './ansible' : nil), exe: exe # print # ===== # # print useful stuff for debugging / running outside of qb # if .qb['print'].include? 'options' puts "SET OPTIONS:\n\n#{ YAML.dump set_options }\n\n" end if .qb['print'].include? 'env' puts "ENV:\n\n#{ YAML.dump cmd.env.to_h }\n\n" end if .qb['print'].include? 'cmd' puts "COMMAND:\n\n#{ cmd.prepare }\n\n" end if .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 !.qb['run'] # run # === # # stuff below here does stuff # # save the options back if ( dir && # we set some options that we can save .values.select {|o| o.save? }.length > 0 && # the role says to save options role. ) [role.] = .select{|key, option| option.save? }.map {|key, option| [key, option.value] }.to_h unless .dirname.exist? FileUtils.mkdir_p .dirname end .open('w') do |f| f.write YAML.dump() 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 |
.set_debug!(args) ⇒ Object
Module (Static) Methods
52 53 54 55 56 57 |
# File 'lib/qb/cli.rb', line 52 def self.set_debug! args if DEBUG_ARGS.any? {|arg| args.include? arg} ENV['QB_DEBUG'] = 'true' DEBUG_ARGS.each {|arg| args.delete arg} end end |
.setup(args = []) ⇒ Fixnum
- While it works, this system of finding the setup files feels kind-of wonky.
- 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.
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.
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 96 97 98 99 100 101 102 103 104 105 106 |
# 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 " First entry of `args` must be nil, String or Pathname, found:\n \n <%= args[0].pretty_inspect %>\n \n args:\n \n <%= args.pretty_inspect %>\n \n END\n end\n \n unless playbook_path.file?\n raise \"Can't find QB setup playbook at `\#{ playbook_path.to_s }`\"\n end\n \n cmd = QB::Ansible::Cmd::Playbook.new \\\n chdir: project_root,\n extra_vars: {\n project_root: project_root,\n qb_dir: project_root,\n qb_cwd: Pathname.getwd,\n qb_user_roles_dir: QB::USER_ROLES_DIR,\n },\n playbook_path: playbook_path\n \n puts cmd.prepare\n \n status = cmd.stream\n \n if status != 0\n $stderr.puts \"ERROR QB setup failed.\"\n end\n \n exit status\n \nend\n" |
.terminal_width ⇒ Object
This code was copied from Rake, available under MIT-LICENSE Copyright (c) 2003, 2004 Jim Weirich
159 160 161 162 163 164 165 166 167 168 |
# File 'lib/qb/cli.rb', line 159 def self.terminal_width result = if ENV["QB_COLUMNS"] ENV["QB_COLUMNS"].to_i else unix? ? dynamic_width : DEFAULT_TERMINAL_WIDTH end result < 10 ? DEFAULT_TERMINAL_WIDTH : result rescue DEFAULT_TERMINAL_WIDTH end |
.unix? ⇒ Boolean
187 188 189 190 |
# File 'lib/qb/cli.rb', line 187 def self.unix? RUBY_PLATFORM =~ \ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i end |