lxd-common 0.9.8 → 0.9.9
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 +5 -5
- data/.gitattributes +3 -0
- data/.gitignore +15 -15
- data/.rubocop.yml +2 -0
- data/.travis.yml +35 -34
- data/Gemfile +1 -1
- data/LICENSE +203 -202
- data/README.md +200 -11
- data/Rakefile +3 -3
- data/Vagrantfile +4 -4
- data/bin/console +6 -6
- data/lib/nexussw/lxd.rb +9 -4
- data/lib/nexussw/lxd/driver.rb +33 -17
- data/lib/nexussw/lxd/driver/cli.rb +2 -2
- data/lib/nexussw/lxd/driver/mixins/cli.rb +99 -47
- data/lib/nexussw/lxd/driver/mixins/helpers/wait.rb +4 -4
- data/lib/nexussw/lxd/driver/mixins/rest.rb +21 -27
- data/lib/nexussw/lxd/driver/rest.rb +2 -2
- data/lib/nexussw/lxd/rest_api.rb +49 -35
- data/lib/nexussw/lxd/rest_api/connection.rb +18 -14
- data/lib/nexussw/lxd/transport.rb +6 -6
- data/lib/nexussw/lxd/transport/cli.rb +2 -2
- data/lib/nexussw/lxd/transport/local.rb +2 -2
- data/lib/nexussw/lxd/transport/mixins/cli.rb +31 -15
- data/lib/nexussw/lxd/transport/mixins/helpers/execute.rb +2 -2
- data/lib/nexussw/lxd/transport/mixins/helpers/folder_txfr.rb +9 -9
- data/lib/nexussw/lxd/transport/mixins/helpers/users.rb +3 -3
- data/lib/nexussw/lxd/transport/mixins/local.rb +22 -18
- data/lib/nexussw/lxd/transport/mixins/rest.rb +51 -43
- data/lib/nexussw/lxd/transport/rest.rb +2 -2
- data/lib/nexussw/lxd/version.rb +1 -1
- data/lxd-common.gemspec +17 -17
- metadata +9 -7
@@ -8,9 +8,9 @@ module NexusSW
|
|
8
8
|
cc = driver.container(container_name)
|
9
9
|
state = driver.container_state(container_name)
|
10
10
|
cc[:expanded_devices].each do |nic, data|
|
11
|
-
next unless data[:type] ==
|
11
|
+
next unless data[:type] == "nic"
|
12
12
|
state[:network][nic][:addresses].each do |address|
|
13
|
-
return address[:address] if address[:family] ==
|
13
|
+
return address[:address] if address[:family] == "inet" && address[:address] && !address[:address].empty?
|
14
14
|
end
|
15
15
|
end
|
16
16
|
nil
|
@@ -22,11 +22,11 @@ module NexusSW
|
|
22
22
|
retval = nil
|
23
23
|
case what
|
24
24
|
when :cloud_init
|
25
|
-
retval = !transport_for(container_name).execute(
|
25
|
+
retval = !transport_for(container_name).execute("test -f /run/cloud-init/result.json").error?
|
26
26
|
when :ip
|
27
27
|
retval = check_for_ip(self, container_name)
|
28
28
|
else
|
29
|
-
raise
|
29
|
+
raise "unrecognized option"
|
30
30
|
end
|
31
31
|
return retval if retval
|
32
32
|
sleep 0.5
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "nexussw/lxd/rest_api"
|
2
|
+
require "nexussw/lxd/driver/mixins/helpers/wait"
|
3
|
+
require "nexussw/lxd/transport/rest"
|
4
4
|
|
5
5
|
module NexusSW
|
6
6
|
module LXD
|
@@ -25,7 +25,7 @@ module NexusSW
|
|
25
25
|
include Helpers::WaitMixin
|
26
26
|
|
27
27
|
def server_info
|
28
|
-
|
28
|
+
api.server_info
|
29
29
|
end
|
30
30
|
|
31
31
|
def transport_for(container_name)
|
@@ -33,35 +33,41 @@ module NexusSW
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def create_container(container_name, container_options = {})
|
36
|
+
autostart = (container_options.delete(:autostart) != false)
|
36
37
|
if container_exists?(container_name)
|
37
|
-
start_container
|
38
|
+
start_container(container_name) if autostart
|
38
39
|
return container_name
|
39
40
|
end
|
40
41
|
# parity note: CLI will run indefinitely rather than timeout hence the 0 timeout
|
41
42
|
retry_forever do
|
42
43
|
api.create_container(container_name, container_options.merge(sync: false))
|
43
44
|
end
|
44
|
-
start_container
|
45
|
+
start_container(container_name) if autostart
|
45
46
|
container_name
|
46
47
|
end
|
47
48
|
|
49
|
+
def update_container(container_name, container_options)
|
50
|
+
api.update_container(container_name, container_options)
|
51
|
+
container(container_name)
|
52
|
+
end
|
53
|
+
|
48
54
|
def start_container(container_id)
|
49
|
-
return if container_status(container_id) ==
|
55
|
+
return if container_status(container_id) == "running"
|
50
56
|
retry_forever do
|
51
57
|
api.start_container(container_id, sync: false)
|
52
58
|
end
|
53
|
-
wait_for_status container_id,
|
59
|
+
wait_for_status container_id, "running"
|
54
60
|
end
|
55
61
|
|
56
62
|
def stop_container(container_id, options = {})
|
57
|
-
return if container_status(container_id) ==
|
63
|
+
return if container_status(container_id) == "stopped"
|
58
64
|
if options[:force]
|
59
65
|
api.stop_container(container_id, force: true)
|
60
66
|
else
|
61
67
|
last_id = nil
|
62
68
|
use_last = false
|
63
69
|
LXD.with_timeout_and_retries({ timeout: 0 }.merge(options)) do # timeout: 0 to enable retry functionality
|
64
|
-
return if container_status(container_id) ==
|
70
|
+
return if container_status(container_id) == "stopped"
|
65
71
|
begin
|
66
72
|
unless use_last
|
67
73
|
# Keep resubmitting until the server complains (Stops will be ignored/hang if init is not yet listening for SIGPWR i.e. recently started)
|
@@ -75,31 +81,19 @@ module NexusSW
|
|
75
81
|
end
|
76
82
|
api.wait_for_operation last_id # , options[:retry_interval]
|
77
83
|
rescue Faraday::TimeoutError => e
|
78
|
-
return if container_status(container_id) ==
|
84
|
+
return if container_status(container_id) == "stopped"
|
79
85
|
raise Timeout::Retry.new e # if options[:retry_interval] # rubocop:disable Style/RaiseArgs
|
80
86
|
end
|
81
87
|
end
|
82
88
|
end
|
83
|
-
wait_for_status container_id,
|
89
|
+
wait_for_status container_id, "stopped"
|
84
90
|
end
|
85
91
|
|
86
92
|
def delete_container(container_id)
|
87
93
|
return unless container_exists? container_id
|
88
94
|
stop_container container_id, force: true
|
89
95
|
|
90
|
-
# ISSUE 17: something upstream is causing a double-tap on the REST endpoint
|
91
|
-
|
92
|
-
# trial return to normal
|
93
|
-
# begin
|
94
96
|
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
|
103
97
|
end
|
104
98
|
|
105
99
|
def container_status(container_id)
|
@@ -107,16 +101,16 @@ module NexusSW
|
|
107
101
|
end
|
108
102
|
|
109
103
|
def container_state(container_id)
|
110
|
-
return nil unless container_status(container_id) ==
|
104
|
+
return nil unless container_status(container_id) == "running" # Parity with CLI
|
111
105
|
api.container_state(container_id)[:metadata]
|
112
106
|
end
|
113
107
|
|
114
108
|
def container(container_id)
|
115
|
-
api.container(container_id)[:metadata]
|
109
|
+
Driver.convert_bools api.container(container_id)[:metadata]
|
116
110
|
end
|
117
111
|
|
118
112
|
def container_exists?(container_id)
|
119
|
-
api.containers[:metadata].map { |url| url.split(
|
113
|
+
api.containers[:metadata].map { |url| url.split("/").last }.include? container_id
|
120
114
|
end
|
121
115
|
|
122
116
|
protected
|
data/lib/nexussw/lxd/rest_api.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "nexussw/lxd/rest_api/connection"
|
2
|
+
require "nexussw/lxd/rest_api/errors"
|
3
|
+
require "shellwords"
|
4
4
|
|
5
5
|
module NexusSW
|
6
6
|
module LXD
|
@@ -11,10 +11,25 @@ module NexusSW
|
|
11
11
|
|
12
12
|
include RestAPI::Connection
|
13
13
|
|
14
|
+
def server_info
|
15
|
+
@server_info ||= LXD.symbolize_keys(get("/1.0"))[:metadata]
|
16
|
+
end
|
17
|
+
|
14
18
|
def create_container(container_name, options)
|
15
19
|
options, sync = parse_options options
|
16
|
-
|
17
|
-
|
20
|
+
handle_async post("/1.0/containers", RestAPI.convert_bools(create_source(options).merge(name: container_name))), sync
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_container(container_name, container_options)
|
24
|
+
if can_patch?
|
25
|
+
patch "/1.0/containers/#{container_name}", RestAPI.convert_bools(container_options)
|
26
|
+
else
|
27
|
+
data = container(container_name)[:metadata].select { |k, _| [:config, :devices, :profiles].include? k }
|
28
|
+
data[:config].merge! container_options[:config] if container_options.key? :config
|
29
|
+
data[:devices].merge! container_options[:devices] if container_options.key? :devices
|
30
|
+
data[:profiles] = container_options[:profiles] if container_options.key? :profiles
|
31
|
+
handle_async put("/1.0/containers/#{container_name}", RestAPI.convert_bools(data)), true
|
32
|
+
end
|
18
33
|
end
|
19
34
|
|
20
35
|
def execute_command(container_name, command, options)
|
@@ -35,12 +50,12 @@ module NexusSW
|
|
35
50
|
|
36
51
|
def start_container(container_name, options)
|
37
52
|
options, sync = parse_options options
|
38
|
-
handle_async put("/1.0/containers/#{container_name}/state", options.merge(action:
|
53
|
+
handle_async put("/1.0/containers/#{container_name}/state", options.merge(action: "start")), sync
|
39
54
|
end
|
40
55
|
|
41
56
|
def stop_container(container_name, options)
|
42
57
|
options, sync = parse_options options
|
43
|
-
handle_async put("/1.0/containers/#{container_name}/state", options.merge(action:
|
58
|
+
handle_async put("/1.0/containers/#{container_name}/state", options.merge(action: "stop")), sync
|
44
59
|
end
|
45
60
|
|
46
61
|
def delete_container(container_name, options = {})
|
@@ -55,10 +70,10 @@ module NexusSW
|
|
55
70
|
|
56
71
|
def write_file(container_name, path, options)
|
57
72
|
post "/1.0/containers/#{container_name}/files?path=#{path}", options[:content] do |req|
|
58
|
-
req.headers[
|
59
|
-
req.headers[
|
60
|
-
req.headers[
|
61
|
-
req.headers[
|
73
|
+
req.headers["Content-Type"] = "application/octet-stream"
|
74
|
+
req.headers["X-LXD-uid"] = options[:uid] if options[:uid]
|
75
|
+
req.headers["X-LXD-gid"] = options[:gid] if options[:gid]
|
76
|
+
req.headers["X-LXD-mode"] = options[:file_mode] if options[:file_mode]
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
@@ -75,30 +90,41 @@ module NexusSW
|
|
75
90
|
end
|
76
91
|
|
77
92
|
def container(container_name)
|
78
|
-
|
79
|
-
get "/1.0/containers/#{container_name}" do |response|
|
80
|
-
retval = JSON.parse(response.body)
|
81
|
-
lift = retval['metadata'].select { |k, _| exceptkeys.include? k }
|
82
|
-
retval['metadata'].delete_if { |k, _| exceptkeys.include? k }
|
83
|
-
retval = LXD.symbolize_keys(retval)
|
84
|
-
retval[:metadata][:config] = lift['config'] if lift.key? 'config'
|
85
|
-
retval[:metadata][:expanded_config] = lift['expanded_config'] if lift.key? 'expanded_config'
|
86
|
-
return retval
|
87
|
-
end
|
93
|
+
get "/1.0/containers/#{container_name}"
|
88
94
|
end
|
89
95
|
|
90
96
|
def containers
|
91
|
-
get(
|
97
|
+
get("/1.0/containers")
|
92
98
|
end
|
93
99
|
|
94
100
|
def wait_for_operation(operation_id)
|
95
101
|
get "/1.0/operations/#{operation_id}/wait"
|
96
102
|
end
|
97
103
|
|
104
|
+
def self.convert_bools(hash)
|
105
|
+
{}.tap do |retval|
|
106
|
+
hash.each do |k, v|
|
107
|
+
if [:ephemeral, :stateful].include? k
|
108
|
+
retval[k] = v
|
109
|
+
else
|
110
|
+
retval[k] = case v
|
111
|
+
when true then "true"
|
112
|
+
when false then "false"
|
113
|
+
else v.is_a?(Hash) && ([:config, :devices].include?(k)) ? convert_bools(v) : v
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
98
120
|
private
|
99
121
|
|
100
122
|
attr_reader :api_options
|
101
123
|
|
124
|
+
def can_patch?
|
125
|
+
server_info[:api_extensions].include? "patch"
|
126
|
+
end
|
127
|
+
|
102
128
|
def handle_async(data, sync)
|
103
129
|
return data if sync == false
|
104
130
|
wait_for_operation data[:metadata][:id]
|
@@ -112,22 +138,10 @@ module NexusSW
|
|
112
138
|
def create_source(options)
|
113
139
|
moveprops = [:type, :alias, :fingerprint, :properties, :protocol, :server]
|
114
140
|
options.dup.tap do |retval|
|
115
|
-
retval[:source] = { type:
|
141
|
+
retval[:source] = { type: "image", mode: "pull" }.merge(retval.select { |k, _| moveprops.include? k }) unless retval.key? :source
|
116
142
|
retval.delete_if { |k, _| moveprops.include? k }
|
117
143
|
end
|
118
144
|
end
|
119
|
-
|
120
|
-
def convert_bools(hash)
|
121
|
-
{}.tap do |retval|
|
122
|
-
hash.each do |k, v|
|
123
|
-
retval[k] = case v
|
124
|
-
when true then 'true'
|
125
|
-
when false then 'false'
|
126
|
-
else v.is_a?(Hash) ? convert_bools(v) : v
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
145
|
end
|
132
146
|
end
|
133
147
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "faraday"
|
2
|
+
require "json"
|
3
|
+
require "openssl"
|
4
4
|
|
5
5
|
module NexusSW
|
6
6
|
module LXD
|
@@ -64,8 +64,12 @@ module NexusSW
|
|
64
64
|
api_options[:verify_ssl].nil? ? true : api_options[:verify_ssl]
|
65
65
|
end
|
66
66
|
|
67
|
-
def
|
68
|
-
|
67
|
+
def do_error(code, message)
|
68
|
+
case code
|
69
|
+
when 404 then raise RestAPI::Error::NotFound, message
|
70
|
+
when 400 then raise RestAPI::Error::BadRequest, message
|
71
|
+
else raise RestAPI::Error, "Error #{code}: #{message}"
|
72
|
+
end
|
69
73
|
end
|
70
74
|
|
71
75
|
def send_request(verb, relative_url, content = nil)
|
@@ -73,7 +77,7 @@ module NexusSW
|
|
73
77
|
response = connection.send(verb) do |req|
|
74
78
|
req.url relative_url
|
75
79
|
if content.is_a? Hash
|
76
|
-
req.headers[
|
80
|
+
req.headers["Content-Type"] = "application/json"
|
77
81
|
req.body = content.to_json
|
78
82
|
elsif content # Only upon file upload at this time
|
79
83
|
yield req if block_given?
|
@@ -81,15 +85,15 @@ module NexusSW
|
|
81
85
|
fileop = true
|
82
86
|
end
|
83
87
|
end
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
begin
|
89
|
+
raw = JSON.parse(response.body)
|
90
|
+
raw = raw[0] if raw.is_a? Array
|
91
|
+
do_error(raw["error_code"].to_i, raw["error"]) if response.status >= 400
|
92
|
+
do_error(raw["metadata"]["status_code"].to_i, raw["metadata"]["err"]) if raw["metadata"].is_a?(Hash) && (raw["metadata"]["class"] == "task") && (raw["metadata"]["status_code"] && raw["metadata"]["status_code"].to_i >= 400)
|
93
|
+
rescue JSON::ParserError
|
94
|
+
do_error response.status, "Malformed JSON Response" if response.status >= 400
|
91
95
|
end
|
92
|
-
block_given? && !fileop ? yield(response) :
|
96
|
+
block_given? && !fileop ? yield(response) : LXD.symbolize_keys(raw)
|
93
97
|
end
|
94
98
|
end
|
95
99
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "nexussw/lxd"
|
2
|
+
require "tempfile"
|
3
3
|
|
4
4
|
module NexusSW
|
5
5
|
module LXD
|
@@ -45,10 +45,10 @@ module NexusSW
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def self.local_tempdir
|
48
|
-
return ENV[
|
49
|
-
return ENV[
|
50
|
-
return ENV[
|
51
|
-
|
48
|
+
return ENV["TEMP"] unless !ENV["TEMP"] || ENV["TEMP"].empty?
|
49
|
+
return ENV["TMP"] unless !ENV["TMP"] || ENV["TMP"].empty?
|
50
|
+
return ENV["TMPDIR"] unless !ENV["TMPDIR"] || ENV["TMPDIR"].empty?
|
51
|
+
"/tmp"
|
52
52
|
end
|
53
53
|
|
54
54
|
def self.chdir_mutex
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "nexussw/lxd/transport/mixins/local"
|
2
|
+
require "nexussw/lxd/transport/mixins/helpers/users"
|
3
|
+
require "nexussw/lxd/transport/mixins/helpers/folder_txfr"
|
4
|
+
require "tempfile"
|
5
|
+
require "shellwords"
|
6
6
|
|
7
7
|
module NexusSW
|
8
8
|
module LXD
|
@@ -38,10 +38,10 @@ module NexusSW
|
|
38
38
|
|
39
39
|
def read_file(path)
|
40
40
|
tfile = Transport.remote_tempname(container_name)
|
41
|
-
retval = execute("#{@container_name}#{path} #{tfile}", subcommand:
|
41
|
+
retval = execute("#{@container_name}#{path} #{tfile}", subcommand: "file pull", capture: false)
|
42
42
|
# return '' if retval.exitstatus == 1
|
43
43
|
retval.error!
|
44
|
-
|
44
|
+
inner_transport.read_file tfile
|
45
45
|
ensure
|
46
46
|
inner_transport.execute("rm -rf #{tfile}", capture: false) if tfile
|
47
47
|
end
|
@@ -59,7 +59,7 @@ module NexusSW
|
|
59
59
|
def download_file(path, local_path)
|
60
60
|
tfile = Transport.remote_tempname(container_name) if punt
|
61
61
|
localname = tfile || local_path
|
62
|
-
execute("#{container_name}#{path} #{localname}", subcommand:
|
62
|
+
execute("#{container_name}#{path} #{localname}", subcommand: "file pull").error!
|
63
63
|
inner_transport.download_file tfile, local_path if tfile
|
64
64
|
ensure
|
65
65
|
inner_transport.execute("rm -rf #{tfile}", capture: false) if tfile
|
@@ -77,19 +77,35 @@ module NexusSW
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def upload_folder(local_path, path)
|
80
|
-
return super unless config[:info] && config[:info][
|
80
|
+
return super unless config[:info] && config[:info]["api_extensions"] && config[:info]["api_extensions"].include?("directory_manipulation")
|
81
81
|
|
82
|
-
|
82
|
+
puntname = local_path
|
83
|
+
if punt
|
84
|
+
tfile = Transport.remote_tempname(container_name)
|
85
|
+
puntname = File.join(tfile, File.basename(local_path))
|
86
|
+
inner_transport.upload_folder(local_path, tfile)
|
87
|
+
end
|
88
|
+
execute("#{puntname} #{container_name}#{path}", subcommand: "file push -r", capture: false).error!
|
89
|
+
ensure
|
90
|
+
inner_transport.execute("rm -rf #{tfile}", capture: false) if tfile
|
83
91
|
end
|
84
92
|
|
85
93
|
def download_folder(path, local_path, options = {})
|
86
|
-
return super unless config[:info] && config[:info][
|
94
|
+
return super unless config[:info] && config[:info]["api_extensions"] && config[:info]["api_extensions"].include?("directory_manipulation")
|
87
95
|
|
88
|
-
|
96
|
+
puntname = local_path
|
97
|
+
if punt
|
98
|
+
tfile = Transport.remote_tempname(container_name)
|
99
|
+
puntname = File.join(tfile, File.basename(path))
|
100
|
+
end
|
101
|
+
execute("#{container_name}#{path} #{tfile || local_path}", subcommand: "file pull -r", capture: false).error!
|
102
|
+
inner_transport.download_folder(puntname, local_path) if punt
|
103
|
+
ensure
|
104
|
+
inner_transport.execute("rm -rf #{tfile}", capture: false) if tfile
|
89
105
|
end
|
90
106
|
|
91
107
|
def add_remote(host_name)
|
92
|
-
execute("add #{host_name} --accept-certificate", subcommand:
|
108
|
+
execute("add #{host_name} --accept-certificate", subcommand: "remote").error! unless remote? host_name
|
93
109
|
end
|
94
110
|
|
95
111
|
def linked_transport(host_name)
|
@@ -100,7 +116,7 @@ module NexusSW
|
|
100
116
|
end
|
101
117
|
|
102
118
|
def remote?(host_name)
|
103
|
-
result = execute
|
119
|
+
result = execute "list", subcommand: "remote"
|
104
120
|
result.error!
|
105
121
|
result.stdout.each_line do |line|
|
106
122
|
return true if line.start_with? "| #{host_name} "
|
@@ -111,7 +127,7 @@ module NexusSW
|
|
111
127
|
private
|
112
128
|
|
113
129
|
def file_perms(options = {})
|
114
|
-
perms =
|
130
|
+
perms = ""
|
115
131
|
perms += " --uid=#{options[:uid] || uid || 0}"
|
116
132
|
perms += " --gid=#{options[:gid] || gid || 0}"
|
117
133
|
fmode = options[:file_mode] || file_mode
|