cloudkeeper 1.0.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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +61 -0
  6. data/.travis.yml +21 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +17 -0
  10. data/README.md +122 -0
  11. data/Rakefile +17 -0
  12. data/bin/cloudkeeper +5 -0
  13. data/cloudkeeper.gemspec +45 -0
  14. data/config/cloudkeeper.yml +27 -0
  15. data/lib/cloudkeeper.rb +16 -0
  16. data/lib/cloudkeeper/backend_connector.rb +123 -0
  17. data/lib/cloudkeeper/cli.rb +174 -0
  18. data/lib/cloudkeeper/command_executioner.rb +22 -0
  19. data/lib/cloudkeeper/entities.rb +11 -0
  20. data/lib/cloudkeeper/entities/appliance.rb +84 -0
  21. data/lib/cloudkeeper/entities/conversions.rb +48 -0
  22. data/lib/cloudkeeper/entities/convertables.rb +8 -0
  23. data/lib/cloudkeeper/entities/convertables/convertable.rb +63 -0
  24. data/lib/cloudkeeper/entities/convertables/ova.rb +54 -0
  25. data/lib/cloudkeeper/entities/image.rb +40 -0
  26. data/lib/cloudkeeper/entities/image_file.rb +23 -0
  27. data/lib/cloudkeeper/entities/image_formats.rb +7 -0
  28. data/lib/cloudkeeper/entities/image_formats/ova.rb +41 -0
  29. data/lib/cloudkeeper/entities/image_list.rb +84 -0
  30. data/lib/cloudkeeper/errors.rb +20 -0
  31. data/lib/cloudkeeper/errors/appliance.rb +7 -0
  32. data/lib/cloudkeeper/errors/appliance/propagation_error.rb +7 -0
  33. data/lib/cloudkeeper/errors/argument_error.rb +5 -0
  34. data/lib/cloudkeeper/errors/backend_error.rb +5 -0
  35. data/lib/cloudkeeper/errors/command_execution_error.rb +5 -0
  36. data/lib/cloudkeeper/errors/convertables.rb +7 -0
  37. data/lib/cloudkeeper/errors/convertables/convertability_error.rb +7 -0
  38. data/lib/cloudkeeper/errors/image.rb +9 -0
  39. data/lib/cloudkeeper/errors/image/conversion_error.rb +7 -0
  40. data/lib/cloudkeeper/errors/image/download_error.rb +7 -0
  41. data/lib/cloudkeeper/errors/image/format.rb +12 -0
  42. data/lib/cloudkeeper/errors/image/format/no_format_recognized_error.rb +9 -0
  43. data/lib/cloudkeeper/errors/image/format/no_required_format_available_error.rb +9 -0
  44. data/lib/cloudkeeper/errors/image/format/ova.rb +12 -0
  45. data/lib/cloudkeeper/errors/image/format/ova/invalid_archive_error.rb +11 -0
  46. data/lib/cloudkeeper/errors/image/format/ova/ova_format_error.rb +11 -0
  47. data/lib/cloudkeeper/errors/image/format/recognition_error.rb +9 -0
  48. data/lib/cloudkeeper/errors/image_list.rb +9 -0
  49. data/lib/cloudkeeper/errors/image_list/download_error.rb +7 -0
  50. data/lib/cloudkeeper/errors/image_list/retrieval_error.rb +7 -0
  51. data/lib/cloudkeeper/errors/image_list/verification_error.rb +7 -0
  52. data/lib/cloudkeeper/errors/invalid_configuration_error.rb +5 -0
  53. data/lib/cloudkeeper/errors/invalid_url_error.rb +5 -0
  54. data/lib/cloudkeeper/errors/network_connection_error.rb +5 -0
  55. data/lib/cloudkeeper/errors/nginx_error.rb +5 -0
  56. data/lib/cloudkeeper/errors/no_such_file_error.rb +5 -0
  57. data/lib/cloudkeeper/errors/not_implemented_error.rb +5 -0
  58. data/lib/cloudkeeper/errors/parsing.rb +10 -0
  59. data/lib/cloudkeeper/errors/parsing/invalid_appliance_hash_error.rb +7 -0
  60. data/lib/cloudkeeper/errors/parsing/invalid_image_hash_error.rb +7 -0
  61. data/lib/cloudkeeper/errors/parsing/invalid_image_list_hash_error.rb +7 -0
  62. data/lib/cloudkeeper/errors/parsing/parsing_error.rb +7 -0
  63. data/lib/cloudkeeper/errors/permission_denied_error.rb +5 -0
  64. data/lib/cloudkeeper/errors/standard_error.rb +5 -0
  65. data/lib/cloudkeeper/managers.rb +7 -0
  66. data/lib/cloudkeeper/managers/appliance_manager.rb +152 -0
  67. data/lib/cloudkeeper/managers/image_list_manager.rb +88 -0
  68. data/lib/cloudkeeper/managers/image_manager.rb +82 -0
  69. data/lib/cloudkeeper/nginx.rb +5 -0
  70. data/lib/cloudkeeper/nginx/http_server.rb +113 -0
  71. data/lib/cloudkeeper/nginx/templates/nginx.conf.erb +32 -0
  72. data/lib/cloudkeeper/settings.rb +19 -0
  73. data/lib/cloudkeeper/utils.rb +7 -0
  74. data/lib/cloudkeeper/utils/checksum.rb +9 -0
  75. data/lib/cloudkeeper/utils/hash.rb +9 -0
  76. data/lib/cloudkeeper/utils/url.rb +11 -0
  77. data/lib/cloudkeeper/version.rb +3 -0
  78. data/lib/cloudkeeper_grpc.rb +5 -0
  79. metadata +416 -0
