Module: Kontena::Cli::Helpers::ExecHelper
- Included in:
- Containers::ExecCommand, Services::ExecCommand
- Defined in:
- lib/kontena/cli/helpers/exec_helper.rb
Constant Summary collapse
- WEBSOCKET_CLIENT_OPTIONS =
{ connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 10.0, open_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 10.0, ping_interval: ENV["EXCON_READ_TIMEOUT"] ? ENV["EXCON_READ_TIMEOUT"].to_f : 30.0, ping_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 10.0, close_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 10.0, write_timeout: ENV["EXCON_WRITE_TIMEOUT"] ? ENV["EXCON_WRITE_TIMEOUT"].to_f : 10.0, }
Instance Method Summary collapse
-
#container_exec(id, cmd, **exec_options) ⇒ Integer
Execute command on container using websocket API.
-
#read_stdin(tty: nil) {|data| ... } ⇒ Object
EOF on stdin (!tty).
-
#websocket_exec(path, cmd, interactive: false, shell: false, tty: false) ⇒ Integer
Connect to server websocket, send from stdin, and write out messages.
-
#websocket_exec_read(ws) ⇒ Integer
Exit code.
- #websocket_exec_write(ws, msg) ⇒ Object
-
#websocket_exec_write_thread(ws, tty: nil) ⇒ Thread
Start thread to read from stdin, and write to websocket.
- #websocket_url(path, query = nil) ⇒ String
Instance Method Details
#container_exec(id, cmd, **exec_options) ⇒ Integer
Execute command on container using websocket API.
204 205 206 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 204 def container_exec(id, cmd, **) websocket_exec("containers/#{id}/exec", cmd, **) end |
#read_stdin(tty: nil) {|data| ... } ⇒ Object
Returns EOF on stdin (!tty).
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 34 def read_stdin(tty: nil) if tty raise ArgumentError, "the input device is not a TTY" unless STDIN.tty? STDIN.raw { |io| # we do not expect EOF on a TTY, ^D sends a tty escape to close the pty instead loop do # raises EOFError, SyscallError or IOError chunk = io.readpartial(1024) # STDIN.raw does not use the ruby external_encoding, it returns binary strings (ASCII-8BIT encoding) # however, we use websocket text frames with JSON, which expects unicode strings encodable as UTF-8, and does not handle arbitrary binary data # assume all stdin input is using ruby's external_encoding... the JSON.dump will fail if not. chunk.force_encoding(Encoding.default_external) yield chunk end } else # line-buffered, using the default external_encoding (probably UTF-8) while line = STDIN.gets yield line end end end |
#websocket_exec(path, cmd, interactive: false, shell: false, tty: false) ⇒ Integer
Connect to server websocket, send from stdin, and write out messages
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 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 193 194 195 196 197 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 137 def websocket_exec(path, cmd, interactive: false, shell: false, tty: false) exit_status = nil write_thread = nil query = {} query[:interactive] = interactive if interactive query[:shell] = shell if shell query[:tty] = tty if tty server = require_current_master url = websocket_url(path, query) token = require_token = WEBSOCKET_CLIENT_OPTIONS.dup [:headers] = { 'Authorization' => "Bearer #{token.access_token}" } [:ssl_params] = { verify_mode: ENV['SSL_IGNORE_ERRORS'].to_s == 'true' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER, ca_file: server.ssl_cert_path, } [:ssl_hostname] = server.ssl_subject_cn logger.debug { "websocket exec connect... #{url}" } # we do not expect CloseError, because the server will send an 'exit' message first, # and we return before seeing the close frame # TODO: handle HTTP 404 errors Kontena::Websocket::Client.connect(url, **) do |ws| logger.debug { "websocket exec open" } # first frame contains exec command websocket_exec_write(ws, 'cmd' => cmd) if interactive # start new thread to write from stdin to websocket write_thread = websocket_exec_write_thread(ws, tty: tty) end # blocks reading from websocket, returns with exec exit code exit_status = websocket_exec_read(ws) fail ws.close_reason unless exit_status end rescue Kontena::Websocket::Error => exc exit_with_error(exc) rescue => exc logger.error { "websocket exec error: #{exc}" } raise else logger.debug { "websocket exec exit: #{exit_status}"} return exit_status ensure if write_thread write_thread.kill write_thread.join end end |
#websocket_exec_read(ws) ⇒ Integer
Returns exit code.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 72 def websocket_exec_read(ws) ws.read do |msg| msg = JSON.parse(msg) logger.debug "websocket exec read: #{msg.inspect}" if msg.has_key?('error') raise msg['error'] elsif msg.has_key?('exit') # breaks the read loop return msg['exit'].to_i elsif msg.has_key?('stream') if msg['stream'] == 'stdout' $stdout << msg['chunk'] else $stderr << msg['chunk'] end end end end |
#websocket_exec_write(ws, msg) ⇒ Object
95 96 97 98 99 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 95 def websocket_exec_write(ws, msg) logger.debug "websocket exec write: #{msg.inspect}" ws.send(JSON.dump(msg)) end |
#websocket_exec_write_thread(ws, tty: nil) ⇒ Thread
Start thread to read from stdin, and write to websocket. Closes websocket on stdin read errors.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 107 def websocket_exec_write_thread(ws, tty: nil) Thread.new do begin if tty console_height, console_width = TTY::Screen.size websocket_exec_write(ws, 'tty_size' => { width: console_width, height: console_height }) end read_stdin(tty: tty) do |stdin| logger.debug "websocket exec stdin with encoding=#{stdin.encoding}: #{stdin.inspect}" websocket_exec_write(ws, 'stdin' => stdin) end websocket_exec_write(ws, 'stdin' => nil) # EOF rescue => exc logger.error exc ws.close(1001, "stdin read #{exc.class}: #{exc}") end end end |
#websocket_url(path, query = nil) ⇒ String
61 62 63 64 65 66 67 |
# File 'lib/kontena/cli/helpers/exec_helper.rb', line 61 def websocket_url(path, query = nil) url = URI.parse(require_current_master.url) url.scheme = url.scheme.sub('http', 'ws') url.path = '/v1/' + path url.query = (query && !query.empty?) ? URI.encode_www_form(query) : nil url.to_s end |