qb 0.3.3 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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