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.
@@ -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