cloudkeeper 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +61 -0
- data/.travis.yml +21 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +17 -0
- data/README.md +122 -0
- data/Rakefile +17 -0
- data/bin/cloudkeeper +5 -0
- data/cloudkeeper.gemspec +45 -0
- data/config/cloudkeeper.yml +27 -0
- data/lib/cloudkeeper.rb +16 -0
- data/lib/cloudkeeper/backend_connector.rb +123 -0
- data/lib/cloudkeeper/cli.rb +174 -0
- data/lib/cloudkeeper/command_executioner.rb +22 -0
- data/lib/cloudkeeper/entities.rb +11 -0
- data/lib/cloudkeeper/entities/appliance.rb +84 -0
- data/lib/cloudkeeper/entities/conversions.rb +48 -0
- data/lib/cloudkeeper/entities/convertables.rb +8 -0
- data/lib/cloudkeeper/entities/convertables/convertable.rb +63 -0
- data/lib/cloudkeeper/entities/convertables/ova.rb +54 -0
- data/lib/cloudkeeper/entities/image.rb +40 -0
- data/lib/cloudkeeper/entities/image_file.rb +23 -0
- data/lib/cloudkeeper/entities/image_formats.rb +7 -0
- data/lib/cloudkeeper/entities/image_formats/ova.rb +41 -0
- data/lib/cloudkeeper/entities/image_list.rb +84 -0
- data/lib/cloudkeeper/errors.rb +20 -0
- data/lib/cloudkeeper/errors/appliance.rb +7 -0
- data/lib/cloudkeeper/errors/appliance/propagation_error.rb +7 -0
- data/lib/cloudkeeper/errors/argument_error.rb +5 -0
- data/lib/cloudkeeper/errors/backend_error.rb +5 -0
- data/lib/cloudkeeper/errors/command_execution_error.rb +5 -0
- data/lib/cloudkeeper/errors/convertables.rb +7 -0
- data/lib/cloudkeeper/errors/convertables/convertability_error.rb +7 -0
- data/lib/cloudkeeper/errors/image.rb +9 -0
- data/lib/cloudkeeper/errors/image/conversion_error.rb +7 -0
- data/lib/cloudkeeper/errors/image/download_error.rb +7 -0
- data/lib/cloudkeeper/errors/image/format.rb +12 -0
- data/lib/cloudkeeper/errors/image/format/no_format_recognized_error.rb +9 -0
- data/lib/cloudkeeper/errors/image/format/no_required_format_available_error.rb +9 -0
- data/lib/cloudkeeper/errors/image/format/ova.rb +12 -0
- data/lib/cloudkeeper/errors/image/format/ova/invalid_archive_error.rb +11 -0
- data/lib/cloudkeeper/errors/image/format/ova/ova_format_error.rb +11 -0
- data/lib/cloudkeeper/errors/image/format/recognition_error.rb +9 -0
- data/lib/cloudkeeper/errors/image_list.rb +9 -0
- data/lib/cloudkeeper/errors/image_list/download_error.rb +7 -0
- data/lib/cloudkeeper/errors/image_list/retrieval_error.rb +7 -0
- data/lib/cloudkeeper/errors/image_list/verification_error.rb +7 -0
- data/lib/cloudkeeper/errors/invalid_configuration_error.rb +5 -0
- data/lib/cloudkeeper/errors/invalid_url_error.rb +5 -0
- data/lib/cloudkeeper/errors/network_connection_error.rb +5 -0
- data/lib/cloudkeeper/errors/nginx_error.rb +5 -0
- data/lib/cloudkeeper/errors/no_such_file_error.rb +5 -0
- data/lib/cloudkeeper/errors/not_implemented_error.rb +5 -0
- data/lib/cloudkeeper/errors/parsing.rb +10 -0
- data/lib/cloudkeeper/errors/parsing/invalid_appliance_hash_error.rb +7 -0
- data/lib/cloudkeeper/errors/parsing/invalid_image_hash_error.rb +7 -0
- data/lib/cloudkeeper/errors/parsing/invalid_image_list_hash_error.rb +7 -0
- data/lib/cloudkeeper/errors/parsing/parsing_error.rb +7 -0
- data/lib/cloudkeeper/errors/permission_denied_error.rb +5 -0
- data/lib/cloudkeeper/errors/standard_error.rb +5 -0
- data/lib/cloudkeeper/managers.rb +7 -0
- data/lib/cloudkeeper/managers/appliance_manager.rb +152 -0
- data/lib/cloudkeeper/managers/image_list_manager.rb +88 -0
- data/lib/cloudkeeper/managers/image_manager.rb +82 -0
- data/lib/cloudkeeper/nginx.rb +5 -0
- data/lib/cloudkeeper/nginx/http_server.rb +113 -0
- data/lib/cloudkeeper/nginx/templates/nginx.conf.erb +32 -0
- data/lib/cloudkeeper/settings.rb +19 -0
- data/lib/cloudkeeper/utils.rb +7 -0
- data/lib/cloudkeeper/utils/checksum.rb +9 -0
- data/lib/cloudkeeper/utils/hash.rb +9 -0
- data/lib/cloudkeeper/utils/url.rb +11 -0
- data/lib/cloudkeeper/version.rb +3 -0
- data/lib/cloudkeeper_grpc.rb +5 -0
- 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,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
|