qb 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ require 'json'
2
+ require 'pp'
3
+
4
+
5
+ # Declarations
6
+ # =====================================================================
7
+
8
+ module QB; end
9
+ module QB::Ansible; end
10
+
11
+
12
+ # Definitions
13
+ # =====================================================================
14
+
15
+ class QB::Ansible::Module
16
+
17
+ # Class Variables
18
+ # =====================================================================
19
+
20
+ @@arg_types = {}
21
+
22
+
23
+ # Class Methods
24
+ # =====================================================================
25
+
26
+ def self.stringify_keys hash
27
+ hash.map {|k, v| [k.to_s, v]}.to_h
28
+ end
29
+
30
+
31
+ def self.arg name, type
32
+ @@arg_types[name.to_sym] = type
33
+ end
34
+
35
+
36
+ # Constructor
37
+ # =====================================================================
38
+
39
+ def initialize
40
+ @changed = false
41
+ @input_file = ARGV[0]
42
+ @input = File.read @input_file
43
+ @args = JSON.load @input
44
+ @facts = {}
45
+ @warnings = []
46
+
47
+ @qb_stdio_out = nil
48
+ @qb_stdio_err = nil
49
+ @qb_stdio_in = nil
50
+
51
+ # debug "HERE!"
52
+ # debug ENV
53
+
54
+ # if QB_STDIO_ env vars are set send stdout and stderr
55
+ # to those sockets to print in the parent process
56
+
57
+ if ENV['QB_STDIO_ERR']
58
+ @qb_stdio_err = $stderr = UNIXSocket.new ENV['QB_STDIO_ERR']
59
+
60
+ debug "Connected to QB stderr stream at #{ ENV['QB_STDIO_ERR'] } #{ @qb_stdio_err.path }."
61
+ end
62
+
63
+ if ENV['QB_STDIO_OUT']
64
+ @qb_stdio_out = $stdout = UNIXSocket.new ENV['QB_STDIO_OUT']
65
+
66
+ debug "Connected to QB stdout stream at #{ ENV['QB_STDIO_OUT'] }."
67
+ end
68
+
69
+ if ENV['QB_STDIO_IN']
70
+ @qb_stdio_in = UNIXSocket.new ENV['QB_STDIO_IN']
71
+
72
+ debug "Connected to QB stdin stream at #{ ENV['QB_STDIO_IN'] }."
73
+ end
74
+
75
+ @@arg_types.each {|key, type|
76
+ var_name = "@#{ key.to_s }"
77
+
78
+ unless instance_variable_get(var_name).nil?
79
+ raise ArgumentError.new NRSER.squish <<-END
80
+ an instance variable named #{ var_name } exists
81
+ with value #{ instance_variable_get(var_name).inspect }
82
+ END
83
+ end
84
+
85
+ instance_variable_set var_name,
86
+ type.check(@args.fetch(key.to_s))
87
+ }
88
+ end
89
+
90
+
91
+
92
+ # Instance Methods
93
+ # =====================================================================
94
+
95
+ # Logging
96
+ # ---------------------------------------------------------------------
97
+ #
98
+ # Logging is a little weird in Ansible modules... Ansible has facilities
99
+ # for notifying the user about warnings and depreciations, which we will
100
+ # make accessible, but it doesn't seem to have facilities for notices and
101
+ # debugging, which I find very useful.
102
+ #
103
+ # When run inside of QB (targeting localhost only at the moment, sadly)
104
+ # we expose additional IO channels for STDIN, STDOUT and STDERR through
105
+ # opening unix socket files that the main QB process spawns threads to
106
+ # listen to, and we provide those file paths via environment variables
107
+ # so modules can pick those up and interact with those streams, allowing
108
+ # them to act like regular scripts inside Ansible-world (see
109
+ # QB::Util::STDIO for details and implementation).
110
+ #
111
+ # We use those channels if present to provide logging mechanisms.
112
+ #
113
+
114
+ # Forward args to {QB.debug} if we are connected to a QB STDERR stream
115
+ # (write to STDERR).
116
+ #
117
+ # @param args see QB.debug
118
+ #
119
+ def debug *args
120
+ if @qb_stdio_err
121
+ header = "<QB::Ansible::Module #{ self.class.name }>"
122
+
123
+ if args[0].is_a? String
124
+ header += " " + args.shift
125
+ end
126
+
127
+ QB.debug header, *args
128
+ end
129
+ end
130
+
131
+ def info msg
132
+ if @qb_stdio_err
133
+ $stderr.puts msg
134
+ end
135
+ end
136
+
137
+ # Append a warning message to @warnings.
138
+ def warn msg
139
+ @warnings << msg
140
+ end
141
+
142
+
143
+ def run
144
+ result = main
145
+
146
+ case result
147
+ when nil
148
+ # pass
149
+ when Hash
150
+ @facts.merge! result
151
+ else
152
+ raise "result of #main should be nil or Hash, found #{ result.inspect }"
153
+ end
154
+
155
+ done
156
+ end
157
+
158
+ def changed! facts = {}
159
+ @changed = true
160
+ @facts.merge! facts
161
+ done
162
+ end
163
+
164
+ def done
165
+ exit_json changed: @changed,
166
+ ansible_facts: self.class.stringify_keys(@facts),
167
+ warnings: @warnings
168
+ end
169
+
170
+ def exit_json hash
171
+ # print JSON response to process' actual STDOUT (instead of $stdout,
172
+ # which may be pointing to the qb parent process)
173
+ STDOUT.print JSON.dump(self.class.stringify_keys(hash))
174
+
175
+ [
176
+ [:stdin, @qb_stdio_in],
177
+ [:stdout, @qb_stdio_out],
178
+ [:stderr, @qb_stdio_err],
179
+ ].each do |name, socket|
180
+ if socket
181
+ debug "Flushing socket #{ name }."
182
+ socket.flush
183
+ debug "Closing #{ name } socket at #{ socket.path.to_s }."
184
+ socket.close
185
+ end
186
+ end
187
+
188
+ exit 0
189
+ end
190
+
191
+ def fail msg
192
+ exit_json failed: true, msg: msg, warnings: @warnings
193
+ end
194
+ end # class QB::Ansible::Module
@@ -1,185 +1,5 @@
1
- require 'json'
2
- require 'pp'
1
+ require_relative './ansible/module'
3
2
 
