lxd-common 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/lib/nexussw/lxd/driver/mixins/cli.rb +1 -1
- data/lib/nexussw/lxd/driver/mixins/rest.rb +29 -28
- data/lib/nexussw/lxd/rest_api/connection.rb +98 -0
- data/lib/nexussw/lxd/rest_api/errors.rb +9 -0
- data/lib/nexussw/lxd/rest_api.rb +130 -0
- data/lib/nexussw/lxd/transport/mixins/cli.rb +2 -1
- data/lib/nexussw/lxd/transport/mixins/helpers/execute.rb +5 -3
- data/lib/nexussw/lxd/transport/mixins/helpers/upload_folder.rb +4 -3
- data/lib/nexussw/lxd/transport/mixins/local.rb +1 -1
- data/lib/nexussw/lxd/transport/mixins/rest.rb +45 -24
- data/lib/nexussw/lxd/version.rb +1 -1
- data/lib/nexussw/lxd.rb +11 -0
- data/lxd-common.gemspec +2 -8
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 031530f1093174e61e18c8a58e85805376bbc9b80368c800f7cc28cc3476e8ac
|
4
|
+
data.tar.gz: 0dea2d0bdb81700c0b0c05b990620fef2ae999b0828d1ddee0714e54a914909f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 677d8545906f020bb8a485362cc39e8b7d809a96822fdeb05225d65f9da79bd1865c5105c3b3706946fc29ed09d3d0cbab4efb8c67c4acdd8724d6f41184f75f
|
7
|
+
data.tar.gz: 18772ed5c102a70bc3e0fdacb0742c7de807d210e339ba83d1db5efc61fc529e36ef423f364f630a3215564e99225b2771572d3699e231617fb2dbad3643f8a4
|
data/.travis.yml
CHANGED
@@ -103,7 +103,7 @@ module NexusSW
|
|
103
103
|
res = inner_transport.execute("lxc list #{container_id} --format=json")
|
104
104
|
res.error!
|
105
105
|
JSON.parse(res.stdout).each do |c|
|
106
|
-
return convert_keys(c.
|
106
|
+
return convert_keys(c.reject { |k, _| k == 'state' }) if c['name'] == container_id
|
107
107
|
end
|
108
108
|
nil
|
109
109
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
+
require 'nexussw/lxd/rest_api'
|
1
2
|
require 'nexussw/lxd/driver/mixins/helpers/wait'
|
2
3
|
require 'nexussw/lxd/transport/rest'
|
3
|
-
require 'hyperkit'
|
4
4
|
|
5
5
|
module NexusSW
|
6
6
|
module LXD
|
@@ -10,27 +10,26 @@ module NexusSW
|
|
10
10
|
# PARITY note: CLI functions are on an indefinite timeout by default, yet we have a 2 minute socket read timeout
|
11
11
|
# Leaving it alone, for now, on calls that are quick in nature
|
12
12
|
# Adapting on known long running calls such as create, stop, execute
|
13
|
-
# REQUEST_TIMEOUT = 120 # upstream default: 120
|
14
13
|
def initialize(rest_endpoint, driver_options = {}, inner_driver = nil)
|
15
14
|
@rest_endpoint = rest_endpoint
|
16
15
|
@driver_options = driver_options
|
17
|
-
|
16
|
+
apioptions = (driver_options || {}).merge(
|
18
17
|
api_endpoint: rest_endpoint,
|
19
18
|
auto_sync: true
|
20
19
|
)
|
21
|
-
@
|
20
|
+
@api = inner_driver || RestAPI.new(apioptions)
|
22
21
|
end
|
23
22
|
|
24
|
-
attr_reader :
|
23
|
+
attr_reader :api, :rest_endpoint, :driver_options
|
25
24
|
|
26
25
|
include Helpers::WaitMixin
|
27
26
|
|
28
27
|
def server_info
|
29
|
-
@server_info ||=
|
28
|
+
@server_info ||= api.get('/1.0')[:metadata]
|
30
29
|
end
|
31
30
|
|
32
31
|
def transport_for(container_name)
|
33
|
-
Transport::Rest.new container_name, info: server_info, connection:
|
32
|
+
Transport::Rest.new container_name, info: server_info, connection: api, driver_options: driver_options, rest_endpoint: rest_endpoint
|
34
33
|
end
|
35
34
|
|
36
35
|
def create_container(container_name, container_options = {})
|
@@ -40,7 +39,7 @@ module NexusSW
|
|
40
39
|
end
|
41
40
|
# parity note: CLI will run indefinitely rather than timeout hence the 0 timeout
|
42
41
|
retry_forever do
|
43
|
-
|
42
|
+
api.create_container(container_name, container_options.merge(sync: false))
|
44
43
|
end
|
45
44
|
start_container container_name
|
46
45
|
container_name
|
@@ -49,7 +48,7 @@ module NexusSW
|
|
49
48
|
def start_container(container_id)
|
50
49
|
return if container_status(container_id) == 'running'
|
51
50
|
retry_forever do
|
52
|
-
|
51
|
+
api.start_container(container_id, sync: false)
|
53
52
|
end
|
54
53
|
wait_for_status container_id, 'running'
|
55
54
|
end
|
@@ -57,7 +56,7 @@ module NexusSW
|
|
57
56
|
def stop_container(container_id, options = {})
|
58
57
|
return if container_status(container_id) == 'stopped'
|
59
58
|
if options[:force]
|
60
|
-
|
59
|
+
api.stop_container(container_id, force: true)
|
61
60
|
else
|
62
61
|
last_id = nil
|
63
62
|
use_last = false
|
@@ -67,14 +66,14 @@ module NexusSW
|
|
67
66
|
unless use_last
|
68
67
|
# Keep resubmitting until the server complains (Stops will be ignored/hang if init is not yet listening for SIGPWR i.e. recently started)
|
69
68
|
begin
|
70
|
-
last_id =
|
71
|
-
rescue
|
69
|
+
last_id = api.stop_container(container_id, sync: false)[:metadata][:id]
|
70
|
+
rescue NexusSW::LXD::RestAPI::Error::BadRequest # Happens if a stop command has previously been accepted as well as other reasons. handle that on next line
|
72
71
|
# if we have a last_id then a prior stop command has successfully initiated so we'll just wait on that one
|
73
72
|
raise unless last_id # rubocop:disable Metrics/BlockNesting
|
74
73
|
use_last = true
|
75
74
|
end
|
76
75
|
end
|
77
|
-
|
76
|
+
api.wait_for_operation last_id # , options[:retry_interval]
|
78
77
|
rescue Faraday::TimeoutError => e
|
79
78
|
return if container_status(container_id) == 'stopped'
|
80
79
|
raise Timeout::Retry.new e # if options[:retry_interval] # rubocop:disable Style/RaiseArgs
|
@@ -89,33 +88,35 @@ module NexusSW
|
|
89
88
|
stop_container container_id, force: true
|
90
89
|
|
91
90
|
# ISSUE 17: something upstream is causing a double-tap on the REST endpoint
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
91
|
+
|
92
|
+
# trial return to normal
|
93
|
+
# begin
|
94
|
+
api.delete_container container_id
|
95
|
+
# rescue ::Faraday::ConnectionFailed, ::NexusSW::LXD::RestAPI::Error::BadRequest
|
96
|
+
# LXD.with_timeout_and_retries timeout: 120 do
|
97
|
+
# loop do
|
98
|
+
# return unless container_exists? container_id
|
99
|
+
# sleep 0.3
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
# end
|
102
103
|
end
|
103
104
|
|
104
105
|
def container_status(container_id)
|
105
|
-
STATUS_CODES[container(container_id)[:status_code].to_i]
|
106
|
+
STATUS_CODES[api.container(container_id)[:metadata][:status_code].to_i]
|
106
107
|
end
|
107
108
|
|
108
109
|
def container_state(container_id)
|
109
110
|
return nil unless container_status(container_id) == 'running' # Parity with CLI
|
110
|
-
|
111
|
+
api.container_state(container_id)[:metadata]
|
111
112
|
end
|
112
113
|
|
113
114
|
def container(container_id)
|
114
|
-
|
115
|
+
api.container(container_id)[:metadata]
|
115
116
|
end
|
116
117
|
|
117
118
|
def container_exists?(container_id)
|
118
|
-
|
119
|
+
api.containers[:metadata].map { |url| url.split('/').last }.include? container_id
|
119
120
|
end
|
120
121
|
|
121
122
|
protected
|
@@ -133,7 +134,7 @@ module NexusSW
|
|
133
134
|
retval = yield
|
134
135
|
LXD.with_timeout_and_retries timeout: 0 do
|
135
136
|
begin
|
136
|
-
|
137
|
+
api.wait_for_operation retval[:metadata][:id]
|
137
138
|
rescue Faraday::TimeoutError => e
|
138
139
|
raise Timeout::Retry.new e # rubocop:disable Style/RaiseArgs
|
139
140
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'json'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module NexusSW
|
6
|
+
module LXD
|
7
|
+
class RestAPI
|
8
|
+
module Connection
|
9
|
+
def get(relative_url, &block)
|
10
|
+
send_request :get, relative_url, &block
|
11
|
+
end
|
12
|
+
|
13
|
+
def put(relative_url, content)
|
14
|
+
send_request :put, relative_url, content
|
15
|
+
end
|
16
|
+
|
17
|
+
def patch(relative_url, content)
|
18
|
+
send_request :patch, relative_url, content
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(relative_url, content, &block)
|
22
|
+
send_request :post, relative_url, content, &block
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(relative_url)
|
26
|
+
send_request :delete, relative_url
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def connection(&block)
|
32
|
+
return @conn if @conn
|
33
|
+
|
34
|
+
opts = {
|
35
|
+
url: baseurl,
|
36
|
+
ssl: {
|
37
|
+
verify: verify_ssl,
|
38
|
+
client_cert: OpenSSL::X509::Certificate.new(cert),
|
39
|
+
client_key: OpenSSL::PKey::RSA.new(key),
|
40
|
+
},
|
41
|
+
}
|
42
|
+
|
43
|
+
@conn = Faraday.new opts, &block
|
44
|
+
end
|
45
|
+
|
46
|
+
def baseurl
|
47
|
+
api_options[:api_endpoint]
|
48
|
+
end
|
49
|
+
|
50
|
+
def ssl_opts
|
51
|
+
api_options[:ssl] || {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def cert
|
55
|
+
File.read(ssl_opts[:client_cert] || "#{ENV['HOME']}/.config/lxc/client.crt")
|
56
|
+
end
|
57
|
+
|
58
|
+
def key
|
59
|
+
File.read(ssl_opts[:client_key] || "#{ENV['HOME']}/.config/lxc/client.key")
|
60
|
+
end
|
61
|
+
|
62
|
+
def verify_ssl
|
63
|
+
return ssl_opts[:verify] if ssl_opts.key? :verify
|
64
|
+
api_options[:verify_ssl].nil? ? true : api_options[:verify_ssl]
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_response(response)
|
68
|
+
LXD.symbolize_keys(JSON.parse(response.body))
|
69
|
+
end
|
70
|
+
|
71
|
+
def send_request(verb, relative_url, content = nil)
|
72
|
+
response = connection.send(verb) do |req|
|
73
|
+
req.url relative_url
|
74
|
+
if content.is_a? Hash
|
75
|
+
req.headers['Content-Type'] = 'application/json'
|
76
|
+
req.body = content.to_json
|
77
|
+
elsif content # Only upon file upload
|
78
|
+
req.headers['Content-Type'] = 'application/octet-stream'
|
79
|
+
req.headers['X-LXD-uid'] = '0'
|
80
|
+
req.headers['X-LXD-gid'] = '0'
|
81
|
+
req.headers['X-LXD-mode'] = '0600'
|
82
|
+
req.body = content.to_s
|
83
|
+
end
|
84
|
+
end
|
85
|
+
if response.status >= 400
|
86
|
+
err = JSON.parse(response.body)
|
87
|
+
case err['error_code']
|
88
|
+
when 404 then raise RestAPI::Error::NotFound, err['error']
|
89
|
+
when 400 then raise RestAPI::Error::BadRequest, err['error']
|
90
|
+
else raise "Error #{err['error_code']}: #{err['error']}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
block_given? ? yield(response) : parse_response(response)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'nexussw/lxd/rest_api/connection'
|
2
|
+
require 'nexussw/lxd/rest_api/errors'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module NexusSW
|
6
|
+
module LXD
|
7
|
+
class RestAPI
|
8
|
+
def initialize(api_options)
|
9
|
+
@api_options = api_options
|
10
|
+
end
|
11
|
+
|
12
|
+
include RestAPI::Connection
|
13
|
+
|
14
|
+
def create_container(container_name, options)
|
15
|
+
options, sync = parse_options options
|
16
|
+
options[:config] = convert_bools(options[:config]) if options.key? :config
|
17
|
+
handle_async post('/1.0/containers', create_source(options).merge(name: container_name)), sync
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute_command(container_name, command, options)
|
21
|
+
options, sync = parse_options options
|
22
|
+
command = command.shellsplit if command.is_a? String
|
23
|
+
handle_async post("/1.0/containers/#{container_name}/exec", options.merge(command: command)), sync
|
24
|
+
end
|
25
|
+
|
26
|
+
def log(container_name, log_name)
|
27
|
+
get "/1.0/containers/#{container_name}/logs/#{log_name}" do |response|
|
28
|
+
return response.body
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete_log(container_name, log_name)
|
33
|
+
delete "/1.0/containers/#{container_name}/logs/#{log_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def start_container(container_name, options)
|
37
|
+
options, sync = parse_options options
|
38
|
+
handle_async put("/1.0/containers/#{container_name}/state", options.merge(action: 'start')), sync
|
39
|
+
end
|
40
|
+
|
41
|
+
def stop_container(container_name, options)
|
42
|
+
options, sync = parse_options options
|
43
|
+
handle_async put("/1.0/containers/#{container_name}/state", options.merge(action: 'stop')), sync
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete_container(container_name, options = {})
|
47
|
+
handle_async delete("/1.0/containers/#{container_name}"), options[:sync]
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_file(container_name, path)
|
51
|
+
get "/1.0/containers/#{container_name}/files?path=#{path}" do |response|
|
52
|
+
return response.body
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_file(container_name, path, options)
|
57
|
+
post "/1.0/containers/#{container_name}/files?path=#{path}", options[:content]
|
58
|
+
end
|
59
|
+
|
60
|
+
def push_file(local_path, container_name, remote_path)
|
61
|
+
write_file container_name, remote_path, content: IO.binread(local_path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def pull_file(container_name, remote_path, local_path)
|
65
|
+
IO.binwrite(local_path, read_file(container_name, remote_path))
|
66
|
+
end
|
67
|
+
|
68
|
+
def container_state(container_name)
|
69
|
+
get "/1.0/containers/#{container_name}/state"
|
70
|
+
end
|
71
|
+
|
72
|
+
def container(container_name)
|
73
|
+
exceptkeys = %w(config expanded_config)
|
74
|
+
get "/1.0/containers/#{container_name}" do |response|
|
75
|
+
retval = JSON.parse(response.body)
|
76
|
+
lift = retval['metadata'].select { |k, _| exceptkeys.include? k }
|
77
|
+
retval['metadata'].delete_if { |k, _| exceptkeys.include? k }
|
78
|
+
retval = LXD.symbolize_keys(retval)
|
79
|
+
retval[:metadata][:config] = lift['config'] if lift.key? 'config'
|
80
|
+
retval[:metadata][:expanded_config] = lift['expanded_config'] if lift.key? 'expanded_config'
|
81
|
+
return retval
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def containers
|
86
|
+
get('/1.0/containers')
|
87
|
+
end
|
88
|
+
|
89
|
+
def wait_for_operation(operation_id)
|
90
|
+
get "/1.0/operations/#{operation_id}/wait" do |response|
|
91
|
+
LXD.symbolize_keys(JSON.parse(response.body))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
attr_reader :api_options
|
98
|
+
|
99
|
+
def handle_async(data, sync)
|
100
|
+
return data if sync == false
|
101
|
+
wait_for_operation data[:metadata][:id]
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_options(options)
|
105
|
+
sync = options[:sync]
|
106
|
+
[options.delete_if { |k, _| k == :sync }, sync]
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_source(options)
|
110
|
+
moveprops = [:type, :alias, :fingerprint, :properties, :protocol, :server]
|
111
|
+
options.dup.tap do |retval|
|
112
|
+
retval[:source] = { type: 'image', mode: 'pull' }.merge(retval.select { |k, _| moveprops.include? k }) unless retval.key? :source
|
113
|
+
retval.delete_if { |k, _| moveprops.include? k }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def convert_bools(hash)
|
118
|
+
{}.tap do |retval|
|
119
|
+
hash.each do |k, v|
|
120
|
+
retval[k] = case v
|
121
|
+
when true then 'true'
|
122
|
+
when false then 'false'
|
123
|
+
else v.is_a?(Hash) ? convert_bools(v) : v
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -23,7 +23,8 @@ module NexusSW
|
|
23
23
|
mycommand = command.is_a?(Array) ? command.join(' ') : command
|
24
24
|
subcommand = options[:subcommand] || "exec #{container_name} --"
|
25
25
|
mycommand = "lxc #{subcommand} #{mycommand}"
|
26
|
-
options = options.except :subcommand if options.key? :subcommand
|
26
|
+
# options = options.except :subcommand if options.key? :subcommand
|
27
|
+
options = options.reject { |k, _| k == :subcommand }
|
27
28
|
# We would have competing timeout logic depending on who the inner transport is
|
28
29
|
# I'll just let rest & local do the timeouts, and if inner is a chef sourced transport, they have timeout logic of their own
|
29
30
|
# with_timeout_and_retries(options) do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'nexussw/lxd/rest_api/errors'
|
2
|
+
|
1
3
|
module NexusSW
|
2
4
|
module LXD
|
3
5
|
class Transport
|
@@ -26,7 +28,7 @@ module NexusSW
|
|
26
28
|
msg = "Error: '#{command}' failed with exit code #{exitstatus}.\n"
|
27
29
|
msg += "STDOUT: #{stdout}" if stdout.is_a?(String) && !stdout.empty?
|
28
30
|
msg += "STDERR: #{stderr}" if stderr.is_a?(String) && !stderr.empty?
|
29
|
-
raise msg
|
31
|
+
raise ::NexusSW::LXD::RestAPI::Error, msg
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
@@ -47,8 +49,8 @@ module NexusSW
|
|
47
49
|
end
|
48
50
|
|
49
51
|
class InteractiveResult < ExecuteResult
|
50
|
-
def initialize(command, options,
|
51
|
-
super(command, options,
|
52
|
+
def initialize(command, options, stdin, thread = nil)
|
53
|
+
super(command, options, nil)
|
52
54
|
@stdin = stdin
|
53
55
|
@thread = thread
|
54
56
|
end
|
@@ -36,7 +36,8 @@ module NexusSW
|
|
36
36
|
# TODO: serious: make sure the tar extract does an overwrite of existing files
|
37
37
|
# multiple converge support as well as CI cycle/dev updated files get updated instead of .1 suffixed (?)
|
38
38
|
# I think I need a flag (it's been a while)
|
39
|
-
|
39
|
+
# TODO: explore the chmod & make the same as if uploaded via CLI
|
40
|
+
execute("bash -c 'mkdir -p #{path} && cd #{path} && tar -xf #{fname} && rm -rf #{fname} && chmod -R 600 #{File.basename(local_path)}'").error!
|
40
41
|
ensure
|
41
42
|
tfile.unlink
|
42
43
|
end
|
@@ -48,10 +49,10 @@ module NexusSW
|
|
48
49
|
return false if @can_archive == false
|
49
50
|
@can_archive ||= begin
|
50
51
|
# I don't want to code tarball logic into the mock transport
|
51
|
-
return false if respond_to?(:
|
52
|
+
return false if respond_to?(:api) && api.respond_to?(:mock)
|
52
53
|
return false if respond_to?(:inner_transport) && inner_transport.respond_to?(:mock)
|
53
54
|
return false if respond_to?(:inner_transport) && inner_transport.respond_to?(:inner_transport) && inner_transport.inner_transport.respond_to?(:mock)
|
54
|
-
return false if respond_to?(:inner_transport) && inner_transport.respond_to?(:
|
55
|
+
return false if respond_to?(:inner_transport) && inner_transport.respond_to?(:api) && inner_transport.api.respond_to?(:mock)
|
55
56
|
`tar --version`
|
56
57
|
true
|
57
58
|
rescue
|
@@ -21,7 +21,7 @@ module NexusSW
|
|
21
21
|
Open3.popen3(command) do |stdin, stdout, stderr, th|
|
22
22
|
if options[:capture] == :interactive
|
23
23
|
# return immediately if interactive so that stdin may be used
|
24
|
-
return Helpers::ExecuteMixin::InteractiveResult.new(command, options,
|
24
|
+
return Helpers::ExecuteMixin::InteractiveResult.new(command, options, stdin, th).tap do |active|
|
25
25
|
chunk_callback(stdout, stderr) do |stdout_chunk, stderr_chunk|
|
26
26
|
active.send_output stdout_chunk if stdout_chunk
|
27
27
|
active.send_output stderr_chunk if stderr_chunk
|
@@ -2,6 +2,7 @@ require 'nexussw/lxd/transport/mixins/helpers/execute'
|
|
2
2
|
require 'nexussw/lxd/transport/mixins/helpers/upload_folder'
|
3
3
|
require 'nio/websocket'
|
4
4
|
require 'tempfile'
|
5
|
+
require 'json'
|
5
6
|
|
6
7
|
module NexusSW
|
7
8
|
module LXD
|
@@ -13,11 +14,11 @@ module NexusSW
|
|
13
14
|
@config = config
|
14
15
|
@rest_endpoint = config[:rest_endpoint]
|
15
16
|
@driver_options = config[:driver_options]
|
16
|
-
@
|
17
|
-
raise 'The rest transport requires the following keys: { :connection, :driver_options, :rest_endpoint }' unless @rest_endpoint && @
|
17
|
+
@api = config[:connection]
|
18
|
+
raise 'The rest transport requires the following keys: { :connection, :driver_options, :rest_endpoint }' unless @rest_endpoint && @api && @driver_options
|
18
19
|
end
|
19
20
|
|
20
|
-
attr_reader :
|
21
|
+
attr_reader :api, :rest_endpoint, :container_name, :config
|
21
22
|
|
22
23
|
include Helpers::ExecuteMixin
|
23
24
|
include Helpers::UploadFolder
|
@@ -62,43 +63,43 @@ module NexusSW
|
|
62
63
|
backchannel = nil
|
63
64
|
getlogs = false
|
64
65
|
if block_given? && (options[:capture] || !config[:info][:api_extensions].include?('container_exec_recording'))
|
65
|
-
|
66
|
-
|
67
|
-
retval =
|
66
|
+
apiopts = { :'wait-for-websocket' => true, interactive: false, sync: false }
|
67
|
+
apiopts[:interactive] = true if options[:capture] == :interactive
|
68
|
+
retval = api.execute_command(container_name, command, apiopts)[:metadata]
|
68
69
|
opid = retval[:id]
|
69
70
|
backchannel = options[:capture] == :interactive ? ws_connect(opid, retval[:metadata][:fds]) : ws_connect(opid, retval[:metadata][:fds], &block)
|
70
71
|
|
71
72
|
# patch for interactive session
|
72
|
-
return Helpers::ExecuteMixin::InteractiveResult.new(command, options,
|
73
|
+
return Helpers::ExecuteMixin::InteractiveResult.new(command, options, StdinStub.pipe(backchannel.waitlist[:'0']), backchannel).tap do |active|
|
73
74
|
backchannel.callback = proc do |stdout|
|
74
75
|
active.send_output stdout
|
75
76
|
end
|
76
77
|
yield active
|
77
78
|
backchannel.exit if backchannel.respond_to? :exit
|
78
|
-
retval =
|
79
|
+
retval = api.wait_for_operation opid
|
79
80
|
active.exitstatus = retval[:metadata][:return].to_i
|
80
81
|
end if options[:capture] == :interactive
|
81
82
|
elsif block_given? && config[:info][:api_extensions].include?('container_exec_recording')
|
82
83
|
getlogs = true
|
83
|
-
retval =
|
84
|
-
opid = retval[:id]
|
84
|
+
retval = api.execute_command(container_name, command, :'record-output' => true, interactive: false, sync: false)
|
85
|
+
opid = retval[:metadata][:id]
|
85
86
|
else
|
86
|
-
opid =
|
87
|
+
opid = api.execute_command(container_name, command, sync: false)[:metadata][:id]
|
87
88
|
end
|
88
89
|
LXD.with_timeout_and_retries({ timeout: 0 }.merge(options)) do
|
89
90
|
begin
|
90
|
-
retval =
|
91
|
+
retval = api.wait_for_operation(opid)[:metadata]
|
91
92
|
backchannel.join if backchannel.respond_to? :join
|
92
93
|
if getlogs
|
93
94
|
begin
|
94
95
|
stdout_log = retval[:metadata][:output][:'1'].split('/').last
|
95
96
|
stderr_log = retval[:metadata][:output][:'2'].split('/').last
|
96
|
-
stdout =
|
97
|
-
stderr =
|
97
|
+
stdout = api.log container_name, stdout_log
|
98
|
+
stderr = api.log container_name, stderr_log
|
98
99
|
yield stdout, stderr
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
|
101
|
+
api.delete_log container_name, stdout_log
|
102
|
+
api.delete_log container_name, stderr_log
|
102
103
|
end
|
103
104
|
end
|
104
105
|
return Helpers::ExecuteMixin::ExecuteResult.new command, options, retval[:metadata][:return].to_i
|
@@ -108,23 +109,21 @@ module NexusSW
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
|
-
# '' instead of
|
112
|
+
# empty '' instead of an exception is a chef-provisioning expectation - at this level we'll let the exception propagate
|
112
113
|
def read_file(path)
|
113
|
-
|
114
|
-
# rescue ::Hyperkit::NotFound
|
115
|
-
# return ''
|
114
|
+
api.read_file container_name, path
|
116
115
|
end
|
117
116
|
|
118
117
|
def write_file(path, content)
|
119
|
-
|
118
|
+
api.write_file container_name, path, content: content
|
120
119
|
end
|
121
120
|
|
122
121
|
def download_file(path, local_path)
|
123
|
-
|
122
|
+
api.pull_file container_name, path, local_path
|
124
123
|
end
|
125
124
|
|
126
125
|
def upload_file(local_path, path)
|
127
|
-
# return
|
126
|
+
# return api.push_file(local_path, container_name, path)
|
128
127
|
write_file(path, IO.binread(local_path))
|
129
128
|
end
|
130
129
|
|
@@ -182,6 +181,28 @@ module NexusSW
|
|
182
181
|
sleep 0.1
|
183
182
|
end
|
184
183
|
end
|
184
|
+
|
185
|
+
def window_resize(width, height)
|
186
|
+
send_control_msg 'window-resize', width: width.to_s, height: height.to_s
|
187
|
+
end
|
188
|
+
|
189
|
+
def signal(signum)
|
190
|
+
send_control_msg 'signal', signum
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def send_control_msg(message, val)
|
196
|
+
msg = {}.tap do |retval|
|
197
|
+
retval['command'] = message
|
198
|
+
case message
|
199
|
+
when 'window-resize' then retval['args'] = val
|
200
|
+
when 'signal' then retval['signal'] = val.to_i
|
201
|
+
end
|
202
|
+
end.to_json
|
203
|
+
|
204
|
+
waitlist[:control].binary msg
|
205
|
+
end
|
185
206
|
end
|
186
207
|
|
187
208
|
def ws_connect(opid, endpoints, &block)
|
data/lib/nexussw/lxd/version.rb
CHANGED
data/lib/nexussw/lxd.rb
CHANGED
@@ -28,5 +28,16 @@ module NexusSW
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
def self.symbolize_keys(hash)
|
33
|
+
{}.tap do |retval|
|
34
|
+
hash.each do |k, v|
|
35
|
+
v.map! do |a|
|
36
|
+
a.is_a?(Hash) ? symbolize_keys(a) : a
|
37
|
+
end if v.is_a?(Array)
|
38
|
+
retval[k.to_sym] = v.is_a?(Hash) ? symbolize_keys(v) : v
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
31
42
|
end
|
32
43
|
end
|
data/lxd-common.gemspec
CHANGED
@@ -8,16 +8,10 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = NexusSW::LXD::VERSION
|
9
9
|
spec.authors = ['Sean Zachariasen']
|
10
10
|
spec.email = ['thewyzard@hotmail.com']
|
11
|
-
|
11
|
+
spec.license = 'Apache-2.0'
|
12
12
|
spec.summary = 'Shared LXD Container Access Library'
|
13
|
-
# spec.description = %q{TODO: Write a longer description or delete this line.}
|
14
13
|
spec.homepage = 'http://github.com/NexusSW/lxd-common'
|
15
14
|
|
16
|
-
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
-
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
-
|
19
|
-
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'" if spec.respond_to?(:metadata)
|
20
|
-
|
21
15
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
22
16
|
f.match(%r{^(test|spec|features)/})
|
23
17
|
end
|
@@ -25,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
25
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
20
|
spec.require_paths = ['lib']
|
27
21
|
|
28
|
-
spec.add_dependency '
|
22
|
+
spec.add_dependency 'faraday', '~> 0.13'
|
29
23
|
spec.add_dependency 'nio4r-websocket', '~> 0.6'
|
30
24
|
|
31
25
|
spec.add_development_dependency 'bundler'
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lxd-common
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Zachariasen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0.13'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0.13'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: nio4r-websocket
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,6 +118,9 @@ files:
|
|
118
118
|
- lib/nexussw/lxd/driver/mixins/helpers/wait.rb
|
119
119
|
- lib/nexussw/lxd/driver/mixins/rest.rb
|
120
120
|
- lib/nexussw/lxd/driver/rest.rb
|
121
|
+
- lib/nexussw/lxd/rest_api.rb
|
122
|
+
- lib/nexussw/lxd/rest_api/connection.rb
|
123
|
+
- lib/nexussw/lxd/rest_api/errors.rb
|
121
124
|
- lib/nexussw/lxd/transport.rb
|
122
125
|
- lib/nexussw/lxd/transport/cli.rb
|
123
126
|
- lib/nexussw/lxd/transport/local.rb
|
@@ -130,7 +133,8 @@ files:
|
|
130
133
|
- lib/nexussw/lxd/version.rb
|
131
134
|
- lxd-common.gemspec
|
132
135
|
homepage: http://github.com/NexusSW/lxd-common
|
133
|
-
licenses:
|
136
|
+
licenses:
|
137
|
+
- Apache-2.0
|
134
138
|
metadata: {}
|
135
139
|
post_install_message:
|
136
140
|
rdoc_options: []
|