@@ -0,0 +1,174 @@
1
+ require 'thor'
2
+ require 'yell'
3
+
4
+ module Cloudkeeper
5
+ class CLI < Thor
6
+ class_option :'logging-level',
7
+ required: true,
8
+ default: Cloudkeeper::Settings['logging']['level'],
9
+ type: :string,
10
+ enum: Yell::Severities
11
+ class_option :'logging-file',
12
+ default: Cloudkeeper::Settings['logging']['file'],
13
+ type: :string,
14
+ desc: 'File to write logs to'
15
+ class_option :debug,
16
+ default: Cloudkeeper::Settings['debug'],
17
+ type: :boolean,
18
+ desc: 'Runs cloudkeeper in debug mode'
19
+
20
+ method_option :'image-lists',
21
+ required: true,
22
+ default: Cloudkeeper::Settings['image-lists'],
23
+ type: :array,
24
+ desc: 'List of image lists to sync against'
25
+ method_option :'ca-dir',
26
+ required: false,
27
+ default: Cloudkeeper::Settings['ca-dir'],
28
+ type: :string,
29
+ desc: 'CA directory'
30
+ method_option :authentication,
31
+ default: Cloudkeeper::Settings['authentication'],
32
+ type: :boolean,
33
+ desc: 'Client <-> server authentication'
34
+ method_option :certificate,
35
+ required: false,
36
+ default: Cloudkeeper::Settings['certificate'],
37
+ type: :string,
38
+ desc: "Core's host certificate"
39
+ method_option :key,
40
+ required: false,
41
+ default: Cloudkeeper::Settings['key'],
42
+ type: :string,
43
+ desc: "Core's host key"
44
+ method_option :'image-dir',
45
+ required: true,
46
+ default: Cloudkeeper::Settings['image-dir'],
47
+ type: :string,
48
+ desc: 'Directory to store images to'
49
+ method_option :'qemu-img-binary',
50
+ required: true,
51
+ default: Cloudkeeper::Settings['binaries']['qemu-img'],
52
+ type: :string,
53
+ desc: 'Path to qemu-img binary (image conversion)'
54
+ method_option :'nginx-binary',
55
+ default: Cloudkeeper::Settings['binaries']['nginx'],
56
+ type: :string,
57
+ desc: 'Path to nginx binary (HTTP server)'
58
+ method_option :'remote-mode',
59
+ default: Cloudkeeper::Settings['remote-mode'],
60
+ type: :boolean,
61
+ desc: 'Remote mode starts HTTP server (NGINX) and serves images to backend via HTTP'
62
+ method_option :'nginx-error-log-file',
63
+ default: Cloudkeeper::Settings['nginx']['error-log-file'],
64
+ type: :string,
65
+ desc: 'NGINX error log file'
66
+ method_option :'nginx-access-log-file',
67
+ default: Cloudkeeper::Settings['nginx']['access-log-file'],
68
+ type: :string,
69
+ desc: 'NGINX access log file'
70
+ method_option :'nginx-pid-file',
71
+ default: Cloudkeeper::Settings['nginx']['pid-file'],
72
+ type: :string,
73
+ desc: 'NGINX pid file'
74
+ method_option :'nginx-ip-address',
75
+ default: Cloudkeeper::Settings['nginx']['ip-address'],
76
+ type: :string,
77
+ desc: 'IP address NGINX can listen on'
78
+ method_option :'nginx-min-port',
79
+ default: Cloudkeeper::Settings['nginx']['min-port'],
80
+ type: :numeric,
81
+ desc: 'Minimal port NGINX can listen on'
82
+ method_option :'nginx-max-port',
83
+ default: Cloudkeeper::Settings['nginx']['max-port'],
84
+ type: :numeric,
85
+ desc: 'Maximal port NGINX can listen on'
86
+ method_option :'backend-endpoint',
87
+ required: true,
88
+ default: Cloudkeeper::Settings['backend']['endpoint'],
89
+ type: :string,
90
+ desc: "Backend's gRPC endpoint"
91
+ method_option :'backend-certificate',
92
+ required: false,
93
+ default: Cloudkeeper::Settings['backend']['certificate'],
94
+ type: :string,
95
+ desc: "Backend's certificate"
96
+ method_option :formats,
97
+ required: true,
98
+ default: Cloudkeeper::Settings['formats'],
99
+ type: :array,
100
+ desc: 'List of acceptable formats images can be converted to'
101
+
102
+ desc 'sync', 'Runs synchronization process'
103
+ def sync
104
+ initialize_sync options
105
+ Cloudkeeper::Managers::ApplianceManager.new.synchronize_appliances
106
+ rescue Cloudkeeper::Errors::InvalidConfigurationError => ex
107
+ abort ex.message
108
+ end
109
+
110
+ desc 'version', 'Prints cloudkeeper version'
111
+ def version
112
+ $stdout.puts Cloudkeeper::VERSION
113
+ end
114
+
115
+ default_task :sync
116
+
117
+ private
118
+
119
+ def initialize_sync(options)
120
+ initialize_configuration options
121
+ validate_configuration!
122
+ initialize_logger
123
+ logger.debug "Cloudkeeper 'sync' called with parameters: #{Cloudkeeper::Settings.to_hash.inspect}"
124
+ end
125
+
126
+ def initialize_configuration(options)
127
+ Cloudkeeper::Settings.clear
128
+ Cloudkeeper::Settings.merge! options.to_hash
129
+ end
130
+
131
+ def validate_configuration!
132
+ validate_configuration_group! :authentication,
133
+ [:certificate, :key, :'backend-certificate'],
134
+ 'Authentication configuration missing'
135
+ validate_configuration_group! :'remote-mode',
136
+ [:'nginx-binary', :'nginx-error-log-file', :'nginx-access-log-file', :'nginx-pid-file',
137
+ :'nginx-ip-address', :'nginx-min-port', :'nginx-max-port'],
138
+ 'NGINX configuration missing'
139
+ end
140
+
141
+ def validate_configuration_group!(flag, required_options, error_message)
142
+ return unless Cloudkeeper::Settings[flag]
143
+
144
+ raise Cloudkeeper::Errors::InvalidConfigurationError, error_message unless all_options_available(required_options)
145
+ end
146
+
147
+ def all_options_available(required_options)
148
+ required_options.reduce(true) { |acc, elem| Cloudkeeper::Settings[elem] && acc }
149
+ end
150
+
151
+ def initialize_logger
152
+ Cloudkeeper::Settings[:'logging-level'] = 'DEBUG' if Cloudkeeper::Settings[:debug]
153
+
154
+ logging_file = Cloudkeeper::Settings[:'logging-file']
155
+ logging_level = Cloudkeeper::Settings[:'logging-level']
156
+
157
+ Yell.new :stdout, name: Object, level: logging_level.downcase, format: Yell::DefaultFormat
158
+ Object.send :include, Yell::Loggable
159
+
160
+ setup_file_logger(logging_file) if logging_file
161
+
162
+ logger.debug 'Running in debug mode...'
163
+ end
164
+
165
+ def setup_file_logger(logging_file)
166
+ unless (File.exist?(logging_file) && File.writable?(logging_file)) || File.writable?(File.dirname(logging_file))
167
+ logger.error "File #{logging_file} isn't writable"
168
+ return
169
+ end
170
+
171
+ logger.adapter :file, logging_file
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,22 @@
1
+ module Cloudkeeper
2
+ class CommandExecutioner
3
+ class << self
4
+ def execute(*args)
5
+ command = Mixlib::ShellOut.new(*args)
6
+ logger.debug "Executing command: #{command.command.inspect}"
7
+ command.run_command
8
+
9
+ if command.error?
10
+ raise Cloudkeeper::Errors::CommandExecutionError, "Command #{command.command.inspect} terminated with an error: " \
11
+ "#{command.stderr}"
12
+ end
13
+
14
+ command.stdout
15
+ end
16
+
17
+ def list_archive(archive)
18
+ execute('tar', '-t', '-f', archive).lines.map(&:chomp)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module Cloudkeeper
2
+ module Entities
3
+ autoload :ImageFile, 'cloudkeeper/entities/image_file'
4
+ autoload :Image, 'cloudkeeper/entities/image'
5
+ autoload :Appliance, 'cloudkeeper/entities/appliance'
6
+ autoload :ImageList, 'cloudkeeper/entities/image_list'
7
+ autoload :ImageFormats, 'cloudkeeper/entities/image_formats'
8
+ autoload :Convertables, 'cloudkeeper/entities/convertables'
9
+ autoload :Conversions, 'cloudkeeper/entities/conversions'
10
+ end
11
+ end
@@ -0,0 +1,84 @@
1
+ module Cloudkeeper
2
+ module Entities
3
+ class Appliance
4
+ attr_accessor :identifier, :description, :mpuri, :title, :group, :ram, :core, :version, :architecture
5
+ attr_accessor :operating_system, :image, :attributes, :vo, :expiration_date, :image_list_identifier
6
+
7
+ REJECTED_ATTRIBUTES = [:vo, :expiration, :image_list_identifier].freeze
8
+
9
+ def initialize(identifier, mpuri, vo, expiration_date, image_list_identifier, title = '', description = '', group = '',
10
+ ram = 1024, core = 1, version = '', architecture = '', operating_system = '', image = nil, attributes = {})
11
+ if identifier.blank? || \
12
+ mpuri.blank? || \
13
+ vo.blank? || \
14
+ expiration_date.blank? || \
15
+ image_list_identifier.blank?
16
+ raise Cloudkeeper::Errors::ArgumentError, 'identifier, mpuri, vo, expiration_date and image_list_identifier ' \
17
+ 'cannot be nil nor empty'
18
+ end
19
+
20
+ @identifier = identifier
21
+ @description = description
22
+ @mpuri = mpuri
23
+ @title = title
24
+ @group = group
25
+ @ram = ram
26
+ @core = core
27
+ @version = version
28
+ @architecture = architecture
29
+ @operating_system = operating_system
30
+ @image = image
31
+ @attributes = attributes
32
+ @vo = vo
33
+ @expiration_date = expiration_date
34
+ @image_list_identifier = image_list_identifier
35
+ end
36
+
37
+ class << self
38
+ def from_hash(appliance_hash)
39
+ appliance_hash.deep_symbolize_keys!
40
+ appliance = populate_appliance appliance_hash
41
+ appliance.image = Image.from_hash(appliance_hash)
42
+
43
+ appliance
44
+ end
45
+
46
+ def populate_appliance(appliance_hash)
47
+ raise Cloudkeeper::Errors::Parsing::InvalidApplianceHashError, 'invalid appliance hash' if appliance_hash.blank?
48
+
49
+ appliance = Appliance.new appliance_hash[:'dc:identifier'],
50
+ appliance_hash[:'ad:mpuri'],
51
+ appliance_hash[:vo],
52
+ appliance_hash[:expiration],
53
+ appliance_hash[:image_list_identifier],
54
+ appliance_hash[:'dc:title'],
55
+ appliance_hash[:'dc:description'],
56
+ appliance_hash[:'ad:group'],
57
+ appliance_hash[:'ad:ram_recommended'],
58
+ appliance_hash[:'ad:core_recommended'],
59
+ appliance_hash[:'hv:version'],
60
+ appliance_hash[:'sl:arch']
61
+
62
+ construct_os_name!(appliance, appliance_hash)
63
+ populate_attributes!(appliance, appliance_hash)
64
+
65
+ appliance
66
+ rescue Cloudkeeper::Errors::ArgumentError => ex
67
+ raise Cloudkeeper::Errors::Parsing::InvalidApplianceHashError, ex, "appliance hash #{appliance_hash.inspect} " \
68
+ "doesn't contain all the necessary data"
69
+ end
70
+
71
+ def construct_os_name!(appliance, appliance_hash)
72
+ appliance.operating_system = appliance_hash[:'sl:os'].to_s
73
+ appliance.operating_system = "#{appliance.operating_system} #{appliance_hash[:'sl:osname']}".strip
74
+ appliance.operating_system = "#{appliance.operating_system} #{appliance_hash[:'sl:osversion']}".strip
75
+ end
76
+
77
+ def populate_attributes!(appliance, appliance_hash)
78
+ appliance_hash.reject! { |k, _v| REJECTED_ATTRIBUTES.include? k }
79
+ appliance.attributes = appliance_hash.map { |k, v| [k.to_s, v.to_s] }.to_h
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ module Cloudkeeper
2
+ module Entities
3
+ module Conversions
4
+ private
5
+
6
+ def convert_image(image)
7
+ image_file = acceptable_image_file image
8
+
9
+ CloudkeeperGrpc::Image.new mode: :LOCAL, location: image_file.file, format: image_file.format.upcase,
10
+ checksum: image_file.checksum, size: image.size.to_i, uri: image.uri
11
+ end
12
+
13
+ def convert_appliance(appliance, image_proto)
14
+ CloudkeeperGrpc::Appliance.new identifier: appliance.identifier.to_s, description: appliance.description.to_s,
15
+ mpuri: appliance.mpuri.to_s, title: appliance.title.to_s, group: appliance.group.to_s,
16
+ ram: appliance.ram.to_i, core: appliance.core.to_i, version: appliance.version.to_s,
17
+ architecture: appliance.architecture.to_s, operating_system: appliance.operating_system.to_s,
18
+ vo: appliance.vo.to_s, image: image_proto, expiration_date: appliance.expiration_date.to_i,
19
+ image_list_identifier: appliance.image_list_identifier.to_s, attributes: appliance.attributes
20
+ end
21
+
22
+ def convert_image_proto(image_proto)
23
+ return nil unless image_proto
24
+
25
+ Cloudkeeper::Entities::Image.new image_proto.uri, image_proto.checksum, image_proto.size
26
+ end
27
+
28
+ def convert_appliance_proto(appliance_proto, image)
29
+ Cloudkeeper::Entities::Appliance.new appliance_proto.identifier, appliance_proto.mpuri, appliance_proto.vo,
30
+ Time.at(appliance_proto.expiration_date).to_datetime,
31
+ appliance_proto.image_list_identifier, appliance_proto.title,
32
+ appliance_proto.description, appliance_proto.group, appliance_proto.ram,
33
+ appliance_proto.core, appliance_proto.version, appliance_proto.architecture,
34
+ appliance_proto.operating_system, image, appliance_proto.attributes.to_h
35
+ end
36
+
37
+ def acceptable_image_file(image)
38
+ image_format = (image.available_formats & Cloudkeeper::Settings[:formats].map(&:to_sym).sort).first
39
+ unless image_format
40
+ raise Cloudkeeper::Errors::Image::Format::NoRequiredFormatAvailableError, 'image is not available in any of the ' \
41
+ 'required formats'
42
+ end
43
+
44
+ image.image_file image_format
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ module Cloudkeeper
2
+ module Entities
3
+ module Convertables
4
+ autoload :Convertable, 'cloudkeeper/entities/convertables/convertable'
5
+ autoload :Ova, 'cloudkeeper/entities/convertables/ova'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,63 @@
1
+ require 'digest'
2
+
3
+ module Cloudkeeper
4
+ module Entities
5
+ module Convertables
6
+ module Convertable
7
+ CONVERT_OUTPUT_FORMATS = [:raw, :qcow2, :vmdk].freeze
8
+
9
+ def self.convert_output_formats
10
+ CONVERT_OUTPUT_FORMATS
11
+ end
12
+
13
+ FORMAT_REGEX = /^to_(?<format>#{convert_output_formats.join('|')})$/
14
+
15
+ def self.included(base)
16
+ raise Cloudkeeper::Errors::Convertables::ConvertabilityError, "#{base.inspect} cannot become a convertable" \
17
+ unless base.method_defined?(:file) && base.method_defined?(:format)
18
+
19
+ super
20
+ end
21
+
22
+ def method_missing(method, *arguments, &block)
23
+ result = method.to_s.match(FORMAT_REGEX)
24
+ return convert(result[:format]) if result && result[:format]
25
+
26
+ super
27
+ end
28
+
29
+ def respond_to_missing?(method, *)
30
+ method =~ FORMAT_REGEX || super
31
+ end
32
+
33
+ private
34
+
35
+ def convert(output_format)
36
+ logger.debug "Converting file #{file.inspect} from #{format.inspect} to #{output_format.inspect}"
37
+ return self if output_format.to_sym == format.to_sym
38
+
39
+ converted_file = File.join(File.dirname(file), "#{File.basename(file, '.*')}.#{output_format}")
40
+ run_convert_command(output_format, converted_file)
41
+
42
+ image_file converted_file, output_format
43
+ end
44
+
45
+ def run_convert_command(output_format, converted_file)
46
+ Cloudkeeper::CommandExecutioner.execute(Cloudkeeper::Settings[:'qemu-img-binary'],
47
+ 'convert',
48
+ '-f',
49
+ format.to_s,
50
+ '-O',
51
+ output_format.to_s,
52
+ file,
53
+ converted_file)
54
+ end
55
+
56
+ def image_file(converted_file, output_format)
57
+ Cloudkeeper::Entities::ImageFile.new converted_file, output_format.to_sym,
58
+ Cloudkeeper::Utils::Checksum.compute(converted_file), false
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,54 @@
1
+ module Cloudkeeper
2
+ module Entities
3
+ module Convertables
4
+ module Ova
5
+ CONVERT_OUTPUT_FORMATS = [:raw, :qcow2].freeze
6
+
7
+ def self.convert_output_formats
8
+ CONVERT_OUTPUT_FORMATS
9
+ end
10
+
11
+ def self.extended(base)
12
+ raise Cloudkeeper::Errors::Convertables::ConvertabilityError, "#{base.inspect} cannot become OVA convertable" \
13
+ unless base.class.included_modules.include?(Cloudkeeper::Entities::Convertables::Convertable)
14
+
15
+ super
16
+ end
17
+
18
+ def to_vmdk
19
+ image_file(extract_disk, :vmdk)
20
+ end
21
+
22
+ def to_ova
23
+ self
24
+ end
25
+
26
+ private
27
+
28
+ def convert(output_format)
29
+ logger.debug "Converting file #{file.inspect} from #{format.inspect} to #{output_format.inspect}"
30
+ vmdk_image = to_vmdk
31
+ final_image = vmdk_image.send("to_#{output_format}".to_sym)
32
+ File.delete vmdk_image.file
33
+
34
+ final_image
35
+ end
36
+
37
+ def extract_disk
38
+ archived_disk = disk_file
39
+ disk_directory = File.dirname(file)
40
+ Cloudkeeper::CommandExecutioner.execute('tar', '-x', '-f', file, '-C', disk_directory, archived_disk)
41
+ File.join(disk_directory, archived_disk)
42
+ end
43
+
44
+ def archive_files
45
+ Cloudkeeper::CommandExecutioner.list_archive file
46
+ end
47
+
48
+ def disk_file
49
+ archive_files.each { |file| return file if Cloudkeeper::Entities::ImageFormats::Ova::VMDK_REGEX =~ file }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end