qb 0.3.25 → 0.4.0
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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/ansible.cfg +10 -1
- data/exe/.qb_interop_receive +3 -10
- data/exe/qb +8 -2
- data/lib/python/qb/__init__.py +6 -0
- data/{roles/qb/ruby/rspec/setup/tasks/persistence.yml → lib/python/qb/ansible/__init__.py} +0 -0
- data/lib/python/qb/ansible/modules/__init__.py +0 -0
- data/lib/python/qb/ansible/modules/docker/__init__.py +0 -0
- data/lib/python/qb/ansible/modules/docker/client.py +177 -0
- data/lib/python/qb/ansible/modules/docker/image_manager.py +754 -0
- data/lib/python/qb/ipc/__init__.py +0 -0
- data/lib/python/qb/ipc/stdio/__init__.py +99 -0
- data/lib/python/qb/ipc/stdio/logging.py +151 -0
- data/lib/qb.rb +3 -3
- data/lib/qb/ansible/cmds/playbook.rb +5 -14
- data/lib/qb/ansible/env.rb +36 -6
- data/lib/qb/ansible/module.rb +396 -152
- data/lib/qb/ansible/module/response.rb +195 -0
- data/lib/qb/ansible/modules.rb +42 -0
- data/lib/qb/ansible/modules/docker/image.rb +273 -0
- data/lib/qb/cli.rb +5 -18
- data/lib/qb/cli/run.rb +2 -2
- data/lib/qb/data.rb +22 -0
- data/lib/qb/data/immutable.rb +39 -0
- data/lib/qb/docker.rb +2 -0
- data/lib/qb/docker/cli.rb +430 -0
- data/lib/qb/docker/image.rb +207 -0
- data/lib/qb/docker/image/name.rb +309 -0
- data/lib/qb/docker/image/tag.rb +113 -0
- data/lib/qb/docker/repo.rb +0 -0
- data/lib/qb/errors.rb +17 -3
- data/lib/qb/execution.rb +83 -0
- data/lib/qb/ipc.rb +48 -0
- data/lib/qb/ipc/stdio.rb +32 -0
- data/lib/qb/ipc/stdio/client.rb +267 -0
- data/lib/qb/ipc/stdio/server.rb +229 -0
- data/lib/qb/ipc/stdio/server/in_service.rb +18 -0
- data/lib/qb/ipc/stdio/server/log_service.rb +168 -0
- data/lib/qb/ipc/stdio/server/out_service.rb +20 -0
- data/lib/qb/ipc/stdio/server/service.rb +229 -0
- data/lib/qb/options.rb +360 -502
- data/lib/qb/options/option.rb +293 -115
- data/lib/qb/options/option/option_parser_concern.rb +228 -0
- data/lib/qb/options/types.rb +73 -0
- data/lib/qb/package.rb +0 -1
- data/lib/qb/package/version.rb +179 -58
- data/lib/qb/package/version/from.rb +192 -51
- data/lib/qb/package/version/leveled.rb +1 -1
- data/lib/qb/path.rb +3 -2
- data/lib/qb/repo/git.rb +9 -85
- data/lib/qb/role/default_dir.rb +2 -2
- data/lib/qb/role/errors.rb +2 -8
- data/lib/qb/util.rb +1 -2
- data/lib/qb/util/bundler.rb +73 -43
- data/lib/qb/util/decorators.rb +99 -0
- data/lib/qb/util/interop.rb +7 -8
- data/lib/qb/util/resource.rb +12 -13
- data/lib/qb/version.rb +10 -0
- data/library/path_facts +5 -10
- data/library/qb.module.rb +105 -0
- data/library/stream +6 -26
- data/load/ansible/module/autorun.rb +25 -0
- data/load/ansible/module/script.rb +123 -0
- data/load/rebundle.rb +39 -0
- data/plugins/filter/dict_filters.py +56 -0
- data/plugins/{filter_plugins/path_plugins.py → filter/path_filters.py} +0 -0
- data/plugins/{filter_plugins/ruby_interop_plugins.py → filter/ruby_interop_filters.py} +1 -17
- data/plugins/{filter_plugins/string_plugins.py → filter/string_filters.py} +1 -20
- data/plugins/{filter_plugins/version_plugins.py → filter/version_filters.py} +3 -18
- data/plugins/{lookup_plugins/every.py → lookup/every_lookups.py} +0 -0
- data/plugins/{lookup_plugins/resolve.py → lookup/resolve_lookups.py} +0 -0
- data/plugins/{lookup_plugins/version.py → lookup/version_lookups.py} +0 -16
- data/plugins/test/dict_tests.py +36 -0
- data/plugins/test/string_tests.py +36 -0
- data/qb.gemspec +7 -3
- data/roles/nrser.rb/library/set_fact_with_ruby.rb +3 -9
- data/roles/nrser.state_mate/library/state +3 -17
- data/roles/qb/call/meta/qb.yml +1 -1
- data/roles/qb/dev/ref/repo/git/meta/qb.yml +1 -1
- data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/defaults/main.yml +1 -1
- data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/meta/main.yml +3 -2
- data/roles/qb/{ruby/rspec/setup → docker/mac/kubernetes}/meta/qb.yml +12 -7
- data/roles/qb/docker/mac/kubernetes/tasks/main.yml +45 -0
- data/roles/qb/git/check/clean/meta/qb.yml +1 -1
- data/roles/qb/git/ignore/meta/qb +10 -3
- data/roles/qb/git/submodule/update/library/git_submodule_update +17 -27
- data/roles/qb/github/pages/setup/meta/qb.yml +1 -1
- data/roles/qb/labs/atom/apm/meta/qb.yml +1 -1
- data/roles/qb/osx/git/change_case/meta/qb.yml +1 -1
- data/roles/qb/osx/notif/meta/qb.yml +1 -1
- data/roles/qb/pkg/bump/library/bump +4 -16
- data/roles/qb/role/qb/defaults/main.yml +2 -0
- data/roles/qb/role/qb/meta/qb.yml +10 -5
- data/roles/qb/role/qb/templates/qb.yml.j2 +7 -2
- data/roles/qb/role/templates/library/module.rb.j2 +12 -23
- data/roles/qb/role/templates/meta/main.yml.j2 +14 -1
- data/roles/qb/ruby/bundler/meta/qb.yml +1 -1
- data/roles/qb/ruby/dependency/meta/qb.yml +1 -1
- data/roles/qb/ruby/gem/bin_stubs/meta/qb.yml +1 -1
- data/roles/qb/ruby/gem/bin_stubs/templates/console +8 -2
- data/roles/qb/ruby/gem/build/meta/qb.yml +1 -1
- data/roles/qb/ruby/gem/new/meta/qb.yml +1 -1
- data/roles/qb/ruby/nrser/rspex/generate/meta/qb.yml +5 -5
- data/roles/qb/ruby/nrser/rspex/issue/meta/qb.yml +1 -1
- data/roles/qb/ruby/yard/clean/meta/qb.yml +1 -1
- data/roles/qb/ruby/yard/config/library/yard.get_output_dir +5 -15
- data/roles/qb/ruby/yard/config/meta/qb.yml +1 -1
- data/roles/qb/ruby/yard/setup/meta/qb.yml +1 -1
- metadata +71 -22
- data/lib/qb/ansible_module.rb +0 -5
- data/lib/qb/util/stdio.rb +0 -187
- data/roles/qb/ruby/rspec/setup/tasks/main.yml +0 -4
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from __future__ import absolute_import, division, print_function
|
|
2
|
+
__metaclass__ = type
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
|
|
7
|
+
def path_env_var_name(name):
|
|
8
|
+
return "QB_STDIO_{}".format(name.upper())
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Connection:
|
|
12
|
+
'''
|
|
13
|
+
Port of Ruby `QB::IPC::STDIO::Client::Connection` class.
|
|
14
|
+
'''
|
|
15
|
+
|
|
16
|
+
def __init__(self, name, type):
|
|
17
|
+
self.name = name
|
|
18
|
+
self.type = type
|
|
19
|
+
self.path = None
|
|
20
|
+
self.socket = None
|
|
21
|
+
self.env_var_name = path_env_var_name(self.name)
|
|
22
|
+
self.connected = False
|
|
23
|
+
|
|
24
|
+
def __str__(self):
|
|
25
|
+
attrs = ' '.join(
|
|
26
|
+
"{}={}".format(name, getattr(self, name))
|
|
27
|
+
for name in ('name', 'type', 'path', 'connected')
|
|
28
|
+
)
|
|
29
|
+
return "<qb.ipc.stdio.Connection {}>".format(attrs)
|
|
30
|
+
|
|
31
|
+
def get_path(self):
|
|
32
|
+
if self.env_var_name in os.environ:
|
|
33
|
+
self.path = os.environ[self.env_var_name]
|
|
34
|
+
return self.path
|
|
35
|
+
|
|
36
|
+
def connect(self, warnings=None):
|
|
37
|
+
if self.connected:
|
|
38
|
+
raise RuntimeError("{} is already connected!".format(self))
|
|
39
|
+
|
|
40
|
+
if self.get_path() is None:
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
self.socket.connect(self.path)
|
|
47
|
+
except socket.error, msg:
|
|
48
|
+
if warngings is not None:
|
|
49
|
+
warning = 'Failed to connect to QB STDOUT stream at {}: {}'
|
|
50
|
+
warning = warning.format(qb_stdout_path, msg)
|
|
51
|
+
warnings.append(warning)
|
|
52
|
+
|
|
53
|
+
self.socket = None
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
self.connected = True
|
|
57
|
+
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
def disconnect(self):
|
|
61
|
+
if not self.connected:
|
|
62
|
+
raise RuntimeError("{} is not connected!".format(self))
|
|
63
|
+
|
|
64
|
+
# if self.type == 'out':
|
|
65
|
+
# self.socket.flush()
|
|
66
|
+
|
|
67
|
+
self.socket.close()
|
|
68
|
+
self.socket = None
|
|
69
|
+
self.connected = False
|
|
70
|
+
|
|
71
|
+
def println(self, line):
|
|
72
|
+
if not line.endswith( u"\n" ):
|
|
73
|
+
line = line + u"\n"
|
|
74
|
+
self.socket.sendall(line.encode("utf-8"))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Client:
|
|
78
|
+
def __init__(self):
|
|
79
|
+
# I don't think need STDIN or we want to deal with what it means here
|
|
80
|
+
# self.stdin = Connection(name='in', type='in')
|
|
81
|
+
self.stdout = Connection(name='out', type='out')
|
|
82
|
+
self.stderr = Connection(name='err', type='out')
|
|
83
|
+
self.log = Connection(name='log', type='out')
|
|
84
|
+
|
|
85
|
+
def connections(self):
|
|
86
|
+
return [self.stdout, self.stderr, self.log]
|
|
87
|
+
|
|
88
|
+
def connect(self, warnings=None):
|
|
89
|
+
for connection in self.connections():
|
|
90
|
+
if not connection.connected:
|
|
91
|
+
connection.connect(warnings)
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def disconnect(sefl):
|
|
95
|
+
for connection in self.connections():
|
|
96
|
+
if connection.connected:
|
|
97
|
+
connection.disconnect()
|
|
98
|
+
|
|
99
|
+
client = Client()
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import absolute_import, division, print_function
|
|
2
|
+
__metaclass__ = type
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import qb.ipc.stdio
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def getLogger(name, level=logging.DEBUG, io_client=qb.ipc.stdio.client):
|
|
12
|
+
logger = logging.getLogger(name)
|
|
13
|
+
if level is not None:
|
|
14
|
+
logger.setLevel(level)
|
|
15
|
+
logger.addHandler(Handler(io_client=io_client))
|
|
16
|
+
return Adapter(logger, {})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Adapter(logging.LoggerAdapter):
|
|
20
|
+
def process(self, msg, kwds):
|
|
21
|
+
payload = None
|
|
22
|
+
if 'payload' in kwds:
|
|
23
|
+
payload = kwds['payload']
|
|
24
|
+
del kwds['payload']
|
|
25
|
+
|
|
26
|
+
if payload:
|
|
27
|
+
try:
|
|
28
|
+
msg = msg.format(**payload)
|
|
29
|
+
except:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
if 'extra' not in kwds:
|
|
33
|
+
kwds['extra'] = {}
|
|
34
|
+
|
|
35
|
+
kwds['extra']['payload'] = payload
|
|
36
|
+
|
|
37
|
+
return msg, kwds
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Handler(logging.Handler):
|
|
41
|
+
"""
|
|
42
|
+
A handler class which writes logging records to the QB master process
|
|
43
|
+
via it's `QB::IPC::STDIO` system, if available.
|
|
44
|
+
|
|
45
|
+
If QB's STDIO system is not available, discards the logs.
|
|
46
|
+
|
|
47
|
+
Based on the Python stdlib's `SocketHandler`, though it ended up retaining
|
|
48
|
+
almost nothing from it since it just proxies to
|
|
49
|
+
:class:`qb.ipc.stdio.Client`, which does all the socket dirty-work.
|
|
50
|
+
|
|
51
|
+
.. note:
|
|
52
|
+
This class **does not** connect the :class:`qb.ipc.stdio.Client`
|
|
53
|
+
instance (which defaults to the 'global' :attr:`qb.ipc.stdio.client`
|
|
54
|
+
instance - and that's what you should use unless you're testing or
|
|
55
|
+
doing something weird).
|
|
56
|
+
|
|
57
|
+
You need to connect the client somewhere else (before or after creating
|
|
58
|
+
loggers is fine).
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __init__(self, io_client=qb.ipc.stdio.client):
|
|
64
|
+
"""
|
|
65
|
+
Initializes the handler with a :class:`qb.ipc.stdio.Client`, which
|
|
66
|
+
default to the 'global' one at :attr:`qb.ipc.stdio.client`. This should
|
|
67
|
+
be fine for everything except testing.
|
|
68
|
+
|
|
69
|
+
See note in class doc about connecting the client.
|
|
70
|
+
|
|
71
|
+
:param io_client: :class:`qb.ipc.stdio.Client`
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
logging.Handler.__init__(self)
|
|
75
|
+
self.io_client = io_client
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def send(self, string):
|
|
79
|
+
"""
|
|
80
|
+
Send a string to the :attr:`io_client`.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
if not self.io_client.log.connected:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
self.io_client.log.println(string)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_sem_log_level(self, level):
|
|
90
|
+
"""
|
|
91
|
+
Trade Python log level string for a Ruby SemnaticLogger one.
|
|
92
|
+
"""
|
|
93
|
+
if level == 'DEBUG' or level == 'INFO' or level == 'ERROR':
|
|
94
|
+
return level.lower()
|
|
95
|
+
elif level == 'WARNING':
|
|
96
|
+
return 'warn'
|
|
97
|
+
elif level == 'CRITICAL':
|
|
98
|
+
return 'fatal'
|
|
99
|
+
else:
|
|
100
|
+
return 'info'
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def emit(self, record):
|
|
104
|
+
"""
|
|
105
|
+
Emit a record.
|
|
106
|
+
Pickles the record and writes it to the socket in binary format.
|
|
107
|
+
If there is an error with the socket, silently drop the packet.
|
|
108
|
+
If there was a problem with the socket, re-establishes the
|
|
109
|
+
socket.
|
|
110
|
+
|
|
111
|
+
record: https://docs.python.org/2/library/logging.html#logrecord-attributes
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
self.format(record)
|
|
116
|
+
|
|
117
|
+
struct = dict(
|
|
118
|
+
level = self.get_sem_log_level(record.levelname),
|
|
119
|
+
name = record.name,
|
|
120
|
+
pid = record.process,
|
|
121
|
+
# thread = threading.current_thread().name,
|
|
122
|
+
thread = record.threadName,
|
|
123
|
+
message = record.message,
|
|
124
|
+
# timestamp = record.asctime,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# The `logging` stdlib module allows you to add extra values
|
|
128
|
+
# by providing a `extra` key to the `Logger#debug` call (and
|
|
129
|
+
# friends), which it just adds to the the keys and values to the
|
|
130
|
+
# `record` object's `#__dict__` (where they better not conflict
|
|
131
|
+
# with anything else or you'll be in trouble I guess).
|
|
132
|
+
#
|
|
133
|
+
# We look for a `payload` key in there.
|
|
134
|
+
#
|
|
135
|
+
# Example logging with a payload:
|
|
136
|
+
#
|
|
137
|
+
# logger.debug("My message", extras=dict(payload=dict(x=1)))
|
|
138
|
+
#
|
|
139
|
+
# Yeah, it sucks... TODO extend Logger or something to make it a
|
|
140
|
+
# little easier to use?
|
|
141
|
+
#
|
|
142
|
+
if 'payload' in record.__dict__:
|
|
143
|
+
struct['payload'] = record.__dict__['payload']
|
|
144
|
+
|
|
145
|
+
string = json.dumps(struct)
|
|
146
|
+
self.send(string)
|
|
147
|
+
except (KeyboardInterrupt, SystemExit):
|
|
148
|
+
raise
|
|
149
|
+
except:
|
|
150
|
+
raise
|
|
151
|
+
# self.handleError(record)
|
data/lib/qb.rb
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
# Deps
|
|
8
8
|
# -----------------------------------------------------------------------
|
|
9
9
|
require 'nrser'
|
|
10
|
+
require 'nrser/core_ext'
|
|
10
11
|
|
|
11
12
|
# Project / Package
|
|
12
13
|
# -----------------------------------------------------------------------
|
|
@@ -15,12 +16,13 @@ require 'qb/python'
|
|
|
15
16
|
require 'qb/version'
|
|
16
17
|
require 'qb/util'
|
|
17
18
|
require 'qb/path'
|
|
19
|
+
require 'qb/data'
|
|
20
|
+
require 'qb/docker'
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
# Refinements
|
|
21
24
|
# =======================================================================
|
|
22
25
|
|
|
23
|
-
using NRSER
|
|
24
26
|
using NRSER::Types
|
|
25
27
|
|
|
26
28
|
|
|
@@ -78,8 +80,6 @@ require 'qb/repo'
|
|
|
78
80
|
require 'qb/cli'
|
|
79
81
|
|
|
80
82
|
require 'qb/ansible'
|
|
81
|
-
# Depreciated namespace:
|
|
82
|
-
require 'qb/ansible_module'
|
|
83
83
|
|
|
84
84
|
require 'qb/package'
|
|
85
85
|
|
|
@@ -9,7 +9,7 @@ require 'cmds'
|
|
|
9
9
|
|
|
10
10
|
# package
|
|
11
11
|
require 'qb/util/bundler'
|
|
12
|
-
require 'qb/
|
|
12
|
+
require 'qb/ipc/stdio/server'
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
module QB; end
|
|
@@ -225,22 +225,13 @@ class QB::Ansible::Cmds::Playbook < ::Cmds
|
|
|
225
225
|
before_spawn
|
|
226
226
|
|
|
227
227
|
QB::Util::Bundler.with_clean_env do
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
stdio_out_services = {'out' => $stdout, 'err' => $stderr}.
|
|
231
|
-
map {|name, dest|
|
|
232
|
-
QB::Util::STDIO::OutService.new(name, dest).tap { |s| s.open! }
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
# and an in service so that modules can prompt for user input
|
|
236
|
-
user_in_service = QB::Util::STDIO::InService.new('in', $stdin).
|
|
237
|
-
tap { |s| s.open! }
|
|
228
|
+
# Start the STDIO server
|
|
229
|
+
stdio_server = QB::IPC::STDIO::Server.new.start!
|
|
238
230
|
|
|
239
231
|
status = super *args, **kwds, &input_block
|
|
240
232
|
|
|
241
|
-
#
|
|
242
|
-
|
|
243
|
-
user_in_service.close!
|
|
233
|
+
# ...and stop it
|
|
234
|
+
stdio_server.stop!
|
|
244
235
|
|
|
245
236
|
# and return the status
|
|
246
237
|
status
|
data/lib/qb/ansible/env.rb
CHANGED
|
@@ -48,11 +48,23 @@ class QB::Ansible::Env
|
|
|
48
48
|
attr_reader :filter_plugins
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
#
|
|
52
|
-
#
|
|
51
|
+
# Paths to search for Ansible/Jinja2 "Lookup Plugins"
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Pathname>]
|
|
54
|
+
#
|
|
53
55
|
attr_reader :lookup_plugins
|
|
54
56
|
|
|
55
57
|
|
|
58
|
+
# Paths to search for Ansible/Jinja2 "Test Plugins"
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<Pathname>]
|
|
61
|
+
#
|
|
62
|
+
attr_reader :test_plugins
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
attr_reader :python_path
|
|
66
|
+
|
|
67
|
+
|
|
56
68
|
# `ANSIBLE_CONFIG_<name>=<value>` ENV var values.
|
|
57
69
|
#
|
|
58
70
|
# @see http://docs.ansible.com/ansible/latest/intro_configuration.html
|
|
@@ -80,11 +92,20 @@ class QB::Ansible::Env
|
|
|
80
92
|
]
|
|
81
93
|
|
|
82
94
|
@filter_plugins = [
|
|
83
|
-
QB::ROOT.join('plugins', '
|
|
95
|
+
QB::ROOT.join('plugins', 'filter'),
|
|
84
96
|
]
|
|
85
97
|
|
|
86
98
|
@lookup_plugins = [
|
|
87
|
-
QB::ROOT.join('plugins', '
|
|
99
|
+
QB::ROOT.join('plugins', 'lookup'),
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
@test_plugins = [
|
|
103
|
+
QB::ROOT.join( 'plugins', 'test' ),
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
@python_path = [
|
|
107
|
+
QB::ROOT.join( 'lib', 'python' ),
|
|
108
|
+
*(ENV['PYTHONPATH'] || '').split( ':' ),
|
|
88
109
|
]
|
|
89
110
|
|
|
90
111
|
@config = {}
|
|
@@ -97,7 +118,7 @@ class QB::Ansible::Env
|
|
|
97
118
|
# @todo Document to_h method.
|
|
98
119
|
#
|
|
99
120
|
# @param [type] arg_name
|
|
100
|
-
# @todo Add name param description.
|
|
121
|
+
# @todo Add name param description.''
|
|
101
122
|
#
|
|
102
123
|
# @return [return_type]
|
|
103
124
|
# @todo Document return value.
|
|
@@ -107,7 +128,8 @@ class QB::Ansible::Env
|
|
|
107
128
|
:roles_path,
|
|
108
129
|
:library,
|
|
109
130
|
:filter_plugins,
|
|
110
|
-
:lookup_plugins
|
|
131
|
+
:lookup_plugins,
|
|
132
|
+
:test_plugins,
|
|
111
133
|
].map { |name|
|
|
112
134
|
value = self.send name
|
|
113
135
|
|
|
@@ -120,6 +142,14 @@ class QB::Ansible::Env
|
|
|
120
142
|
hash[ self.class.to_var_name( "CONFIG_#{ name }" ) ] = value.to_s
|
|
121
143
|
}
|
|
122
144
|
|
|
145
|
+
hash[ 'QB_AM_AUTORUN_PATH' ] = \
|
|
146
|
+
(QB::ROOT / 'load' / 'ansible' / 'module' / 'autorun.rb').to_s
|
|
147
|
+
|
|
148
|
+
hash[ 'QB_AM_SCRIPT_PATH' ] = \
|
|
149
|
+
(QB::ROOT / 'load' / 'ansible' / 'module' / 'script.rb').to_s
|
|
150
|
+
|
|
151
|
+
hash[ 'PYTHONPATH' ] = python_path.join ':'
|
|
152
|
+
|
|
123
153
|
hash
|
|
124
154
|
end # #to_h
|
|
125
155
|
|
data/lib/qb/ansible/module.rb
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Requirements
|
|
5
|
+
# =======================================================================
|
|
6
|
+
|
|
7
|
+
# Stdlib
|
|
8
|
+
# -----------------------------------------------------------------------
|
|
9
|
+
|
|
1
10
|
require 'json'
|
|
2
11
|
require 'pp'
|
|
3
12
|
|
|
13
|
+
# Deps
|
|
14
|
+
# ----------------------------------------------------------------------------
|
|
4
15
|
|
|
5
|
-
|
|
6
|
-
|
|
16
|
+
require 'nrser'
|
|
17
|
+
require 'nrser/props/immutable/instance_variables'
|
|
7
18
|
|
|
8
|
-
|
|
9
|
-
|
|
19
|
+
# Project / Package
|
|
20
|
+
# -----------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
require 'qb/ipc/stdio/client'
|
|
10
23
|
|
|
11
24
|
|
|
12
25
|
# Declarations
|
|
@@ -16,147 +29,358 @@ module QB; end
|
|
|
16
29
|
module QB::Ansible; end
|
|
17
30
|
|
|
18
31
|
|
|
32
|
+
# Refinements
|
|
33
|
+
# =======================================================================
|
|
34
|
+
|
|
35
|
+
using NRSER::Types
|
|
36
|
+
|
|
37
|
+
|
|
19
38
|
# Definitions
|
|
20
39
|
# =====================================================================
|
|
21
40
|
|
|
41
|
+
module QB
|
|
42
|
+
module Ansible
|
|
22
43
|
class QB::Ansible::Module
|
|
23
44
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
45
|
+
# Sub-Tree Requirements
|
|
46
|
+
# ============================================================================
|
|
47
|
+
|
|
48
|
+
require_relative './module/response'
|
|
28
49
|
|
|
29
50
|
|
|
30
|
-
#
|
|
31
|
-
#
|
|
51
|
+
# Mixins
|
|
52
|
+
# ============================================================================
|
|
32
53
|
|
|
33
|
-
|
|
34
|
-
hash.map {|k, v| [k.to_s, v]}.to_h
|
|
35
|
-
end
|
|
54
|
+
include NRSER::Props::Immutable::InstanceVariables
|
|
36
55
|
|
|
56
|
+
include NRSER::Log::Mixin
|
|
37
57
|
|
|
38
|
-
def self.arg name, type
|
|
39
|
-
@@arg_types[name.to_sym] = type
|
|
40
|
-
end
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
# Construction
|
|
59
|
+
# Class Methods
|
|
44
60
|
# =====================================================================
|
|
45
61
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
# if QB_STDIO_ env vars are set send stdout and stderr
|
|
64
|
-
# to those sockets to print in the parent process
|
|
65
|
-
|
|
66
|
-
if ENV['QB_STDIO_ERR']
|
|
67
|
-
@qb_stdio_err = $stderr = UNIXSocket.new ENV['QB_STDIO_ERR']
|
|
62
|
+
module Formatters
|
|
63
|
+
class Processor < SemanticLogger::Formatters::Default
|
|
64
|
+
|
|
65
|
+
def backtrace_to_s
|
|
66
|
+
lines = log.backtrace_to_s.lines
|
|
67
|
+
|
|
68
|
+
if lines.length > 42
|
|
69
|
+
lines = [
|
|
70
|
+
*lines[0..21],
|
|
71
|
+
"\n# ...\n\n",
|
|
72
|
+
*lines[-21..-1]
|
|
73
|
+
]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
lines.join
|
|
77
|
+
end
|
|
68
78
|
|
|
69
|
-
|
|
79
|
+
# Exception
|
|
80
|
+
def exception
|
|
81
|
+
"-- Exception: #{log.exception.class}: #{log.exception.message}\n#{backtrace_to_s}" if log.exception
|
|
82
|
+
end
|
|
70
83
|
end
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
class JSON < SemanticLogger::Formatters::Raw
|
|
86
|
+
# Default JSON time format is ISO8601
|
|
87
|
+
def initialize time_format: :iso_8601,
|
|
88
|
+
log_host: true,
|
|
89
|
+
log_application: true,
|
|
90
|
+
time_key: :timestamp
|
|
91
|
+
super(
|
|
92
|
+
time_format: time_format,
|
|
93
|
+
log_host: log_host,
|
|
94
|
+
log_application: log_application,
|
|
95
|
+
time_key: time_key,
|
|
96
|
+
)
|
|
97
|
+
end
|
|
74
98
|
|
|
75
|
-
|
|
99
|
+
def call log, logger
|
|
100
|
+
raw = super( log, logger )
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
raw.to_json
|
|
104
|
+
rescue Exception => error
|
|
105
|
+
# SemanticLogger::Processor.instance.appender.logger.warn \
|
|
106
|
+
# "Unable to JSON encode for logging", raw: raw
|
|
107
|
+
|
|
108
|
+
$stderr.puts "Unable to JSON encode log"
|
|
109
|
+
$stderr.puts raw.pretty_inspect
|
|
110
|
+
|
|
111
|
+
raise
|
|
112
|
+
end
|
|
113
|
+
end
|
|
76
114
|
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def self.setup_io!
|
|
119
|
+
# Initialize
|
|
120
|
+
$qb_stdio_client ||= QB::IPC::STDIO::Client.new.connect!
|
|
77
121
|
|
|
78
|
-
if
|
|
79
|
-
|
|
122
|
+
if $qb_stdio_client.log.connected? && NRSER::Log.appender.nil?
|
|
123
|
+
# SemanticLogger::Processor.logger = \
|
|
80
124
|
|
|
81
|
-
|
|
125
|
+
SemanticLogger::Processor.instance.appender.logger = \
|
|
126
|
+
SemanticLogger::Appender::File.new(
|
|
127
|
+
io: $stderr,
|
|
128
|
+
level: :warn,
|
|
129
|
+
formatter: Formatters::Processor.new,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
NRSER::Log.setup! \
|
|
133
|
+
application: 'qb',
|
|
134
|
+
sync: true,
|
|
135
|
+
dest: {
|
|
136
|
+
io: $qb_stdio_client.log.socket,
|
|
137
|
+
formatter: Formatters::JSON.new,
|
|
138
|
+
}
|
|
82
139
|
end
|
|
83
140
|
|
|
84
|
-
|
|
85
|
-
|
|
141
|
+
end # .setup_logging
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Wrap a "run" call with error handling.
|
|
145
|
+
#
|
|
146
|
+
# @private
|
|
147
|
+
#
|
|
148
|
+
# @param [Proc<() => RESULT] &block
|
|
149
|
+
#
|
|
150
|
+
# @return [RESULT]
|
|
151
|
+
# On success, returns the result of `&block`.
|
|
152
|
+
#
|
|
153
|
+
# @raise [SystemExit]
|
|
154
|
+
# Any exception raised in `&block` is logged at `fatal` level, then
|
|
155
|
+
# `exit false` is called, raising a {SystemExit} error.
|
|
156
|
+
#
|
|
157
|
+
# The only exception: if `&block` raises a {SystemExit} error, that error
|
|
158
|
+
# is simply re-raised without any logging. This should allow nesting
|
|
159
|
+
# {.handle_run_error} calls, since the first `rescue` will log any
|
|
160
|
+
# error and raise {SystemExit}, which will then simply be bubbled-up
|
|
161
|
+
# by {.handle_run_error} wrappers further up the call chain.
|
|
162
|
+
#
|
|
163
|
+
def self.handle_run_error &block
|
|
164
|
+
begin
|
|
165
|
+
block.call
|
|
166
|
+
rescue SystemExit => error
|
|
167
|
+
# Bubble {SystemExit} up to exit normally
|
|
168
|
+
raise
|
|
169
|
+
rescue Exception => error
|
|
170
|
+
# Everything else is unexpected, and needs to be logged in a way that's
|
|
171
|
+
# more useful than the JSON-ified crap Ansible would normally print
|
|
86
172
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
END
|
|
173
|
+
# If we don't have a logger setup, log to real `STDERR` so we get
|
|
174
|
+
# *something* back in the Ansible output, even if it's JSON mess
|
|
175
|
+
if NRSER::Log.appender.nil?
|
|
176
|
+
NRSER::Log.setup! application: 'qb', dest: STDERR
|
|
92
177
|
end
|
|
93
178
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
binding.erb <<-END
|
|
98
|
-
Value
|
|
99
|
-
|
|
100
|
-
<%= value.pretty_inspect %>
|
|
101
|
-
|
|
102
|
-
for argument <%= key.inspect %> is not valid for type
|
|
103
|
-
|
|
104
|
-
<%= type %>
|
|
105
|
-
|
|
106
|
-
Arguments:
|
|
107
|
-
|
|
108
|
-
<%= all_args.pretty_inspect %>
|
|
109
|
-
|
|
110
|
-
END
|
|
111
|
-
end
|
|
179
|
+
# Log it out
|
|
180
|
+
logger.fatal error
|
|
112
181
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
182
|
+
# And GTFO
|
|
183
|
+
exit false
|
|
184
|
+
end
|
|
185
|
+
end # .handle_run_error
|
|
186
|
+
|
|
187
|
+
private_class_method :handle_run_error
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# Is the module being run from Ansible via it's "WANT_JSON" mode?
|
|
191
|
+
#
|
|
192
|
+
# Tests if `argv` is a single string argument that is a file path.
|
|
193
|
+
#
|
|
194
|
+
# @see http://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#non-native-want-json-modules
|
|
195
|
+
#
|
|
196
|
+
# @param [Array<String>] argv
|
|
197
|
+
# The CLI argument strings.
|
|
198
|
+
#
|
|
199
|
+
# @return [Boolean]
|
|
200
|
+
# `true` if `argv` looks like it came from Ansible's "WANT_JSON" mode.
|
|
201
|
+
#
|
|
202
|
+
def self.WANT_JSON_mode? argv = ARGV
|
|
203
|
+
ARGV.length == 1 && File.file?( ARGV[0] )
|
|
204
|
+
end # .WANT_JSON_mode?
|
|
116
205
|
|
|
117
206
|
|
|
118
|
-
|
|
119
|
-
#
|
|
207
|
+
# Load args from a file in JSON format.
|
|
208
|
+
#
|
|
209
|
+
# @param [String | Pathname] file_path
|
|
210
|
+
# File path to load from.
|
|
211
|
+
#
|
|
212
|
+
# @return [Array<(Hash, Hash?)>]
|
|
213
|
+
# Tuple of:
|
|
214
|
+
#
|
|
215
|
+
# 1. `args:`
|
|
216
|
+
# - `Hash<String, *>`
|
|
217
|
+
# 2. `args_source:`
|
|
218
|
+
# - `nil | Hash{ type: :file, path: String, contents: String }`
|
|
219
|
+
#
|
|
220
|
+
def self.load_args_from_JSON_file file_path
|
|
221
|
+
file_contents = File.read file_path
|
|
222
|
+
|
|
223
|
+
args = JSON.load( file_contents ).with_indifferent_access
|
|
224
|
+
|
|
225
|
+
t.hash_( keys: t.str ).check( args ) do |type:, value:|
|
|
226
|
+
binding.erb <<~END
|
|
227
|
+
JSON file contents must load into a `Hash<String, *>`
|
|
228
|
+
|
|
229
|
+
Loaded value (of class <%= value.class %>):
|
|
230
|
+
|
|
231
|
+
<%= value.pretty_inspect %>
|
|
232
|
+
|
|
233
|
+
END
|
|
234
|
+
end
|
|
120
235
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
236
|
+
[ args, { type: :file,
|
|
237
|
+
path: file_path.to_s,
|
|
238
|
+
contents: file_contents,
|
|
239
|
+
} ]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# Load the raw arguments.
|
|
244
|
+
#
|
|
245
|
+
def self.load_args
|
|
246
|
+
if WANT_JSON_mode?
|
|
247
|
+
load_args_from_JSON_file ARGV[0]
|
|
130
248
|
else
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
249
|
+
load_args_from_CLI_options
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# Run the module!
|
|
255
|
+
#
|
|
256
|
+
# @return (see #run!)
|
|
257
|
+
#
|
|
258
|
+
def self.run!
|
|
259
|
+
handle_run_error do
|
|
260
|
+
setup_io!
|
|
143
261
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
key, value = arg[2..-1].split( '=', 2 )
|
|
147
|
-
|
|
148
|
-
@args[key] = begin
|
|
149
|
-
JSON.load value
|
|
150
|
-
rescue
|
|
151
|
-
value
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
262
|
+
args, args_source = load_args
|
|
263
|
+
run_from_args! args, args_source: args_source
|
|
155
264
|
end
|
|
156
|
-
end #
|
|
265
|
+
end # .run!
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# Create and run an instance and populate it's args by loading JSON from a
|
|
269
|
+
# file path.
|
|
270
|
+
#
|
|
271
|
+
# Used to run via Ansible's "WANT_JSON" mode.
|
|
272
|
+
#
|
|
273
|
+
# @see http://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#non-native-want-json-modules
|
|
274
|
+
#
|
|
275
|
+
# @param [String | Pathname] file_path
|
|
276
|
+
# Path to the JSON file containing the args.
|
|
277
|
+
#
|
|
278
|
+
# @return (see #run!)
|
|
279
|
+
#
|
|
280
|
+
def self.run_from_JSON_args_file! file_path
|
|
281
|
+
file_contents = File.read file_path
|
|
157
282
|
|
|
158
|
-
|
|
159
|
-
|
|
283
|
+
args = JSON.load file_contents
|
|
284
|
+
|
|
285
|
+
t.hash_( keys: t.str ).check( args ) do |type:, value:|
|
|
286
|
+
binding.erb <<~END
|
|
287
|
+
JSON file contents must load into a `Hash<String, *>`
|
|
288
|
+
|
|
289
|
+
Loaded value (of class <%= value.class %>):
|
|
290
|
+
|
|
291
|
+
<%= value.pretty_inspect %>
|
|
292
|
+
|
|
293
|
+
END
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
run_from_args! args,
|
|
297
|
+
args_source: {
|
|
298
|
+
type: :file,
|
|
299
|
+
path: file_path,
|
|
300
|
+
contents: file_contents,
|
|
301
|
+
}
|
|
302
|
+
end # .run_from_JSON_args_file!
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# Run from a hash-like of argument names mapped to values, with optional
|
|
306
|
+
# info about the source of the arguments.
|
|
307
|
+
#
|
|
308
|
+
# @param [#each_pair] args
|
|
309
|
+
# Argument names (String or Symbol) mapped to their value data.
|
|
310
|
+
#
|
|
311
|
+
# @return (see #run!)
|
|
312
|
+
#
|
|
313
|
+
def self.run_from_args! args, args_source: nil
|
|
314
|
+
logger.trace "Running from args",
|
|
315
|
+
args: args,
|
|
316
|
+
args_source: args_source
|
|
317
|
+
|
|
318
|
+
instance = self.from_data args
|
|
319
|
+
instance.args_source = args_source
|
|
320
|
+
instance.args = args
|
|
321
|
+
instance.run!
|
|
322
|
+
end # .run_from_args!
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# @todo Document arg method.
|
|
326
|
+
#
|
|
327
|
+
# @param [type] arg_name
|
|
328
|
+
# @todo Add name param description.
|
|
329
|
+
#
|
|
330
|
+
# @return [return_type]
|
|
331
|
+
# @todo Document return value.
|
|
332
|
+
#
|
|
333
|
+
def self.arg *args, **opts
|
|
334
|
+
name, opts = t.match args.length,
|
|
335
|
+
# Normal {.prop} form
|
|
336
|
+
1, ->( _ ){ [ args[0], opts ] },
|
|
337
|
+
|
|
338
|
+
# Backwards-compatible form
|
|
339
|
+
2, ->( _ ){ [ args[0], opts.merge( type: args[1] ) ] }
|
|
340
|
+
|
|
341
|
+
prop name, **opts
|
|
342
|
+
end # .arg
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# Attributes
|
|
346
|
+
# ==========================================================================
|
|
347
|
+
|
|
348
|
+
# Optional information on the source of the arguments.
|
|
349
|
+
#
|
|
350
|
+
# @return [nil | Hash<Symbol, Object>]
|
|
351
|
+
#
|
|
352
|
+
attr_accessor :args_source
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# The raw parsed arguments. Used for backwards-compatibility with how
|
|
356
|
+
# {QB::Ansible::Module} used to work before {NRSER::Props} and {#arg}.
|
|
357
|
+
#
|
|
358
|
+
# @todo
|
|
359
|
+
# May want to get rid of this once using props is totally flushed out.
|
|
360
|
+
#
|
|
361
|
+
# It should at least be deal with in the constructor somehow so this
|
|
362
|
+
# can be changed to an `attr_reader`.
|
|
363
|
+
#
|
|
364
|
+
# @return [Hash<String, VALUE>]
|
|
365
|
+
#
|
|
366
|
+
attr_accessor :args
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# The response that will be returned to Ansible (JSON-encoded and written
|
|
370
|
+
# to `STDOUT`).
|
|
371
|
+
#
|
|
372
|
+
# @return [QB::Ansible::Module::Response]
|
|
373
|
+
#
|
|
374
|
+
attr_reader :response
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# Construction
|
|
378
|
+
# =====================================================================
|
|
379
|
+
|
|
380
|
+
def initialize values = {}
|
|
381
|
+
initialize_props values
|
|
382
|
+
@response = QB::Ansible::Module::Response.new
|
|
383
|
+
end
|
|
160
384
|
|
|
161
385
|
|
|
162
386
|
# Instance Methods
|
|
@@ -176,7 +400,7 @@ class QB::Ansible::Module
|
|
|
176
400
|
# listen to, and we provide those file paths via environment variables
|
|
177
401
|
# so modules can pick those up and interact with those streams, allowing
|
|
178
402
|
# them to act like regular scripts inside Ansible-world (see
|
|
179
|
-
# QB::
|
|
403
|
+
# QB::IPC::STDIO for details and implementation).
|
|
180
404
|
#
|
|
181
405
|
# We use those channels if present to provide logging mechanisms.
|
|
182
406
|
#
|
|
@@ -187,37 +411,54 @@ class QB::Ansible::Module
|
|
|
187
411
|
# @param args see QB.debug
|
|
188
412
|
#
|
|
189
413
|
def debug *args
|
|
190
|
-
|
|
191
|
-
header = "<QB::Ansible::Module #{ self.class.name }>"
|
|
192
|
-
|
|
193
|
-
if args[0].is_a? String
|
|
194
|
-
header += " " + args.shift
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
QB.debug header, *args
|
|
198
|
-
end
|
|
414
|
+
logger.debug payload: args
|
|
199
415
|
end
|
|
200
416
|
|
|
417
|
+
|
|
418
|
+
# Old logging function - use `#logger.info` instead.
|
|
419
|
+
#
|
|
420
|
+
# @deprecated
|
|
421
|
+
#
|
|
201
422
|
def info msg
|
|
202
|
-
|
|
203
|
-
$stderr.puts msg
|
|
204
|
-
end
|
|
423
|
+
logger.info msg
|
|
205
424
|
end
|
|
206
425
|
|
|
207
|
-
|
|
426
|
+
|
|
427
|
+
# Append a warning message to the {#response}'s {Response#warnings}
|
|
428
|
+
# array and log it.
|
|
429
|
+
#
|
|
430
|
+
# @todo
|
|
431
|
+
# Should be incorporated into {#logger}? Seems like it would need one of:
|
|
432
|
+
#
|
|
433
|
+
# 1. `on_...` hooks, like `Logger#on_warn`, etc.
|
|
434
|
+
#
|
|
435
|
+
# This might be nice but I'd rather hold off on throwing more shit
|
|
436
|
+
# into {NRSER::Log::Logger} for the time being if possible.
|
|
437
|
+
#
|
|
438
|
+
# 2. Adding a custom appender when we run a module that has a ref to
|
|
439
|
+
# the module instance and so it's {Response}.
|
|
440
|
+
#
|
|
441
|
+
#
|
|
442
|
+
# @param [String] msg
|
|
443
|
+
# Non-empty string.
|
|
444
|
+
#
|
|
445
|
+
# @return [nil]
|
|
446
|
+
#
|
|
208
447
|
def warn msg
|
|
209
|
-
|
|
448
|
+
logger.warn msg
|
|
449
|
+
response.warnings << msg
|
|
450
|
+
nil
|
|
210
451
|
end
|
|
211
452
|
|
|
212
453
|
|
|
213
|
-
def run
|
|
454
|
+
def run!
|
|
214
455
|
result = main
|
|
215
456
|
|
|
216
457
|
case result
|
|
217
458
|
when nil
|
|
218
459
|
# pass
|
|
219
460
|
when Hash
|
|
220
|
-
|
|
461
|
+
response.facts.merge! result
|
|
221
462
|
else
|
|
222
463
|
raise "result of #main should be nil or Hash, found #{ result.inspect }"
|
|
223
464
|
end
|
|
@@ -225,40 +466,43 @@ class QB::Ansible::Module
|
|
|
225
466
|
done
|
|
226
467
|
end
|
|
227
468
|
|
|
469
|
+
|
|
228
470
|
def changed! facts = {}
|
|
229
|
-
|
|
230
|
-
|
|
471
|
+
response.changed = true
|
|
472
|
+
|
|
473
|
+
unless facts.empty?
|
|
474
|
+
response.facts.merge! facts
|
|
475
|
+
end
|
|
476
|
+
|
|
231
477
|
done
|
|
232
478
|
end
|
|
233
479
|
|
|
480
|
+
|
|
234
481
|
def done
|
|
235
|
-
exit_json
|
|
236
|
-
ansible_facts: self.class.stringify_keys(@facts),
|
|
237
|
-
warnings: @warnings
|
|
482
|
+
exit_json response.to_data( add_class: false ).compact
|
|
238
483
|
end
|
|
239
484
|
|
|
485
|
+
|
|
240
486
|
def exit_json hash
|
|
241
487
|
# print JSON response to process' actual STDOUT (instead of $stdout,
|
|
242
488
|
# which may be pointing to the qb parent process)
|
|
243
|
-
STDOUT.print JSON.pretty_generate(
|
|
244
|
-
|
|
245
|
-
[
|
|
246
|
-
[:stdin, @qb_stdio_in],
|
|
247
|
-
[:stdout, @qb_stdio_out],
|
|
248
|
-
[:stderr, @qb_stdio_err],
|
|
249
|
-
].each do |name, socket|
|
|
250
|
-
if socket
|
|
251
|
-
debug "Flushing socket #{ name }."
|
|
252
|
-
socket.flush
|
|
253
|
-
debug "Closing #{ name } socket at #{ socket.path.to_s }."
|
|
254
|
-
socket.close
|
|
255
|
-
end
|
|
256
|
-
end
|
|
489
|
+
STDOUT.print JSON.pretty_generate( hash.stringify_keys )
|
|
257
490
|
|
|
258
|
-
exit
|
|
491
|
+
exit true
|
|
259
492
|
end
|
|
260
493
|
|
|
261
|
-
|
|
262
|
-
|
|
494
|
+
|
|
495
|
+
def fail msg, **values
|
|
496
|
+
fail_response = QB::Ansible::Module::Response.new \
|
|
497
|
+
failed: true,
|
|
498
|
+
msg: msg.to_s,
|
|
499
|
+
warnings: response.warnings,
|
|
500
|
+
depreciations: response.depreciations
|
|
501
|
+
|
|
502
|
+
STDOUT.print \
|
|
503
|
+
JSON.pretty_generate( fail_response.to_data( add_class: false ).compact )
|
|
504
|
+
|
|
505
|
+
exit false
|
|
263
506
|
end
|
|
264
|
-
|
|
507
|
+
|
|
508
|
+
end; end; end # class QB::Ansible::Module
|