cloudkeeper 1.0.0

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