4
3
  module QB
5
- class AnsibleModule
6
-
7
- # Class Variables
8
- # =====================================================================
9
-
10
- @@arg_types = {}
11
-
12
-
13
- # Class Methods
14
- # =====================================================================
15
-
16
- def self.stringify_keys hash
17
- hash.map {|k, v| [k.to_s, v]}.to_h
18
- end
19
-
20
-
21
- def self.arg name, type
22
- @@arg_types[name.to_sym] = type
23
- end
24
-
25
-
26
- # Constructor
27
- # =====================================================================
28
-
29
- def initialize
30
- @changed = false
31
- @input_file = ARGV[0]
32
- @input = File.read @input_file
33
- @args = JSON.load @input
34
- @facts = {}
35
- @warnings = []
36
-
37
- @qb_stdio_out = nil
38
- @qb_stdio_err = nil
39
- @qb_stdio_in = nil
40
-
41
- # debug "HERE!"
42
- # debug ENV
43
-
44
- # if QB_STDIO_ env vars are set send stdout and stderr
45
- # to those sockets to print in the parent process
46
-
47
- if ENV['QB_STDIO_ERR']
48
- @qb_stdio_err = $stderr = UNIXSocket.new ENV['QB_STDIO_ERR']
49
-
50
- debug "Connected to QB stderr stream at #{ ENV['QB_STDIO_ERR'] } #{ @qb_stdio_err.path }."
51
- end
52
-
53
- if ENV['QB_STDIO_OUT']
54
- @qb_stdio_out = $stdout = UNIXSocket.new ENV['QB_STDIO_OUT']
55
-
56
- debug "Connected to QB stdout stream at #{ ENV['QB_STDIO_OUT'] }."
57
- end
58
-
59
- if ENV['QB_STDIO_IN']
60
- @qb_stdio_in = UNIXSocket.new ENV['QB_STDIO_IN']
61
-
62
- debug "Connected to QB stdin stream at #{ ENV['QB_STDIO_IN'] }."
63
- end
64
-
65
- @@arg_types.each {|key, type|
66
- var_name = "@#{ key.to_s }"
67
-
68
- unless instance_variable_get(var_name).nil?
69
- raise ArgumentError.new NRSER.squish <<-END
70
- an instance variable named #{ var_name } exists
71
- with value #{ instance_variable_get(var_name).inspect }
72
- END
73
- end
74
-
75
- instance_variable_set var_name,
76
- type.check(@args.fetch(key.to_s))
77
- }
78
- end
79
-
80
-
81
-
82
- # Instance Methods
83
- # =====================================================================
84
-
85
- # Logging
86
- # ---------------------------------------------------------------------
87
- #
88
- # Logging is a little weird in Ansible modules... Ansible has facilities
89
- # for notifying the user about warnings and depreciations, which we will
90
- # make accessible, but it doesn't seem to have facilities for notices and
91
- # debugging, which I find very useful.
92
- #
93
- # When run inside of QB (targeting localhost only at the moment, sadly)
94
- # we expose additional IO channels for STDIN, STDOUT and STDERR through
95
- # opening unix socket files that the main QB process spawns threads to
96
- # listen to, and we provide those file paths via environment variables
97
- # so modules can pick those up and interact with those streams, allowing
98
- # them to act like regular scripts inside Ansible-world (see
99
- # QB::Util::STDIO for details and implementation).
100
- #
101
- # We use those channels if present to provide logging mechanisms.
102
- #
103
-
104
- # Forward args to {QB.debug} if we are connected to a QB STDERR stream
105
- # (write to STDERR).
106
- #
107
- # @param args see QB.debug
108
- #
109
- def debug *args
110
- if @qb_stdio_err
111
- header = "<QB::AnsibleModule #{ self.class.name }>"
112
-
113
- if args[0].is_a? String
114
- header += " " + args.shift
115
- end
116
-
117
- QB.debug header, *args
118
- end
119
- end
120
-
121
- def info msg
122
- if @qb_stdio_err
123
- $stderr.puts msg
124
- end
125
- end
126
-
127
- # Append a warning message to @warnings.
128
- def warn msg
129
- @warnings << msg
130
- end
131
-
132
-
133
- def run
134
- result = main
135
-
136
- case result
137
- when nil
138
- # pass
139
- when Hash
140
- @facts.merge! result
141
- else
142
- raise "result of #main should be nil or Hash, found #{ result.inspect }"
143
- end
144
-
145
- done
146
- end
147
-
148
- def changed! facts = {}
149
- @changed = true
150
- @facts.merge! facts
151
- done
152
- end
153
-
154
- def done
155
- exit_json changed: @changed,
156
- ansible_facts: self.class.stringify_keys(@facts),
157
- warnings: @warnings
158
- end
159
-
160
- def exit_json hash
161
- # print JSON response to process' actual STDOUT (instead of $stdout,
162
- # which may be pointing to the qb parent process)
163
- STDOUT.print JSON.dump(self.class.stringify_keys(hash))
164
-
165
- [
166
- [:stdin, @qb_stdio_in],
167
- [:stdout, @qb_stdio_out],
168
- [:stderr, @qb_stdio_err],
169
- ].each do |name, socket|
170
- if socket
171
- debug "Flushing socket #{ name }."
172
- socket.flush
173
- debug "Closing #{ name } socket at #{ socket.path.to_s }."
174
- socket.close
175
- end
176
- end
177
-
178
- exit 0
179
- end
180
-
181
- def fail msg
182
- exit_json failed: true, msg: msg, warnings: @warnings
183
- end
184
- end
4
+ AnsibleModule = QB::Ansible::Module
185
5
  end # QB
