class QB::Ansible::QB::Ansible::QB::Ansible::Module

Attributes

args[RW]

The raw parsed arguments. Used for backwards-compatibility with how {QB::Ansible::Module} used to work before {NRSER::Props} and {#arg}.

@todo

May want to get rid of this once using props is totally flushed out.

It should at least be deal with in the constructor somehow so this
can be changed to an `attr_reader`.

@return [Hash<String, VALUE>]

args_source[RW]

Optional information on the source of the arguments.

@return [nil | Hash<Symbol, Object>]

response[R]

The response that will be returned to Ansible (JSON-encoded and written to `STDOUT`).

@return [QB::Ansible::Module::Response]

Public Class Methods

WANT_JSON_mode?(argv = ARGV) click to toggle source

Is the module being run from Ansible via it's “WANT_JSON” mode?

Tests if `argv` is a single string argument that is a file path.

@see docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#non-native-want-json-modules

@param [Array<String>] argv

The CLI argument strings.

@return [Boolean]

`true` if `argv` looks like it came from Ansible's "WANT_JSON" mode.
# File lib/qb/ansible/module.rb, line 202
def self.WANT_JSON_mode? argv = ARGV
  ARGV.length == 1 && File.file?( ARGV[0] )
end
arg(*args, **opts) click to toggle source

@todo Document arg method.

@param [type] arg_name

@todo Add name param description.

@return [return_type]

@todo Document return value.
# File lib/qb/ansible/module.rb, line 333
def self.arg *args, **opts
  name, opts = t.match args.length,
    # Normal {.prop} form
    1, ->( _ ){ [ args[0], opts ] },
    
    # Backwards-compatible form
    2, ->( _ ){ [ args[0], opts.merge( type: args[1] ) ]  }
  
  prop name, **opts
end
load_args() click to toggle source

Load the raw arguments.

# File lib/qb/ansible/module.rb, line 245
def self.load_args
  if WANT_JSON_mode?
    load_args_from_JSON_file ARGV[0]
  else
    load_args_from_CLI_options
  end
end
load_args_from_JSON_file(file_path) click to toggle source

Load args from a file in JSON format.

@param [String | Pathname] file_path

File path to load from.

@return [Array<(Hash, Hash?)>]

Tuple of:

1.  `args:`
    -   `Hash<String, *>`
2.  `args_source:`
    -   `nil | Hash{ type: :file, path: String, contents: String }`
# File lib/qb/ansible/module.rb, line 220
  def self.load_args_from_JSON_file file_path
    file_contents = File.read file_path

    args = JSON.load( file_contents ).with_indifferent_access

    t.hash_( keys: t.str ).check( args ) do |type:, value:|
      binding.erb <<~END
        JSON file contents must load into a `Hash<String, *>`
        
        Loaded value (of class <%= value.class %>):
        
            <%= value.pretty_inspect %>
        
      END
    end
    
    [ args, { type: :file,
              path: file_path.to_s,
              contents: file_contents,
            } ]
  end
new(values = {}) click to toggle source

Construction

# File lib/qb/ansible/module.rb, line 380
def initialize values = {}
  initialize_props values
  @response = QB::Ansible::Module::Response.new
end
run!() click to toggle source

Run the module!

@return (see run!)

# File lib/qb/ansible/module.rb, line 258
def self.run!
  handle_run_error do
    setup_io!
    
    args, args_source = load_args
    run_from_args! args, args_source: args_source
  end
end
run_from_JSON_args_file!(file_path) click to toggle source

Create and run an instance and populate it's args by loading JSON from a file path.

Used to run via Ansible's “WANT_JSON” mode.

@see docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#non-native-want-json-modules

@param [String | Pathname] file_path

Path to the JSON file containing the args.

@return (see run!)

# File lib/qb/ansible/module.rb, line 280
  def self.run_from_JSON_args_file! file_path
    file_contents = File.read file_path
    
    args = JSON.load file_contents
    
    t.hash_( keys: t.str ).check( args ) do |type:, value:|
      binding.erb <<~END
        JSON file contents must load into a `Hash<String, *>`
        
        Loaded value (of class <%= value.class %>):
        
            <%= value.pretty_inspect %>
        
      END
    end
    
    run_from_args!  args,
                    args_source: {
                      type: :file,
                      path: file_path,
                      contents: file_contents,
                    }
  end
run_from_args!(args, args_source: nil) click to toggle source

Run from a hash-like of argument names mapped to values, with optional info about the source of the arguments.

@param [#each_pair] args

Argument names (String or Symbol) mapped to their value data.

@return (see run!)

# File lib/qb/ansible/module.rb, line 313
def self.run_from_args! args, args_source: nil
  logger.trace "Running from args",
    args: args,
    args_source: args_source
  
  instance = self.from_data args
  instance.args_source = args_source
  instance.args = args
  instance.run!
end
setup_io!() click to toggle source
# File lib/qb/ansible/module.rb, line 118
def self.setup_io!
  # Initialize
  $qb_stdio_client ||= QB::IPC::STDIO::Client.new.connect!
  
  if $qb_stdio_client.log.connected? && NRSER::Log.appender.nil?
    # SemanticLogger::Processor.logger = \
    
    SemanticLogger::Processor.instance.appender.logger = \
      SemanticLogger::Appender::File.new(
        io: $stderr,
        level: :warn,
        formatter: Formatters::Processor.new,
      )
    
    NRSER::Log.setup! \
      application: 'qb',
      sync: true,
      dest: {
        io: $qb_stdio_client.log.socket,
        formatter: Formatters::JSON.new,
      }
  end
  
end

Private Class Methods

handle_run_error(&block) click to toggle source

Wrap a “run” call with error handling.

@private

@param [Proc<() => RESULT] &block

@return [RESULT]

On success, returns the result of `&block`.

@raise [SystemExit]

Any exception raised in `&block` is logged at `fatal` level, then
`exit false` is called, raising a {SystemExit} error.

The only exception: if `&block` raises a {SystemExit} error, that error
is simply re-raised without any logging. This should allow nesting
{.handle_run_error} calls, since the first `rescue` will log any
error and raise {SystemExit}, which will then simply be bubbled-up
by {.handle_run_error} wrappers further up the call chain.
# File lib/qb/ansible/module.rb, line 163
def self.handle_run_error &block
  begin
    block.call
  rescue SystemExit => error
    # Bubble {SystemExit} up to exit normally
    raise
  rescue Exception => error
    # Everything else is unexpected, and needs to be logged in a way that's
    # more useful than the JSON-ified crap Ansible would normally print
    
    # If we don't have a logger setup, log to real `STDERR` so we get
    # *something* back in the Ansible output, even if it's JSON mess
    if NRSER::Log.appender.nil?
      NRSER::Log.setup! application: 'qb', dest: STDERR
    end
    
    # Log it out
    logger.fatal error
    
    # And GTFO
    exit false
  end
end

Public Instance Methods

changed!(facts = {}) click to toggle source
# File lib/qb/ansible/module.rb, line 470
def changed! facts = {}
  response.changed = true
  
  unless facts.empty?
    response.facts.merge! facts
  end
  
  done
end
debug(*args) click to toggle source

Forward args to {QB.debug} if we are connected to a QB STDERR stream (write to STDERR).

@param args see QB.debug

# File lib/qb/ansible/module.rb, line 413
def debug *args
  logger.debug payload: args
end
done() click to toggle source
# File lib/qb/ansible/module.rb, line 481
def done
  exit_json response.to_data( add_class: false ).compact
end
exit_json(hash) click to toggle source
# File lib/qb/ansible/module.rb, line 486
def exit_json hash
  # print JSON response to process' actual STDOUT (instead of $stdout,
  # which may be pointing to the qb parent process)
  STDOUT.print JSON.pretty_generate( hash.stringify_keys )
  
  exit true
end
fail(msg, **values) click to toggle source
# File lib/qb/ansible/module.rb, line 495
def fail msg, **values
  fail_response = QB::Ansible::Module::Response.new \
    failed: true,
    msg: msg.to_s,
    warnings: response.warnings,
    depreciations: response.depreciations
  
  STDOUT.print \
    JSON.pretty_generate( fail_response.to_data( add_class: false ).compact )
  
  exit false
end
info(msg) click to toggle source

Old logging function - use `#logger.info` instead.

@deprecated

# File lib/qb/ansible/module.rb, line 422
def info msg
  logger.info msg
end
run!() click to toggle source
# File lib/qb/ansible/module.rb, line 454
def run!
  result = main
  
  case result
  when nil
    # pass
  when Hash
    response.facts.merge! result
  else
    raise "result of #main should be nil or Hash, found #{ result.inspect }"
  end
  
  done
end
warn(msg) click to toggle source

Append a warning message to the {#response}'s {Response#warnings} array and log it.

@todo

Should be incorporated into {#logger}? Seems like it would need one of:

1.  `on_...` hooks, like `Logger#on_warn`, etc.

    This might be nice but I'd rather hold off on throwing more shit
    into {NRSER::Log::Logger} for the time being if possible.

2.  Adding a custom appender when we run a module that has a ref to
    the module instance and so it's {Response}.

@param [String] msg

Non-empty string.

@return [nil]

# File lib/qb/ansible/module.rb, line 447
def warn msg
  logger.warn msg
  response.warnings << msg
  nil
end