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.
@@ -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] == 'nic'
11
+ next unless data[:type] == "nic"
12
12
  state[:network][nic][:addresses].each do |address|
13
- return address[:address] if address[:family] == 'inet' && address[:address] && !address[:address].empty?
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('test -f /run/cloud-init/result.json').error?
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 'unrecognized option'
29
+ raise "unrecognized option"
30
30
  end
31
31
  return retval if retval
32
32
  sleep 0.5
@@ -1,6 +1,6 @@
1
- require 'nexussw/lxd/rest_api'
2
- require 'nexussw/lxd/driver/mixins/helpers/wait'
3
- require 'nexussw/lxd/transport/rest'
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
- @server_info ||= api.get('/1.0')[:metadata]
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 container_name # Start the container for Parity with the CLI
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 container_name
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) == 'running'
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, 'running'
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) == 'stopped'
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) == 'stopped'
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) == 'stopped'
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, 'stopped'
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) == 'running' # Parity with CLI
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('/').last }.include? container_id
113
+ api.containers[:metadata].map { |url| url.split("/").last }.include? container_id
120
114
  end
121
115
 
122
116
  protected
@@ -1,5 +1,5 @@
1
- require 'nexussw/lxd/driver'
2
- require 'nexussw/lxd/driver/mixins/rest'
1
+ require "nexussw/lxd/driver"
2
+ require "nexussw/lxd/driver/mixins/rest"
3
3
 
4
4
  module NexusSW
5
5
  module LXD
@@ -1,6 +1,6 @@
1
- require 'nexussw/lxd/rest_api/connection'
2
- require 'nexussw/lxd/rest_api/errors'
3
- require 'shellwords'
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
- 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
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: 'start')), sync
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: 'stop')), sync
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['Content-Type'] = 'application/octet-stream'
59
- req.headers['X-LXD-uid'] = options[:uid] if options[:uid]
60
- req.headers['X-LXD-gid'] = options[:gid] if options[:gid]
61
- req.headers['X-LXD-mode'] = options[:file_mode] if options[:file_mode]
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
- exceptkeys = %w(config expanded_config)
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('/1.0/containers')
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: 'image', mode: 'pull' }.merge(retval.select { |k, _| moveprops.include? k }) unless retval.key? :source
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 'faraday'
2
- require 'json'
3
- require 'openssl'
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 parse_response(response)
68
- LXD.symbolize_keys(JSON.parse(response.body))
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['Content-Type'] = 'application/json'
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
- if response.status >= 400
85
- err = JSON.parse(response.body)
86
- case err['error_code']
87
- when 404 then raise RestAPI::Error::NotFound, err['error']
88
- when 400 then raise RestAPI::Error::BadRequest, err['error']
89
- else raise RestAPI::Error, "Error #{err['error_code']}: #{err['error']}"
90
- end
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) : parse_response(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 'nexussw/lxd'
2
- require 'tempfile'
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['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'
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,5 +1,5 @@
1
- require 'nexussw/lxd/transport'
2
- require 'nexussw/lxd/transport/mixins/cli'
1
+ require "nexussw/lxd/transport"
2
+ require "nexussw/lxd/transport/mixins/cli"
3
3
 
4
4
  module NexusSW
5
5
  module LXD
@@ -1,5 +1,5 @@
1
- require 'nexussw/lxd/transport'
2
- require 'nexussw/lxd/transport/mixins/local'
1
+ require "nexussw/lxd/transport"
2
+ require "nexussw/lxd/transport/mixins/local"
3
3
 
4
4
  module NexusSW
5
5
  module LXD
@@ -1,8 +1,8 @@
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'
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: 'file pull', capture: false)
41
+ retval = execute("#{@container_name}#{path} #{tfile}", subcommand: "file pull", capture: false)
42
42
  # return '' if retval.exitstatus == 1
43
43
  retval.error!
44
- return inner_transport.read_file tfile
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: 'file pull').error!
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]['api_extensions'] && config[:info]['api_extensions'].include?('directory_manipulation')
80
+ return super unless config[:info] && config[:info]["api_extensions"] && config[:info]["api_extensions"].include?("directory_manipulation")
81
81
 
82
- execute("-r #{local_path} #{container_name}#{path}", subcommand: 'file push', capture: false).error!
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]['api_extensions'] && config[:info]['api_extensions'].include?('directory_manipulation')
94
+ return super unless config[:info] && config[:info]["api_extensions"] && config[:info]["api_extensions"].include?("directory_manipulation")
87
95
 
88
- execute("-r #{container_name}#{path} #{local_path}", subcommand: 'file pull', capture: false).error!
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: 'remote').error! unless remote? host_name
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 'list', subcommand: 'remote'
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