@@ -1,3 +1,9 @@
1
+ # Requirements
2
+ # =====================================================================
3
+
4
+ # package
5
+ require 'qb/ansible/cmds/playbook'
6
+
1
7
 
2
8
  module QB; end
3
9
 
@@ -18,13 +24,33 @@ module QB::CLI
18
24
  raise "Need path to playbook in first arg."
19
25
  end
20
26
 
21
- path = QB::Util.resolve args[0]
27
+ playbook_path = QB::Util.resolve args[0]
22
28
 
23
- unless path.file?
29
+ unless playbook_path.file?
24
30
  raise "Can't find Ansible playbook at #{ path.to_s }"
25
31
  end
26
32
 
33
+ # By default, we won't change directories to run the command.
34
+ chdir = nil
35
+
36
+ # See if there is an Ansible config in the parent directories
37
+ ansible_cfg_path = QB::Util.find_up \
38
+ QB::Ansible::ConfigFile::FILE_NAME,
39
+ playbook_path.dirname,
40
+ raise_on_not_found: false
41
+
42
+ # If we did find an Ansible config, we're going to want to run in that
43
+ # directory and add it to the role search path so that we merge it's
44
+ # values into our env vars (otherwise they would override the config
45
+ # values).
46
+ unless ansible_cfg_path.nil?
47
+ QB::Role::PATH.unshift ansible_cfg_path.dirname
48
+ chdir = ansible_cfg_path.dirname
49
+ end
27
50
 
