require 'json' require 'pp' module QB class AnsibleModule # Class Variables # ===================================================================== @@arg_types = {} # Class Methods # ===================================================================== def self.stringify_keys hash hash.map {|k, v| [k.to_s, v]}.to_h end def self.arg name, type @@arg_types[name.to_sym] = type end # Constructor # ===================================================================== def initialize @changed = false @input_file = ARGV[0] @input = File.read @input_file @args = JSON.load @input @facts = {} @warnings = [] @qb_stdio_out = nil @qb_stdio_err = nil @qb_stdio_in = nil # debug "HERE!" # debug ENV # if QB_STDIO_ env vars are set send stdout and stderr # to those sockets to print in the parent process if ENV['QB_STDIO_ERR'] @qb_stdio_err = $stderr = UNIXSocket.new ENV['QB_STDIO_ERR'] debug "Connected to QB stderr stream at #{ ENV['QB_STDIO_ERR'] } #{ @qb_stdio_err.path }." end if ENV['QB_STDIO_OUT'] @qb_stdio_out = $stdout = UNIXSocket.new ENV['QB_STDIO_OUT'] debug "Connected to QB stdout stream at #{ ENV['QB_STDIO_OUT'] }." end if ENV['QB_STDIO_IN'] @qb_stdio_in = UNIXSocket.new ENV['QB_STDIO_IN'] debug "Connected to QB stdin stream at #{ ENV['QB_STDIO_IN'] }." end @@arg_types.each {|key, type| var_name = "@#{ key.to_s }" unless instance_variable_get(var_name).nil? raise ArgumentError.new NRSER.squish <<-END an instance variable named #{ var_name } exists with value #{ instance_variable_get(var_name).inspect } END end instance_variable_set var_name, type.check(@args.fetch(key.to_s)) } end # Instance Methods # ===================================================================== # Logging # --------------------------------------------------------------------- # # Logging is a little weird in Ansible modules... Ansible has facilities # for notifying the user about warnings and depreciations, which we will # make accessible, but it doesn't seem to have facilities for notices and # debugging, which I find very useful. # # When run inside of QB (targeting localhost only at the moment, sadly) # we expose additional IO channels for STDIN, STDOUT and STDERR through # opening unix socket files that the main QB process spawns threads to # listen to, and we provide those file paths via environment variables # so modules can pick those up and interact with those streams, allowing # them to act like regular scripts inside Ansible-world (see # QB::Util::STDIO for details and implementation). # # We use those channels if present to provide logging mechanisms. # # Forward args to {QB.debug} if we are connected to a QB STDERR stream # (write to STDERR). # # @param args see QB.debug # def debug *args if @qb_stdio_err header = "<QB::AnsibleModule #{ self.class.name }>" if args[0].is_a? String header += " " + args.shift end QB.debug header, *args end end # Append a warning message to @warnings. def warn msg @warnings << msg end def run result = main case result when nil # pass when Hash @facts.merge! result else raise "result of #main should be nil or Hash, found #{ result.inspect }" end done end def changed! facts = {} @changed = true @facts.merge! facts done end def done exit_json changed: @changed, ansible_facts: self.class.stringify_keys(@facts), warnings: @warnings end 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.dump(self.class.stringify_keys(hash)) [ [:stdin, @qb_stdio_in], [:stdout, @qb_stdio_out], [:stderr, @qb_stdio_err], ].each do |name, socket| if socket debug "Flushing socket #{ name }." socket.flush debug "Closing #{ name } socket at #{ socket.path.to_s }." socket.close end end exit 0 end def fail msg exit_json failed: true, msg: msg, warnings: @warnings end end end # QB