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