lxd-common 0.6.0 → 0.7.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/.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: []
|