51
+ cmd = QB::Ansible::Cmds::Playbook.new \
52
+ playbook_path: playbook_path,
53
+ chdir: chdir
28
54
 
29
55
  end # .play
30
56
 
File without changes
@@ -204,7 +204,7 @@ module QB
204
204
  def self.search_path
205
205
  QB::Role::PATH.
206
206
  map { |path|
207
- if QB::Ansible::ConfigFile.file_path?(path)
207
+ if QB::Ansible::ConfigFile.end_with_config_file?(path)
208
208
  if File.file?(path)
209
209
  QB::Ansible::ConfigFile.new(path).defaults.roles_path
210
210
  end
@@ -95,13 +95,25 @@ module QB
95
95
  # @param [Pathname] from (Pathname.pwd)
96
96
  # directory to start from.
97
97
  #
98
+ # @param [Boolean] raise_on_not_found:
99
+ # When `true`, a {QB::FSStateError} will be raised if no file is found
100
+ # (default behavior).
101
+ #
102
+ # This is something of a legacy behavior - I think it would be better
103
+ # to have {find_up} return `nil` in that case and add a `find_up!`
104
+ # method that raises on not found. But I'm not going to do it right now.
105
+ #
98
106
  # @return [Pathname]
99
107
  # Pathname of found file.
100
108
  #
101
- # @raise
102
- # if file is not found in `from` or any of it's parent directories.
109
+ # @return [nil]
110
+ # If no file is found and the `raise_on_not_found` option is `false`.
111
+ #
112
+ # @raise [QB::FSStateError]
113
+ # If file is not found in `from` or any of it's parent directories
114
+ # and the `raise_on_not_found` option is `true` (default behavior).
103
115
  #
104
- def self.find_up filename, from = Pathname.pwd
116
+ def self.find_up filename, from = Pathname.pwd, raise_on_not_found: true
105
117
  path = from + filename
106
118
 
107
119
  return from if path.exist?
@@ -109,10 +121,14 @@ module QB
109
121
  parent = from.parent
110
122
 
111
123
  if from == parent
112
- raise "not found in current or any parent directories: #{ filename }"
124
+ if raise_on_not_found
125
+ raise "not found in current or any parent directories: #{ filename }"
126
+ else
127
+ return nil
128
+ end
113
129
  end
114
130
 
115
- return find_up filename, parent
131
+ return find_up filename, parent, raise_on_not_found: raise_on_not_found
116
132
  end # .find_up
117
133
  end # Util
118
134
  end # QB