qb 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.vscode/.gitignore +2 -0
- data/.vscode/settings.json +3 -0
- data/VERSION +1 -1
- data/exe/qb +2 -0
- data/lib/python/qb/ansible/display_handler.py +136 -0
- data/lib/python/qb/ipc/rpc/__init__.py +0 -0
- data/lib/python/qb/ipc/rpc/client.py +170 -0
- data/lib/python/qb/ipc/stdio/__init__.py +17 -0
- data/lib/python/qb/ipc/stdio/logging.py +5 -21
- data/lib/python/qb/logging.py +97 -0
- data/lib/python/qb/strings.py +32 -3
- data/lib/qb.rb +10 -1
- data/lib/qb/ansible/cmd/playbook.rb +16 -10
- data/lib/qb/ansible/plugins/filters.rb +55 -0
- data/lib/qb/cli.rb +1 -0
- data/lib/qb/cli/dev.rb +124 -0
- data/lib/qb/cli/run.rb +283 -279
- data/lib/qb/cli/setup.rb +97 -74
- data/lib/qb/ipc/rpc.rb +42 -0
- data/lib/qb/ipc/rpc/server.rb +281 -0
- data/lib/qb/ipc/stdio/server.rb +1 -1
- data/lib/qb/role.rb +24 -18
- data/lib/qb/util.rb +173 -94
- data/lib/qb/version.rb +12 -0
- data/plugins/filter/path_filters.py +27 -10
- data/plugins/filter/rpc_filters.py +112 -0
- data/plugins/filter/string_filters.py +15 -0
- data/plugins/filter/version_filters.py +2 -4
- data/plugins/lookup/version_lookups.py +2 -2
- data/qb.gemspec +16 -5
- data/requirements.txt +3 -2
- data/roles/qb/dev/ref/archive/defaults/main.yml +24 -0
- data/roles/qb/dev/ref/archive/meta/main.yml +23 -0
- data/roles/qb/dev/ref/archive/meta/qb.yml +58 -0
- data/roles/qb/dev/ref/archive/tasks/main.yml +64 -0
- data/roles/qb/git/check/clean/tasks/main.yml +1 -1
- data/roles/qb/nodejs/yarn/setup/tasks/main.yml +1 -1
- data/roles/qb/python/config/defaults/main.yml +11 -0
- data/roles/qb/python/config/meta/main.yml +23 -0
- data/roles/qb/python/config/meta/qb.yml +58 -0
- data/roles/qb/python/config/tasks/main.yml +10 -0
- data/roles/qb/python/pip/defaults/main.yml +9 -0
- data/roles/qb/python/pip/meta/main.yml +23 -0
- data/roles/qb/python/pip/meta/qb.yml +55 -0
- data/roles/qb/python/pip/tasks/main.yml +10 -0
- data/roles/qb/python/sphinx/setup/defaults/main.yml +7 -0
- data/roles/qb/python/sphinx/setup/doc/notes/quickstart.md +81 -0
- data/roles/qb/python/sphinx/setup/meta/main.yml +23 -0
- data/roles/qb/python/sphinx/setup/meta/qb.yml +55 -0
- data/roles/qb/python/sphinx/setup/tasks/main.yml +10 -0
- data/roles/qb/ruby/gem/release/tasks/main.yml +5 -0
- metadata +73 -43
- data/exe/.qb_interop_receive +0 -32
- data/lib/python/qb/interop.py +0 -90
- data/lib/qb/util/interop.rb +0 -107
- data/plugins/filter/ruby_interop_filters.py +0 -30
- data/roles/qb/ruby/nrser/rspex/generate/defaults/main.yml +0 -10
- data/roles/qb/ruby/nrser/rspex/generate/meta/main.yml +0 -8
- data/roles/qb/ruby/nrser/rspex/generate/meta/qb.yml +0 -91
- data/roles/qb/ruby/nrser/rspex/generate/tasks/class.yml +0 -22
- data/roles/qb/ruby/nrser/rspex/generate/tasks/helpers.yml +0 -96
- data/roles/qb/ruby/nrser/rspex/generate/tasks/main.yml +0 -9
- data/roles/qb/ruby/nrser/rspex/generate/templates/class_spec.rb.j2 +0 -10
- data/roles/qb/ruby/nrser/rspex/issue/defaults/main.yml +0 -2
- data/roles/qb/ruby/nrser/rspex/issue/meta/main.yml +0 -8
- data/roles/qb/ruby/nrser/rspex/issue/meta/qb.yml +0 -69
- data/roles/qb/ruby/nrser/rspex/issue/tasks/main.yml +0 -21
- data/roles/qb/setup/meta/main.yml +0 -8
- data/roles/qb/setup/meta/qb.yml +0 -69
- data/roles/qb/setup/tasks/main.yml +0 -6
- data/roles/qb/setup/tasks/packages.yml +0 -3
- data/roles/qb/setup/tasks/packages/pip.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1adb8b34388a2f7459a5acff24162cb7c3959401
|
4
|
+
data.tar.gz: 16174eacdd3a1b2bdd19084d674d2834284c7dfc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a214e3865fd351527b73637e87e1a53358c88362c24966fa64ced70868241ae4db773bb35d62289dc726c7fe66e8dfa607be3620a6340fd4c04d5a6aa8e48c8b
|
7
|
+
data.tar.gz: 99358c88001aa83cfdebdc9bc6b4af4fcc03468715c637f152cba56edb6e52b2fd73303a6198ffb02cd5883fd921b0904a30ac562ee5660de4a5aeacb0ecd6ad
|
data/.vscode/.gitignore
ADDED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.4
|
data/exe/qb
CHANGED
@@ -0,0 +1,136 @@
|
|
1
|
+
from __future__ import absolute_import, division, print_function
|
2
|
+
__metaclass__ = type
|
3
|
+
|
4
|
+
import logging
|
5
|
+
import qb.logging
|
6
|
+
|
7
|
+
# Find a Display if possible
|
8
|
+
try:
|
9
|
+
from __main__ import display
|
10
|
+
except ImportError:
|
11
|
+
try:
|
12
|
+
from ansible.utils.display import Display
|
13
|
+
except ImportError:
|
14
|
+
display = None
|
15
|
+
else:
|
16
|
+
display = Display()
|
17
|
+
|
18
|
+
|
19
|
+
class DisplayHandler(logging.Handler):
|
20
|
+
'''
|
21
|
+
A handler class that writes messages to Ansible's
|
22
|
+
`ansible.utils.display.Display`, which then writes them to the user output.
|
23
|
+
|
24
|
+
Includes static methods that let it act as a sort of a singleton, with
|
25
|
+
a single instance created on-demand.
|
26
|
+
'''
|
27
|
+
|
28
|
+
|
29
|
+
# Singleton instance
|
30
|
+
_instance = None
|
31
|
+
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def getDisplay():
|
35
|
+
'''
|
36
|
+
Get the display instance, if we were able to import or create one.
|
37
|
+
|
38
|
+
:rtype: None
|
39
|
+
:return: No display could be found or created.
|
40
|
+
|
41
|
+
:rtype: ansible.util.display.Display
|
42
|
+
:return: The display we're using.
|
43
|
+
'''
|
44
|
+
return display
|
45
|
+
# .getDisplay
|
46
|
+
|
47
|
+
|
48
|
+
@staticmethod
|
49
|
+
def getInstance():
|
50
|
+
'''
|
51
|
+
:rtype: DisplayHandler
|
52
|
+
:return: The singleton instance.
|
53
|
+
'''
|
54
|
+
if DisplayHandler._instance is None:
|
55
|
+
DisplayHandler._instance = DisplayHandler()
|
56
|
+
return DisplayHandler._instance
|
57
|
+
# .getInstance
|
58
|
+
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def enable():
|
62
|
+
'''
|
63
|
+
Enable logging to Ansible's display by sending {.getInstance()} to
|
64
|
+
{qb.logging.addHandler()}.
|
65
|
+
|
66
|
+
:raises:
|
67
|
+
'''
|
68
|
+
instance = DisplayHandler.getInstance()
|
69
|
+
|
70
|
+
if instance.display is None:
|
71
|
+
raise RuntimeError("No display available")
|
72
|
+
|
73
|
+
return qb.logging.addHandler(instance)
|
74
|
+
# .enable
|
75
|
+
|
76
|
+
|
77
|
+
def disable():
|
78
|
+
'''
|
79
|
+
Disable logging to Ansible's display be sending {.getInstance()} to
|
80
|
+
{qb.logging.removeHandler()}.
|
81
|
+
'''
|
82
|
+
return qb.logging.removeHandler(DisplayHandler.getInstance())
|
83
|
+
# .disable
|
84
|
+
|
85
|
+
|
86
|
+
def is_enabled():
|
87
|
+
return qb.logging.hasHandler(DisplayHandler.getInstance())
|
88
|
+
# .is_enabled
|
89
|
+
|
90
|
+
|
91
|
+
def __init__(self, display=None):
|
92
|
+
logging.Handler.__init__(self)
|
93
|
+
|
94
|
+
if display is None:
|
95
|
+
display = DisplayHandler.getDisplay()
|
96
|
+
|
97
|
+
self.display = display
|
98
|
+
# #__init__
|
99
|
+
|
100
|
+
|
101
|
+
def emit(self, record):
|
102
|
+
'''
|
103
|
+
Overridden to send log records to Ansible's display.
|
104
|
+
'''
|
105
|
+
|
106
|
+
if self.display is None:
|
107
|
+
# Nothing we can do, drop it
|
108
|
+
return
|
109
|
+
|
110
|
+
try:
|
111
|
+
self.format(record)
|
112
|
+
|
113
|
+
if record.levelname == 'DEBUG':
|
114
|
+
self.display.debug(record.message)
|
115
|
+
|
116
|
+
elif record.levelname == 'INFO':
|
117
|
+
# `verbose` I guess?
|
118
|
+
self.display.verbose(record.message)
|
119
|
+
|
120
|
+
elif record.levelname == 'WARNING':
|
121
|
+
self.display.warning(record.message)
|
122
|
+
|
123
|
+
elif record.levelname == 'ERROR':
|
124
|
+
self.display.error(record.message)
|
125
|
+
|
126
|
+
elif record.levelname == 'CRITICAL':
|
127
|
+
self.display.error("(CRITICAL) {}".format(record.message))
|
128
|
+
|
129
|
+
else:
|
130
|
+
pass
|
131
|
+
except (KeyboardInterrupt, SystemExit):
|
132
|
+
raise
|
133
|
+
except:
|
134
|
+
raise
|
135
|
+
# self.handleError(record)
|
136
|
+
# #emit
|
File without changes
|
@@ -0,0 +1,170 @@
|
|
1
|
+
##############################################################################
|
2
|
+
#
|
3
|
+
##############################################################################
|
4
|
+
|
5
|
+
# Imports
|
6
|
+
# ============================================================================
|
7
|
+
|
8
|
+
# Make Python 3-ish
|
9
|
+
from __future__ import (absolute_import, division, print_function)
|
10
|
+
__metaclass__ = type
|
11
|
+
|
12
|
+
# Stdlib
|
13
|
+
# ----------------------------------------------------------------------------
|
14
|
+
|
15
|
+
# Need {os.environ}, {os.path.join}
|
16
|
+
import os
|
17
|
+
|
18
|
+
# Need {urllib.quote_plus} to URL-encode socket paths for the
|
19
|
+
# {requests_unixsocket} package.
|
20
|
+
import urllib
|
21
|
+
|
22
|
+
# Deps
|
23
|
+
# ----------------------------------------------------------------------------
|
24
|
+
|
25
|
+
# Facilities making requests to UNIX domain sockets with the {requests}
|
26
|
+
# package.
|
27
|
+
import requests_unixsocket
|
28
|
+
|
29
|
+
|
30
|
+
# Constants
|
31
|
+
# ============================================================================
|
32
|
+
|
33
|
+
# The name of the ENV var that holds the socket path, which is created in a
|
34
|
+
# temp dir that only exists while the main QB process is running and is only
|
35
|
+
# accessible to the user that ran it.
|
36
|
+
#
|
37
|
+
RPC_SOCKET_ENV_VAR_NAME = 'QB_RPC_SOCKET'
|
38
|
+
|
39
|
+
|
40
|
+
# Module Variables
|
41
|
+
# ============================================================================
|
42
|
+
|
43
|
+
# A "module-level global" (yeah, weird they're called "globals") that holds
|
44
|
+
# the default {Client}, which is created on demand.
|
45
|
+
#
|
46
|
+
# Need to preface it's use with
|
47
|
+
#
|
48
|
+
# global _client
|
49
|
+
#
|
50
|
+
_client = None
|
51
|
+
|
52
|
+
|
53
|
+
# Module Functions
|
54
|
+
# ============================================================================
|
55
|
+
#
|
56
|
+
# Static helpers and functions that operate on the default {Client}, which is
|
57
|
+
# created on demand using the ENV var set by the QB master process that hosts
|
58
|
+
# the server (during normal execution... things are set up flexibly because I'm
|
59
|
+
# sure we'd want to do things differently during testing).
|
60
|
+
#
|
61
|
+
# NOTE Due to the way Python's files<->imports system works, this seems like
|
62
|
+
# the least annoying way to create a decent API without sticking stuff in
|
63
|
+
# the `__init__.py` file, which I *hate* because it's really hard to
|
64
|
+
# remember which ones have shit in them.
|
65
|
+
#
|
66
|
+
|
67
|
+
def client_from_env():
|
68
|
+
return Client(socket_path=os.environ[RPC_SOCKET_ENV_VAR_NAME])
|
69
|
+
|
70
|
+
|
71
|
+
def requests_path_for(socket_path):
|
72
|
+
'''
|
73
|
+
URL-quotes the socket file path and protocol prefixes it with `http+unix://`
|
74
|
+
|
75
|
+
The {requests} module - as extended by {requests_unixsocket} - requires that
|
76
|
+
the actual file path to the socket by URL-quoted, probably because it uses
|
77
|
+
some split-by-/ logic to parse it, which would normally consider the socket
|
78
|
+
path part of the HTTP path.
|
79
|
+
|
80
|
+
:rtype: str
|
81
|
+
:return: Path ready for use with {requests_unixsocket.Session}.
|
82
|
+
'''
|
83
|
+
return "http+unix://{}".format(urllib.quote_plus(socket_path))
|
84
|
+
|
85
|
+
|
86
|
+
def init_from_env(force=False):
|
87
|
+
global _client
|
88
|
+
|
89
|
+
if _client is None or force is True:
|
90
|
+
_client = client_from_env()
|
91
|
+
return True
|
92
|
+
else:
|
93
|
+
return False
|
94
|
+
|
95
|
+
|
96
|
+
def get_client():
|
97
|
+
global _client
|
98
|
+
init_from_env()
|
99
|
+
return _client
|
100
|
+
|
101
|
+
|
102
|
+
def set_client(client):
|
103
|
+
global _client
|
104
|
+
_client = client
|
105
|
+
|
106
|
+
|
107
|
+
def get(path):
|
108
|
+
return get_client().get(path)
|
109
|
+
|
110
|
+
|
111
|
+
def post(path, **payload):
|
112
|
+
return get_client().post(path, **payload)
|
113
|
+
|
114
|
+
|
115
|
+
def send(receiver, method, *args, **kwds):
|
116
|
+
return get_client().send(receiver, method, *args, **kwds)
|
117
|
+
|
118
|
+
|
119
|
+
class Client:
|
120
|
+
'''
|
121
|
+
RPC client for making calls to the QB master Ruby process (HTTP over
|
122
|
+
a UNIX domain socket).
|
123
|
+
'''
|
124
|
+
|
125
|
+
def __init__(self, socket_path):
|
126
|
+
self.socket_path = socket_path
|
127
|
+
self.session = requests_unixsocket.Session()
|
128
|
+
self.requests_path = requests_path_for(self.socket_path)
|
129
|
+
|
130
|
+
|
131
|
+
def full_path_for(self, path):
|
132
|
+
if path[0] == '/':
|
133
|
+
path = path[1:]
|
134
|
+
return os.path.join(self.requests_path, path)
|
135
|
+
|
136
|
+
|
137
|
+
def handle_response(self, response):
|
138
|
+
return response.json()['data']
|
139
|
+
|
140
|
+
|
141
|
+
def get(self, path):
|
142
|
+
return self.handle_response(
|
143
|
+
self.session.get(
|
144
|
+
self.full_path_for(path)
|
145
|
+
)
|
146
|
+
)
|
147
|
+
|
148
|
+
|
149
|
+
def post(self, path, **payload):
|
150
|
+
return self.handle_response(
|
151
|
+
self.session.post(
|
152
|
+
self.full_path_for(path),
|
153
|
+
json = payload,
|
154
|
+
)
|
155
|
+
)
|
156
|
+
|
157
|
+
|
158
|
+
def send(self, receiver, method, *args, **kwds):
|
159
|
+
return self.handle_response(
|
160
|
+
self.session.post(
|
161
|
+
self.full_path_for('/send'),
|
162
|
+
json = dict(
|
163
|
+
receiver = receiver,
|
164
|
+
method = method,
|
165
|
+
args = args,
|
166
|
+
kwds = kwds,
|
167
|
+
)
|
168
|
+
)
|
169
|
+
)
|
170
|
+
|
@@ -4,7 +4,24 @@ __metaclass__ = type
|
|
4
4
|
import os
|
5
5
|
import socket
|
6
6
|
|
7
|
+
|
7
8
|
def path_env_var_name(name):
|
9
|
+
'''
|
10
|
+
Get the ENV var name whose value (if any) will be the file system path
|
11
|
+
to the UNIX socket file for that IO stream.
|
12
|
+
|
13
|
+
The names in current use:
|
14
|
+
|
15
|
+
>>> path_env_var_name('out')
|
16
|
+
'QB_STDIO_OUT'
|
17
|
+
|
18
|
+
>>> path_env_var_name('err')
|
19
|
+
'QB_STDIO_ERR'
|
20
|
+
|
21
|
+
>>> path_env_var_name('log')
|
22
|
+
'QB_STDIO_LOG'
|
23
|
+
'''
|
24
|
+
|
8
25
|
return "QB_STDIO_{}".format(name.upper())
|
9
26
|
|
10
27
|
|
@@ -5,9 +5,11 @@ import logging
|
|
5
5
|
import threading
|
6
6
|
import json
|
7
7
|
|
8
|
+
import qb.logging
|
8
9
|
import qb.ipc.stdio
|
9
10
|
|
10
11
|
|
12
|
+
# FIXME After adding central logging stuff
|
11
13
|
def getLogger(name, level=logging.DEBUG, io_client=qb.ipc.stdio.client):
|
12
14
|
logger = logging.getLogger(name)
|
13
15
|
if level is not None:
|
@@ -16,27 +18,6 @@ def getLogger(name, level=logging.DEBUG, io_client=qb.ipc.stdio.client):
|
|
16
18
|
return Adapter(logger, {})
|
17
19
|
|
18
20
|
|
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
21
|
class Handler(logging.Handler):
|
41
22
|
"""
|
42
23
|
A handler class which writes logging records to the QB master process
|
@@ -103,6 +84,9 @@ class Handler(logging.Handler):
|
|
103
84
|
def emit(self, record):
|
104
85
|
"""
|
105
86
|
Emit a record.
|
87
|
+
|
88
|
+
Doc coppied in (TOOD re-write):
|
89
|
+
|
106
90
|
Pickles the record and writes it to the socket in binary format.
|
107
91
|
If there is an error with the socket, silently drop the packet.
|
108
92
|
If there was a problem with the socket, re-establishes the
|
@@ -0,0 +1,97 @@
|
|
1
|
+
from __future__ import absolute_import, division, print_function
|
2
|
+
__metaclass__ = type
|
3
|
+
|
4
|
+
import logging
|
5
|
+
import threading
|
6
|
+
import json
|
7
|
+
import weakref
|
8
|
+
|
9
|
+
import qb.ipc.stdio
|
10
|
+
|
11
|
+
# Current handlers
|
12
|
+
_handlers = []
|
13
|
+
|
14
|
+
|
15
|
+
def hasHandler(handler):
|
16
|
+
return handler in _handlers
|
17
|
+
# hasHandler
|
18
|
+
|
19
|
+
|
20
|
+
def addHandler(handler):
|
21
|
+
'''
|
22
|
+
Add a handler if it's not already added.
|
23
|
+
|
24
|
+
:rtype: Boolean
|
25
|
+
:return: `True` if it was added (not already there), `False` if already
|
26
|
+
present.
|
27
|
+
'''
|
28
|
+
if not handler in _handlers:
|
29
|
+
_handlers.append(handler)
|
30
|
+
return True
|
31
|
+
else:
|
32
|
+
return False
|
33
|
+
# addHandler
|
34
|
+
|
35
|
+
|
36
|
+
def removeHandler(handler):
|
37
|
+
'''
|
38
|
+
Remove a handler.
|
39
|
+
|
40
|
+
:rtype: Boolean
|
41
|
+
:return: `True` if it was removed, `False` if wasn't there to begin
|
42
|
+
with.
|
43
|
+
'''
|
44
|
+
if handler in _handlers:
|
45
|
+
_handlers.remove(handler)
|
46
|
+
return True
|
47
|
+
else:
|
48
|
+
return False
|
49
|
+
# removeHandler
|
50
|
+
|
51
|
+
|
52
|
+
def getLogger(name, level=logging.DEBUG):
|
53
|
+
logger = logging.getLogger(name)
|
54
|
+
if level is not None:
|
55
|
+
logger.setLevel(level)
|
56
|
+
for handler in _handlers:
|
57
|
+
logger.addHandler(handler)
|
58
|
+
return Adapter(logger, {})
|
59
|
+
|
60
|
+
|
61
|
+
class Adapter(logging.LoggerAdapter):
|
62
|
+
'''
|
63
|
+
Adapter to allow Ruby's Semantic Logger (basis of NRSER::Log) style logging,
|
64
|
+
which is then easy to translate when sending logs up to the QB master
|
65
|
+
process via IPC.
|
66
|
+
|
67
|
+
Usage:
|
68
|
+
|
69
|
+
logger.info(
|
70
|
+
"Message with payload {value} interpolations",
|
71
|
+
payload = dict(
|
72
|
+
value = "interpolated into message",
|
73
|
+
mote = "values",
|
74
|
+
# ...
|
75
|
+
)
|
76
|
+
)
|
77
|
+
|
78
|
+
'''
|
79
|
+
|
80
|
+
def process(self, msg, kwds):
|
81
|
+
payload = None
|
82
|
+
if 'payload' in kwds:
|
83
|
+
payload = kwds['payload']
|
84
|
+
del kwds['payload']
|
85
|
+
|
86
|
+
if payload:
|
87
|
+
try:
|
88
|
+
msg = msg.format(**payload)
|
89
|
+
except:
|
90
|
+
pass
|
91
|
+
|
92
|
+
if 'extra' not in kwds:
|
93
|
+
kwds['extra'] = {}
|
94
|
+
|
95
|
+
kwds['extra']['payload'] = payload
|
96
|
+
|
97
|
+
return msg, kwds
|