lxd-common 0.9.8 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|