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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Requirements
|
|
5
|
+
# =======================================================================
|
|
6
|
+
|
|
7
|
+
# Stdlib
|
|
8
|
+
# -----------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
# Used to generate a random ID to use in socket file names
|
|
11
|
+
require 'securerandom'
|
|
12
|
+
|
|
13
|
+
# Need {FileUtils.rm_rf}
|
|
14
|
+
require 'fileutils'
|
|
15
|
+
|
|
16
|
+
# Deps
|
|
17
|
+
# -----------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
require 'nrser'
|
|
20
|
+
|
|
21
|
+
# Project / Package
|
|
22
|
+
# -----------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Declarations
|
|
27
|
+
# ========================================================================
|
|
28
|
+
|
|
29
|
+
module QB::IPC
|
|
30
|
+
module STDIO; end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Definitions
|
|
35
|
+
# =======================================================================
|
|
36
|
+
|
|
37
|
+
# Server functionality to make the master QB process' STDIO streams available
|
|
38
|
+
# to external processes, specifically Ansible modules.
|
|
39
|
+
#
|
|
40
|
+
# Ansible's handling of STDIO in modules is really not suitable for our use
|
|
41
|
+
# case - we want to see what modules and other external process commands
|
|
42
|
+
# are doing in real time, much like invoking them in a Bash script.
|
|
43
|
+
#
|
|
44
|
+
# This thing is **far** from perfect, but it's been incredibly helpful for a
|
|
45
|
+
# simple solution.
|
|
46
|
+
#
|
|
47
|
+
# Basically, {OutService} instances are created for `STDOUT` and `STDERR`,
|
|
48
|
+
# which each create a {UNIXServer} on a local socket file and spawn a {Thread}
|
|
49
|
+
# to listen to it. The socket's path is then made available to the Ansible
|
|
50
|
+
# child process via ENV vars, and that process in turn carries those ENV vars
|
|
51
|
+
# to it's module child processes, who can then use an instance of the
|
|
52
|
+
# corresponding {QB::IPC::STDIO::Client} class to connect to those sockets and
|
|
53
|
+
# write output that is passed through to the master QB process' output streams.
|
|
54
|
+
#
|
|
55
|
+
# The protocol is simply text line-based, and modules - or any other process -
|
|
56
|
+
# written in other languages can easily connect and write as well.
|
|
57
|
+
#
|
|
58
|
+
# @note
|
|
59
|
+
# This feature only works for `localhost`. I have no idea what it will do
|
|
60
|
+
# in other cases. It doesn't seem like it should break anything, but remotely
|
|
61
|
+
# executing modules definitely won't be able to connect to the sockets on
|
|
62
|
+
# the host.
|
|
63
|
+
#
|
|
64
|
+
# @todo
|
|
65
|
+
# There is also a {InService} for `STDIN`, but it's is pretty experimental /
|
|
66
|
+
# broken at this point. That would be nice to fix in the future so that
|
|
67
|
+
# programs that make use of user interaction work seamlessly through QB.
|
|
68
|
+
#
|
|
69
|
+
# This will probably require using pseudo-TTY streams or whatever mess.
|
|
70
|
+
#
|
|
71
|
+
class QB::IPC::STDIO::Server
|
|
72
|
+
|
|
73
|
+
# Sub-Tree Requirements
|
|
74
|
+
# ========================================================================
|
|
75
|
+
|
|
76
|
+
require_relative './server/in_service'
|
|
77
|
+
require_relative './server/out_service'
|
|
78
|
+
require_relative './server/log_service'
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Mixins
|
|
82
|
+
# ========================================================================
|
|
83
|
+
|
|
84
|
+
# Add {.logger} and {#logger} methods
|
|
85
|
+
include NRSER::Log::Mixin
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Class Methods
|
|
89
|
+
# ========================================================================
|
|
90
|
+
|
|
91
|
+
# Clean up resources for an instance. Broken out because I was trying to
|
|
92
|
+
# make it run as a finalizer to remove the directory in all cases, but that
|
|
93
|
+
# does not seem to be triggering. Whatever man...
|
|
94
|
+
#
|
|
95
|
+
# @param [Fixnum] object_id:
|
|
96
|
+
# The instance's `#object_id`, just for logging purposes.
|
|
97
|
+
#
|
|
98
|
+
# @param [Array<Service>]
|
|
99
|
+
# The instance's services, which we will {Service#close!}.
|
|
100
|
+
#
|
|
101
|
+
# @param [Pathname] socket_dir:
|
|
102
|
+
# The tmpdir created for the sockets, which we will remove.
|
|
103
|
+
#
|
|
104
|
+
# @return [nil]
|
|
105
|
+
#
|
|
106
|
+
def self.clean_up_for object_id:, services:, socket_dir:
|
|
107
|
+
logger.debug "Cleaning up...",
|
|
108
|
+
object_id: object_id,
|
|
109
|
+
socket_dir: socket_dir
|
|
110
|
+
|
|
111
|
+
services.each do |service|
|
|
112
|
+
logger.catch.warn(
|
|
113
|
+
"Unable to close service",
|
|
114
|
+
service: service,
|
|
115
|
+
) { service.close! }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
FileUtils.rm_rf( socket_dir ) if socket_dir.exist?
|
|
119
|
+
|
|
120
|
+
logger.debug "Clean!",
|
|
121
|
+
object_id: object_id,
|
|
122
|
+
socket_dir: socket_dir
|
|
123
|
+
|
|
124
|
+
nil
|
|
125
|
+
end # .finalize
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Make a {Proc} to use for finalization.
|
|
129
|
+
#
|
|
130
|
+
# Needs to be done outside instance scope to doesn't close over the
|
|
131
|
+
# instance.
|
|
132
|
+
#
|
|
133
|
+
# @param **kwds
|
|
134
|
+
# Passed to {.clean_up_for}.
|
|
135
|
+
#
|
|
136
|
+
# @return [Proc<() => nil>]
|
|
137
|
+
# @todo Document return value.
|
|
138
|
+
#
|
|
139
|
+
def self.finalizer_for **kwds
|
|
140
|
+
-> {
|
|
141
|
+
logger.debug "Finalizing...", **kwds
|
|
142
|
+
clean_up_for **kwds
|
|
143
|
+
logger.debug "Finalized", **kwds
|
|
144
|
+
}
|
|
145
|
+
end # .finalizer_for
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# Attributes
|
|
149
|
+
# ========================================================================
|
|
150
|
+
|
|
151
|
+
# Where the UNIX socket files get put.
|
|
152
|
+
#
|
|
153
|
+
# @return [Pathname]
|
|
154
|
+
#
|
|
155
|
+
attr_reader :socket_dir
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Construction
|
|
159
|
+
# ========================================================================
|
|
160
|
+
|
|
161
|
+
# Instantiate a new `QB::IPC::STDIO::Server`.
|
|
162
|
+
#
|
|
163
|
+
def initialize
|
|
164
|
+
@socket_dir = Dir.mktmpdir( 'qb_ipc_stdio' ).to_pn
|
|
165
|
+
|
|
166
|
+
@in_service = QB::IPC::STDIO::Server::InService.new \
|
|
167
|
+
name: :in,
|
|
168
|
+
socket_dir: socket_dir,
|
|
169
|
+
src: $stdin
|
|
170
|
+
|
|
171
|
+
@out_service = QB::IPC::STDIO::Server::OutService.new \
|
|
172
|
+
name: :out,
|
|
173
|
+
socket_dir: socket_dir,
|
|
174
|
+
dest: $stdout
|
|
175
|
+
|
|
176
|
+
@err_service = QB::IPC::STDIO::Server::OutService.new \
|
|
177
|
+
name: :err,
|
|
178
|
+
socket_dir: socket_dir,
|
|
179
|
+
dest: $stderr
|
|
180
|
+
|
|
181
|
+
@log_service = QB::IPC::STDIO::Server::LogService.new \
|
|
182
|
+
name: :log,
|
|
183
|
+
socket_dir: socket_dir
|
|
184
|
+
|
|
185
|
+
ObjectSpace.define_finalizer \
|
|
186
|
+
self,
|
|
187
|
+
self.class.finalizer_for(
|
|
188
|
+
object_id: object_id,
|
|
189
|
+
services: services,
|
|
190
|
+
socket_dir: socket_dir
|
|
191
|
+
)
|
|
192
|
+
end # #initialize
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# Instance Methods
|
|
196
|
+
# ========================================================================
|
|
197
|
+
|
|
198
|
+
# @return [Array<(InService, OutService, OutService)>]
|
|
199
|
+
# Array of in, out and err services.
|
|
200
|
+
#
|
|
201
|
+
def services
|
|
202
|
+
[ @in_service, @out_service, @err_service, @log_service ]
|
|
203
|
+
end # #services
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# Start all the {#services} by calling {Service#open!} on them.
|
|
207
|
+
#
|
|
208
|
+
# @return [self]
|
|
209
|
+
#
|
|
210
|
+
def start!
|
|
211
|
+
services.each &:open!
|
|
212
|
+
self
|
|
213
|
+
end # #open!
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# Stop all {#services} by calling {Service#close!} on them and clean up
|
|
217
|
+
# the resources.
|
|
218
|
+
#
|
|
219
|
+
# @return [self]]
|
|
220
|
+
#
|
|
221
|
+
def stop!
|
|
222
|
+
self.class.clean_up_for \
|
|
223
|
+
object_id: object_id,
|
|
224
|
+
services: services,
|
|
225
|
+
socket_dir: socket_dir
|
|
226
|
+
self
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
end # class QB::IPC::STDIO::Server
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require_relative './service'
|
|
2
|
+
|
|
3
|
+
# QB STDIO Service to proxy interactive user input from the main process
|
|
4
|
+
# to modules.
|
|
5
|
+
class QB::IPC::STDIO::Server::InService < QB::IPC::STDIO::Server::Service
|
|
6
|
+
def initialize name:, socket_dir:, src:
|
|
7
|
+
super name: name, socket_dir: socket_dir
|
|
8
|
+
@src = src
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def work_in_thread
|
|
12
|
+
while (line = @src.gets) do
|
|
13
|
+
@socket.puts line
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
close!
|
|
17
|
+
end
|
|
18
|
+
end # InService
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
require 'nrser/core_ext/hash'
|
|
2
|
+
require_relative './service'
|
|
3
|
+
|
|
4
|
+
# QB STDIO Service to receive log lines in JSON format and forward them
|
|
5
|
+
# on to the logger.
|
|
6
|
+
#
|
|
7
|
+
class QB::IPC::STDIO::Server::LogService < QB::IPC::STDIO::Server::Service
|
|
8
|
+
|
|
9
|
+
class Log < SemanticLogger::Log
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Construction
|
|
13
|
+
# ========================================================================
|
|
14
|
+
|
|
15
|
+
def initialize **kwds
|
|
16
|
+
super *kwds.values_at( :name, :level, :level_index )
|
|
17
|
+
|
|
18
|
+
if kwds.key? :timestamp
|
|
19
|
+
self.time = Time.parse kwds[:timestamp]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
self.tags = kwds[:tags] || []
|
|
23
|
+
self.named_tags = kwds[:named_tags] || {}
|
|
24
|
+
|
|
25
|
+
@pid = kwds[:pid] || '???'
|
|
26
|
+
@thread = kwds[:thread] || '???'
|
|
27
|
+
|
|
28
|
+
exception = if kwds.key? :exception
|
|
29
|
+
klass = kwds[:exception]["name"].safe_constantize
|
|
30
|
+
|
|
31
|
+
if klass
|
|
32
|
+
# HACK Good God...
|
|
33
|
+
#
|
|
34
|
+
# What we're doing is constructing an instance of the
|
|
35
|
+
# exception class so that SemLog is happy with it... so we
|
|
36
|
+
# take the class name, load that constant, then we *don't*
|
|
37
|
+
# create an instance, because that could require args, and
|
|
38
|
+
# all we need is something that holds the message and
|
|
39
|
+
# backtrace, so we add the message as the response from
|
|
40
|
+
# dynamically-created `#to_s` and `#message` methods added
|
|
41
|
+
# *to that instance only*. Then we set the backtrace using
|
|
42
|
+
# the regular instance API.
|
|
43
|
+
#
|
|
44
|
+
# ...and it kinda seems to work. But I suspect it will fuck
|
|
45
|
+
# me/us/someone at some point if left like this...
|
|
46
|
+
#
|
|
47
|
+
|
|
48
|
+
message = kwds[:exception]["message"] || '(none)'
|
|
49
|
+
|
|
50
|
+
error = klass.allocate
|
|
51
|
+
|
|
52
|
+
metaclass = class << error; self; end
|
|
53
|
+
|
|
54
|
+
[:to_s, :message].each do |name|
|
|
55
|
+
metaclass.send( :define_method, name ){ message }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if kwds[:exception]["stack_trace"]
|
|
59
|
+
error.set_backtrace kwds[:exception]["stack_trace"]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
error
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
assign exception: exception, **kwds.slice(
|
|
67
|
+
:message,
|
|
68
|
+
:payload,
|
|
69
|
+
:min_duration,
|
|
70
|
+
:metric,
|
|
71
|
+
:metric_amount,
|
|
72
|
+
:duration,
|
|
73
|
+
# :backtrace,
|
|
74
|
+
# :log_exception,
|
|
75
|
+
# :on_exception_level,
|
|
76
|
+
:dimension,
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Instance Methods
|
|
81
|
+
# ========================================================================
|
|
82
|
+
|
|
83
|
+
def process_info thread_name_length = 30
|
|
84
|
+
"IPC:#{ @pid }:#{"%.#{ thread_name_length }s" % @thread}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
end # class Log
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def initialize name:, socket_dir:
|
|
92
|
+
super name: name, socket_dir: socket_dir
|
|
93
|
+
@loggers = {}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def work_in_thread
|
|
97
|
+
while (line = @socket.gets) do
|
|
98
|
+
logger.trace "received line",
|
|
99
|
+
line: line
|
|
100
|
+
|
|
101
|
+
load_log_in_thread line
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
protected
|
|
107
|
+
# ========================================================================
|
|
108
|
+
|
|
109
|
+
# Get a {NRSER::Log::Logger} for a log name, creating them on demand
|
|
110
|
+
# and caching after that.
|
|
111
|
+
#
|
|
112
|
+
# @param [String] name
|
|
113
|
+
# Name from the log message.
|
|
114
|
+
#
|
|
115
|
+
# @return [NRSER::Log::Logger]
|
|
116
|
+
#
|
|
117
|
+
def logger_for name
|
|
118
|
+
@loggers[name] ||= NRSER::Log[name]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Log a {Log} in it's logger if it should log.
|
|
123
|
+
#
|
|
124
|
+
# @protected
|
|
125
|
+
#
|
|
126
|
+
# @param [Log] log
|
|
127
|
+
# Log instance to dispatch
|
|
128
|
+
#
|
|
129
|
+
# @return [void]
|
|
130
|
+
#
|
|
131
|
+
def write_log log
|
|
132
|
+
logger = logger_for log.name
|
|
133
|
+
# logger.level = :trace
|
|
134
|
+
logger.log( log ) if logger.should_log?( log )
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# Try to load the line into a {SemanticLogger::Log} instance.
|
|
139
|
+
#
|
|
140
|
+
def load_log_in_thread line
|
|
141
|
+
# logger.with_level :trace do
|
|
142
|
+
decoded = logger.catch.warn(
|
|
143
|
+
"Unable to decode log message",
|
|
144
|
+
line: line,
|
|
145
|
+
) { ActiveSupport::JSON.decode line }
|
|
146
|
+
|
|
147
|
+
logger.trace "Decoded log message", decoded
|
|
148
|
+
|
|
149
|
+
return nil unless decoded
|
|
150
|
+
|
|
151
|
+
logger.catch.warn(
|
|
152
|
+
"Unable to process log message",
|
|
153
|
+
line: line,
|
|
154
|
+
decoded: decoded,
|
|
155
|
+
) do
|
|
156
|
+
log = Log.new **decoded.to_options
|
|
157
|
+
|
|
158
|
+
logger.trace "Constructed {Log}",
|
|
159
|
+
log: log
|
|
160
|
+
|
|
161
|
+
write_log log
|
|
162
|
+
end # logger.catch.warn
|
|
163
|
+
# end # logger.with_level :trace
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
public # end protected ***************************************************
|
|
167
|
+
|
|
168
|
+
end # LogService
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require_relative './service'
|
|
2
|
+
|
|
3
|
+
# QB STDIO Service to proxy output from modules back to the main user
|
|
4
|
+
# process.
|
|
5
|
+
class QB::IPC::STDIO::Server::OutService < QB::IPC::STDIO::Server::Service
|
|
6
|
+
def initialize name:, socket_dir:, dest:
|
|
7
|
+
super name: name, socket_dir: socket_dir
|
|
8
|
+
@dest = dest
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def work_in_thread
|
|
12
|
+
while (line = @socket.gets) do
|
|
13
|
+
logger.trace "received line",
|
|
14
|
+
line: line,
|
|
15
|
+
dest: @dest
|
|
16
|
+
|
|
17
|
+
@dest.puts line
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end # InService
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# Requirements
|
|
2
|
+
# =====================================================================
|
|
3
|
+
|
|
4
|
+
# Stdlib
|
|
5
|
+
# ----------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
require 'thread'
|
|
8
|
+
require 'socket'
|
|
9
|
+
require 'fileutils'
|
|
10
|
+
|
|
11
|
+
# Deps
|
|
12
|
+
# ----------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
require 'nrser'
|
|
15
|
+
|
|
16
|
+
# Project
|
|
17
|
+
# ----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# Need {QB::IPC::STDIO.path_env_var_name}
|
|
20
|
+
require 'qb/ipc/stdio'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Definitions
|
|
24
|
+
# =====================================================================
|
|
25
|
+
|
|
26
|
+
# STDIO as a service exposed on a UNIX socket so that modules can stream
|
|
27
|
+
# their output to it, which is in turn printed to the console `qb` is running
|
|
28
|
+
# in.
|
|
29
|
+
class QB::IPC::STDIO::Server::Service
|
|
30
|
+
|
|
31
|
+
# Mixins
|
|
32
|
+
# ========================================================================
|
|
33
|
+
|
|
34
|
+
# Add {.logger} and {#logger}
|
|
35
|
+
include NRSER::Log::Mixin
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Attributes
|
|
39
|
+
# ========================================================================
|
|
40
|
+
|
|
41
|
+
# The service's name, like `:in`, `:out`, `:err`.
|
|
42
|
+
#
|
|
43
|
+
# @return [Synbol]
|
|
44
|
+
#
|
|
45
|
+
attr_reader :name
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Absolute path to socket file.
|
|
49
|
+
#
|
|
50
|
+
# @return [Pathname]
|
|
51
|
+
#
|
|
52
|
+
attr_reader :path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# TODO document `thread` attribute.
|
|
56
|
+
#
|
|
57
|
+
# @return [attr_type]
|
|
58
|
+
#
|
|
59
|
+
attr_reader :thread
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# TODO document `env_var_name` attribute.
|
|
63
|
+
#
|
|
64
|
+
# @return [String]
|
|
65
|
+
#
|
|
66
|
+
attr_reader :env_var_name
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# The UNIX socket server.
|
|
70
|
+
#
|
|
71
|
+
# @return [UNIXServer?]
|
|
72
|
+
#
|
|
73
|
+
attr_reader :server
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# The socket we accept from the server.
|
|
77
|
+
#
|
|
78
|
+
# @return [UNIXSocket]
|
|
79
|
+
#
|
|
80
|
+
attr_reader :socket
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Construction
|
|
84
|
+
# ========================================================================
|
|
85
|
+
|
|
86
|
+
# Construct an IO service.
|
|
87
|
+
#
|
|
88
|
+
# @param [Symbol] name
|
|
89
|
+
# What this service is for... `:in`, `:out`, `:err`...
|
|
90
|
+
#
|
|
91
|
+
# Used as the thread name.
|
|
92
|
+
#
|
|
93
|
+
def initialize name:, socket_dir:
|
|
94
|
+
@name = name
|
|
95
|
+
@thread = nil
|
|
96
|
+
@server = nil
|
|
97
|
+
@socket = nil
|
|
98
|
+
@env_var_name = QB::IPC::STDIO.path_env_var_name name
|
|
99
|
+
|
|
100
|
+
@path = socket_dir.join "#{ name }.sock"
|
|
101
|
+
|
|
102
|
+
self.logger = create_logger
|
|
103
|
+
|
|
104
|
+
logger.debug "Initialized"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
protected
|
|
109
|
+
# ========================================================================
|
|
110
|
+
|
|
111
|
+
# Initialize the {#logger}.
|
|
112
|
+
#
|
|
113
|
+
# @protected
|
|
114
|
+
# @return [nil]
|
|
115
|
+
#
|
|
116
|
+
def create_logger
|
|
117
|
+
logger = NRSER::Log[ self ]
|
|
118
|
+
|
|
119
|
+
# HACK
|
|
120
|
+
#
|
|
121
|
+
# Tracing the IO is *really* noisy and spaghettis up the log output
|
|
122
|
+
# due to the threaded nature of the this beast... which is what you
|
|
123
|
+
# *want* if you're debugging main/module process IO, since it shows
|
|
124
|
+
# you what's happening synchronously, but that's pretty much all you
|
|
125
|
+
# can debug when it's being output.
|
|
126
|
+
#
|
|
127
|
+
# The `debug`-level output is
|
|
128
|
+
#
|
|
129
|
+
# For that reason, I quickly threw
|
|
130
|
+
#
|
|
131
|
+
if ENV['QB_TRACE_STDIO'].truthy?
|
|
132
|
+
logger.level = :trace
|
|
133
|
+
elsif ENV['QB_DEBUG_STDIO'].truthy?
|
|
134
|
+
logger.level = :debug
|
|
135
|
+
else
|
|
136
|
+
logger.level = :info
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
logger
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
public # end private *****************************************************
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Instance Methods
|
|
146
|
+
# ========================================================================
|
|
147
|
+
|
|
148
|
+
# @return [String]
|
|
149
|
+
# a short string describing the instance. Used to set the name for
|
|
150
|
+
# instance loggers.
|
|
151
|
+
def to_s
|
|
152
|
+
"#<#{ self.class.name } name=#{ name.inspect } path=#{ path.to_s }>"
|
|
153
|
+
end # #to_s
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def open!
|
|
157
|
+
logger.debug "Opening..."
|
|
158
|
+
|
|
159
|
+
# make sure env var is not already set (basically just prevents you from
|
|
160
|
+
# accidentally opening two instances with the same name)
|
|
161
|
+
if ENV.key? env_var_name
|
|
162
|
+
raise "env already contains key #{ env_var_name }" \
|
|
163
|
+
"with value #{ ENV[env_var_name] }"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
@thread = Thread.new do
|
|
167
|
+
Thread.current.name = name
|
|
168
|
+
logger.trace "thread started."
|
|
169
|
+
|
|
170
|
+
@server = UNIXServer.new path.to_s
|
|
171
|
+
|
|
172
|
+
while true do
|
|
173
|
+
@socket = server.accept
|
|
174
|
+
work_in_thread
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# set the env key so children can find the socket path
|
|
179
|
+
ENV[env_var_name] = path.to_s
|
|
180
|
+
logger.debug "Set env var",
|
|
181
|
+
env_var_name => ENV[env_var_name]
|
|
182
|
+
|
|
183
|
+
logger.debug "Service open."
|
|
184
|
+
end # open
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# We're done here, clean up!
|
|
188
|
+
#
|
|
189
|
+
# @todo
|
|
190
|
+
# Not sure how correct this is... fucking threading. *Seems* to work...
|
|
191
|
+
#
|
|
192
|
+
# @return [nil]
|
|
193
|
+
#
|
|
194
|
+
def close!
|
|
195
|
+
logger.debug "Closing...",
|
|
196
|
+
socket: socket,
|
|
197
|
+
server: server,
|
|
198
|
+
path_exists: path.exist?,
|
|
199
|
+
thread: thread,
|
|
200
|
+
env_var: {
|
|
201
|
+
env_var_name => ENV[env_var_name],
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Remove the path from the ENV so if we do anything after this the
|
|
205
|
+
# old one isn't hanging around
|
|
206
|
+
ENV.delete env_var_name
|
|
207
|
+
|
|
208
|
+
# Kill the thread first so that it can't try to do anything else
|
|
209
|
+
thread.kill if thread && thread.alive?
|
|
210
|
+
|
|
211
|
+
socket.close unless socket.nil?
|
|
212
|
+
@socket = nil
|
|
213
|
+
server.close unless server.nil?
|
|
214
|
+
@server = nil
|
|
215
|
+
FileUtils.rm( path ) if path.exist?
|
|
216
|
+
|
|
217
|
+
logger.debug "Closed.",
|
|
218
|
+
socket: socket,
|
|
219
|
+
server: server,
|
|
220
|
+
path_exists: path.exist?,
|
|
221
|
+
thread: thread,
|
|
222
|
+
env_var: {
|
|
223
|
+
env_var_name => ENV[env_var_name],
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
nil
|
|
227
|
+
end # #close!
|
|
228
|
+
|
|
229
|
+
end # QB::IPC::STDIO::Server::Service
|