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