Class: QB::IPC::STDIO::Server

Inherits:
Object
  • Object
show all
Includes:
NRSER::Log::Mixin
Defined in:
lib/qb/ipc/stdio/server.rb

Overview

TODO:

There is also a InService for STDIN, but it's is pretty experimental / broken at this point. That would be nice to fix in the future so that programs that make use of user interaction work seamlessly through QB.

This will probably require using pseudo-TTY streams or whatever mess.

Note:

This feature only works for localhost. I have no idea what it will do in other cases. It doesn't seem like it should break anything, but remotely executing modules definitely won't be able to connect to the sockets on the host.

Server functionality to make the master QB process' STDIO streams available to external processes, specifically Ansible modules.

Ansible's handling of STDIO in modules is really not suitable for our use case - we want to see what modules and other external process commands are doing in real time, much like invoking them in a Bash script.

This thing is far from perfect, but it's been incredibly helpful for a simple solution.

Basically, OutService instances are created for STDOUT and STDERR, which each create a UNIXServer on a local socket file and spawn a Thread to listen to it. The socket's path is then made available to the Ansible child process via ENV vars, and that process in turn carries those ENV vars to it's module child processes, who can then use an instance of the corresponding Client class to connect to those sockets and write output that is passed through to the master QB process' output streams.

The protocol is simply text line-based, and modules - or any other process - written in other languages can easily connect and write as well.

Defined Under Namespace

Classes: InService, LogService, OutService, Service

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeServer

Instantiate a new QB::IPC::STDIO::Server.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/qb/ipc/stdio/server.rb', line 163

def initialize
  @socket_dir = Dir.mktmpdir( 'qb-ipc-stdio' ).to_pn
  
  @in_service   = QB::IPC::STDIO::Server::InService.new \
                    name: :in,
                    socket_dir: socket_dir,
                    src: $stdin
                    
  @out_service  = QB::IPC::STDIO::Server::OutService.new \
                    name: :out,
                    socket_dir: socket_dir,
                    dest: $stdout
                    
  @err_service  = QB::IPC::STDIO::Server::OutService.new \
                    name: :err,
                    socket_dir: socket_dir,
                    dest: $stderr
  
  @log_service  = QB::IPC::STDIO::Server::LogService.new \
                    name: :log,
                    socket_dir: socket_dir
                    
  ObjectSpace.define_finalizer \
    self,
    self.class.finalizer_for(
      object_id: object_id,
      services: services,
      socket_dir: socket_dir
    )
end

Instance Attribute Details

#socket_dirPathname (readonly)

Where the UNIX socket files get put.

Returns:

  • (Pathname)


155
156
157
# File 'lib/qb/ipc/stdio/server.rb', line 155

def socket_dir
  @socket_dir
end

Class Method Details

.clean_up_for(object_id:, services:, socket_dir:) ⇒ nil

Clean up resources for an instance. Broken out because I was trying to make it run as a finalizer to remove the directory in all cases, but that does not seem to be triggering. Whatever man...

Parameters:

  • object_id: (Fixnum)

    The instance's #object_id, just for logging purposes.

  • The (Array<Service>)

    instance's services, which we will QB::IPC::STDIO::Server::Service#close!.

  • socket_dir: (Pathname)

    The tmpdir created for the sockets, which we will remove.

Returns:

  • (nil)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/qb/ipc/stdio/server.rb', line 106

def self.clean_up_for object_id:, services:, socket_dir:
  logger.debug "Cleaning up...",
    object_id: object_id,
    socket_dir: socket_dir
  
  services.each do |service|
    logger.catch.warn(
      "Unable to close service",
      service: service,
    ) { service.close! }
  end
      
  FileUtils.rm_rf( socket_dir ) if socket_dir.exist?
  
  logger.debug "Clean!",
    object_id: object_id,
    socket_dir: socket_dir
  
  nil
end

.finalizer_for(**kwds) ⇒ Proc<() => nil>

Make a Proc to use for finalization.

Needs to be done outside instance scope to doesn't close over the instance.

Parameters:

Returns:

  • (Proc<() => nil>)

    @todo Document return value.



139
140
141
142
143
144
145
# File 'lib/qb/ipc/stdio/server.rb', line 139

def self.finalizer_for **kwds
  -> {
    logger.debug "Finalizing...", **kwds
    clean_up_for **kwds
    logger.debug "Finalized", **kwds
  }
end

Instance Method Details

#servicesArray<(InService, OutService, OutService)>

Returns Array of in, out and err services.

Returns:



201
202
203
# File 'lib/qb/ipc/stdio/server.rb', line 201

def services
  [ @in_service, @out_service, @err_service, @log_service ]
end

#start!self

Start all the #services by calling QB::IPC::STDIO::Server::Service#open! on them.

Returns:

  • (self)


210
211
212
213
# File 'lib/qb/ipc/stdio/server.rb', line 210

def start!
  services.each &:open!
  self
end

#stop!self

Stop all #services by calling QB::IPC::STDIO::Server::Service#close! on them and clean up the resources.

Returns:

  • (self)

    ]



221
222
223
224
225
226
227
# File 'lib/qb/ipc/stdio/server.rb', line 221

def stop!
  self.class.clean_up_for \
    object_id: object_id,
    services: services,
    socket_dir: socket_dir
  self
end