Module: QB::CLI

Defined in:
lib/qb/cli.rb,
lib/qb/cli/run.rb,
lib/qb/cli/help.rb,
lib/qb/cli/play.rb,
lib/qb/cli/setup.rb

Overview

Definitions

Class Method Summary collapse

Class Method Details

.ask(name:, description: nil, type:, default: NRSER::NO_ARG) ⇒ return_type

TODO:

Document ask method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



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
# File 'lib/qb/cli.rb', line 47

def 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



105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/qb/cli.rb', line 105

def 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



121
122
123
124
125
# File 'lib/qb/cli.rb', line 121

def ask_for_options role:, options:
  options.select { |opt| opt.value.nil? }.each { |option|
    ask_for_option role: role, option: option
  }
end

.help1

TODO:

We should have more types of help.

Show the help message.

Returns:

  • (1)

    Error exit status - we don't want qb ... && ... to move on to the second command when we end up falling back to help.



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
   = 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"

.play(args) ⇒ Fixnum

Play an Ansible playbook (like state.yml) in the QB environment (sets up path env vars, IO streams, etc.).

Parameters:

  • args (Array<String>)

    CLI arguments to use.

Returns:

  • (Fixnum)

    The ansible-playbook command exit code.



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::Cmds::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.

Parameters:

  • args (Array<String>)

    CLI args to work with.

Returns:

  • (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).



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
# File 'lib/qb/cli/run.rb', line 29

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
  
  QB.check_qb_version role
  
  options = QB::Options.new role, args
  
  QB.debug "role options set on cli", options.role_options.select {|k, o|
    !o.value.nil?
  }
  
  QB.debug "qb options", options.qb
  
  cwd = Dir.getwd
  
  dir = nil
  
  if role.uses_default_dir?
    # 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
  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',
      playbook_role
    ],
  }
  
  if options.qb['user']
    play['become'] = true
    play['become_user'] = options.qb['user']
  end
  
  playbook = [play]
  
  QB.debug "playbook", playbook
  
  env = QB::Ansible::Env.new
  
  # 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::Cmds::Playbook.new \
    env: env,
    playbook: playbook,
    role_options: options,
    chdir: (File.exists?('./ansible/ansible.cfg') ? './ansible' : nil)
  
  # 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
  
  status = cmd.stream
  
  if status != 0
    $stderr.puts "ERROR ansible-playbook failed."
  end
  
  # exit status
  status
end

.setup(args = []) ⇒ Fixnum

Play //dev/setup.yml

Parameters:

  • args (Array<String>) (defaults to: [])

    CLI arguments to use.

Returns:

  • (Fixnum)

    The ansible-playbook command exit code.



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
53
54
55
# File 'lib/qb/cli/setup.rb', line 27

def self.setup args = []
  project_root = NRSER.git_root '.'
  playbook_path = project_root / 'dev' / 'setup.qb.yml'
  
  unless playbook_path.file?
    raise "Can't find QB setup playbook at `#{ playbook_path.to_s }`"
  end
  
  cmd = QB::Ansible::Cmds::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