itchy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.rspec +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +3 -0
- data/LICENSE +203 -0
- data/README.md +16 -0
- data/Rakefile +25 -0
- data/bin/itchy +142 -0
- data/config/itchy.yml +32 -0
- data/itchy.gemspec +41 -0
- data/lib/itchy/errors/abstract_env_error.rb +4 -0
- data/lib/itchy/errors/env_lookup_error.rb +4 -0
- data/lib/itchy/errors/event_processing_error.rb +4 -0
- data/lib/itchy/errors/file_inspecting_error.rb +4 -0
- data/lib/itchy/errors/format_converting_error.rb +4 -0
- data/lib/itchy/errors/image_transforming_error.rb +4 -0
- data/lib/itchy/errors/not_found_error.rb +4 -0
- data/lib/itchy/errors/not_implemented_error.rb +4 -0
- data/lib/itchy/errors/unknown_event_error.rb +4 -0
- data/lib/itchy/errors/working_with_files_error.rb +4 -0
- data/lib/itchy/errors/wrong_state_error.rb +4 -0
- data/lib/itchy/errors.rb +5 -0
- data/lib/itchy/event_handlers/available_postfix_event_handler.rb +43 -0
- data/lib/itchy/event_handlers/available_prefix_event_handler.rb +10 -0
- data/lib/itchy/event_handlers/base_event_handler.rb +82 -0
- data/lib/itchy/event_handlers/expire_postfix_event_handler.rb +30 -0
- data/lib/itchy/event_handlers/expire_prefix_event_handler.rb +9 -0
- data/lib/itchy/event_handlers/process_postfix_event_handler.rb +9 -0
- data/lib/itchy/event_handlers/process_prefix_event_handler.rb +10 -0
- data/lib/itchy/event_handlers/subscription_image_new_event_handler.rb +9 -0
- data/lib/itchy/event_handlers.rb +8 -0
- data/lib/itchy/event_processer.rb +103 -0
- data/lib/itchy/format_converter.rb +43 -0
- data/lib/itchy/helpers/vmcatcher_env_helper.rb +44 -0
- data/lib/itchy/helpers.rb +5 -0
- data/lib/itchy/image_transformer.rb +220 -0
- data/lib/itchy/log.rb +78 -0
- data/lib/itchy/metadata_archiver.rb +59 -0
- data/lib/itchy/settings.rb +35 -0
- data/lib/itchy/version.rb +3 -0
- data/lib/itchy/vmcatcher_configuration.rb +62 -0
- data/lib/itchy/vmcatcher_env.rb +58 -0
- data/lib/itchy/vmcatcher_event.rb +71 -0
- data/lib/itchy.rb +26 -0
- data/spec/itchy/event_handlers/available_postfix_event_handler_spec.rb +0 -0
- data/spec/itchy/event_handlers/expire_postfix_event_handler_spec.rb +0 -0
- data/spec/itchy/event_processer_spec.rb +0 -0
- data/spec/itchy/format_converter_spec.rb +0 -0
- data/spec/itchy/image_transformer_spec.rb +0 -0
- data/spec/itchy/metadata_archiver_spec.rb +0 -0
- data/spec/itchy/vmcatcher_configuration_spec.rb +0 -0
- data/spec/itchy/vmcatcher_env_spec.rb +1 -0
- data/spec/itchy/vmcatcher_event_spec.rb +0 -0
- data/spec/spec_helper.rb +11 -0
- metadata +379 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
module Itchy::EventHandlers
|
2
|
+
# Basic handler implementing required methods. Can be used
|
3
|
+
# as a dummy for testing purposes.
|
4
|
+
class BaseEventHandler
|
5
|
+
TEMPFILE_BASE = 'vmcatcher_event_metadata_archive'
|
6
|
+
EVENT_FILE_REGEXP = /^(?<time>\d+)_(?<type>[[:alnum:]]+)_(?<dc_identifier>[[[:alnum:]]-]+)\.json$/
|
7
|
+
|
8
|
+
attr_reader :vmcatcher_configuration, :options
|
9
|
+
|
10
|
+
# Event handler constructor.
|
11
|
+
#
|
12
|
+
# @param vmcatcher_configuration [Itchy::VmcatcherConfiguration] current vmcatcher configuration
|
13
|
+
# @param options [Settingslogic] current itchy configuration
|
14
|
+
def initialize(vmcatcher_configuration, options)
|
15
|
+
unless vmcatcher_configuration.is_a?(Itchy::VmcatcherConfiguration)
|
16
|
+
fail ArgumentError, '\'vmcatcher_configuration\' must be an instance of ' \
|
17
|
+
'Itchy::VmcatcherConfiguration!'
|
18
|
+
end
|
19
|
+
|
20
|
+
@vmcatcher_configuration = vmcatcher_configuration
|
21
|
+
@options = options || ::Hashie::Mash.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Triggers an archiving procedure on the registered event.
|
25
|
+
#
|
26
|
+
# @param vmcatcher_event [Itchy::VmcatcherEvent] event being archived
|
27
|
+
def archive!(vmcatcher_event)
|
28
|
+
unless vmcatcher_event.is_a?(Itchy::VmcatcherEvent)
|
29
|
+
fail ArgumentError, '\'vmcatcher_event\' must be an instance of ' \
|
30
|
+
'Itchy::VmcatcherEvent!'
|
31
|
+
end
|
32
|
+
|
33
|
+
Itchy::Log.info "[#{self.class.name}] Saving " \
|
34
|
+
"#{vmcatcher_event.type.inspect} " \
|
35
|
+
"for #{vmcatcher_event.dc_identifier.inspect}"
|
36
|
+
|
37
|
+
temp_file = ::Tempfile.new(TEMPFILE_BASE)
|
38
|
+
permanent_file_path = ::File.join(
|
39
|
+
options.metadata_dir,
|
40
|
+
"#{::Time.now.to_i}_#{vmcatcher_event.type || 'Unknown'}_#{vmcatcher_event.dc_identifier || 'NoID'}.json"
|
41
|
+
)
|
42
|
+
|
43
|
+
temp_file.write(vmcatcher_event.to_pretty_json)
|
44
|
+
temp_file.flush
|
45
|
+
|
46
|
+
::FileUtils.cp(temp_file.path, permanent_file_path)
|
47
|
+
temp_file.close
|
48
|
+
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Handling procedure
|
53
|
+
def handle!(vmcatcher_event, _event_file)
|
54
|
+
unless vmcatcher_event.is_a?(Itchy::VmcatcherEvent)
|
55
|
+
fail ArgumentError, '\'vmcatcher_event\' must be an instance of ' \
|
56
|
+
'Itchy::VmcatcherEvent!'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Save created descriptor to descriptor directory. Every descriptor
|
61
|
+
# is stored in its own directory.
|
62
|
+
#
|
63
|
+
# @param descriptor [String] json form of appliance descriptor
|
64
|
+
# @param name [String] name of appliance descriptor (event name)
|
65
|
+
def save_descriptor(descriptor, name)
|
66
|
+
name.slice! @options.metadata_dir
|
67
|
+
dir_name = name
|
68
|
+
dir_name.slice! '.json'
|
69
|
+
::FileUtils.mkdir_p "#{@options.descriptor_dir}/#{dir_name}"
|
70
|
+
File.open("#{@options.descriptor_dir}#{dir_name}/#{name}.json", 'w') { |f| f.write(descriptor) }
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
# Creates an image transformer instance with options.
|
76
|
+
#
|
77
|
+
# @return [ImageTransformer] image transformer instance
|
78
|
+
def image_transformer_instance
|
79
|
+
@image_transformer_instance_cache ||= Itchy::ImageTransformer.new(options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Itchy::EventHandlers
|
2
|
+
# Handler for ExpirePostfix event (image expired).
|
3
|
+
class ExpirePostfixEventHandler < BaseEventHandler
|
4
|
+
# Handles an ExpirePostfix event.
|
5
|
+
#
|
6
|
+
# @param vmcatcher_event [Itchy::VmcatcherEvent] vmcatcher event to handle
|
7
|
+
# @param event_name [String] name of the event
|
8
|
+
def handle!(vmcatcher_event, event_name)
|
9
|
+
super
|
10
|
+
Itchy::Log.info "[#{self.class.name}] Handling expired image " \
|
11
|
+
"for #{vmcatcher_event.dc_identifier.inspect}"
|
12
|
+
save_descriptor(create_descriptor(vmcatcher_event), event_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Create appliance descriptor from VMCATCHER_EVENT metadata.
|
18
|
+
#
|
19
|
+
# @param metadata [Itchy::VmcatcherEvent] vmcatcher event to get metadata from
|
20
|
+
# @return [String] json form of created description
|
21
|
+
def create_descriptor(metadata)
|
22
|
+
Itchy::Log.debug "[#{self.class.name}] Creating appliance descriptor"
|
23
|
+
appliance = ::Cloud::Appliance::Descriptor::Appliance.new action: :expiration
|
24
|
+
appliance.version = metadata.hv_version
|
25
|
+
appliance.identifier = metadata.dc_identifier
|
26
|
+
|
27
|
+
descriptor = appliance.to_json
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Itchy::EventHandlers
|
2
|
+
# Handler for ExpirePrefix event (image will be expired).
|
3
|
+
class ExpirePrefixEventHandler < BaseEventHandler
|
4
|
+
def handle!(vmcatcher_event, event_file)
|
5
|
+
super
|
6
|
+
Itchy::Log.warn "[#{self.class.name}] Just ignoring #{vmcatcher_event.type.inspect}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Itchy::EventHandlers
|
2
|
+
# Handler for ProcessPostfix event (finished cache update).
|
3
|
+
class ProcessPostfixEventHandler < BaseEventHandler
|
4
|
+
def handle!(vmcatcher_event, event_file)
|
5
|
+
super
|
6
|
+
Itchy::Log.warn "[#{self.class.name}] Just ignoring #{vmcatcher_event.type.inspect}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Itchy::EventHandlers
|
2
|
+
# Handler for ProcessPrefix event (starting cache update).
|
3
|
+
class ProcessPrefixEventHandler < BaseEventHandler
|
4
|
+
def handle!(vmcatcher_event, file)
|
5
|
+
super
|
6
|
+
Itchy::Log.info "[#{self.class.name}] Handling #{vmcatcher_event.type.inspect}" \
|
7
|
+
'This kind of event is just logged, nothing to process.'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Itchy::EventHandlers
|
2
|
+
# Handler for SubscriptionImageNew event (new image added to image list).
|
3
|
+
class SubscriptionImageNewEventHandler < BaseEventHandler
|
4
|
+
def handle!(vmcatcher_event, event_file)
|
5
|
+
super
|
6
|
+
Itchy::Log.warn "[#{self.class.name}] Just ignoring #{vmcatcher_event.type.inspect}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Wrapper for all available vmcatcher event handlers.
|
2
|
+
module Itchy::EventHandlers; end
|
3
|
+
|
4
|
+
# Load base handler first
|
5
|
+
require File.join(File.dirname(__FILE__), 'event_handlers', 'base_event_handler')
|
6
|
+
|
7
|
+
# Load all available event handlers
|
8
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'event_handlers', '*.rb')) { |handler_file| require handler_file.chomp('.rb') }
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Itchy
|
2
|
+
# Process stored evenets.
|
3
|
+
class EventProcesser
|
4
|
+
attr_reader :vmc_configuration, :options
|
5
|
+
|
6
|
+
# Creates and processer instance for stored vmcatcher events.
|
7
|
+
#
|
8
|
+
# @param vmc_configuration [Itchy::Vmcatcher::Configuration] vmcatcher configuration
|
9
|
+
# @param options [Hashie::Mash] hask-like structure with options
|
10
|
+
def initialize(vmc_configuration, options)
|
11
|
+
fail ArgumentError, '"vmc_configuration" must be an instance ' \
|
12
|
+
'of Itchy::VmcatcherConfiguration' unless vmc_configuration.is_a? Itchy::VmcatcherConfiguration
|
13
|
+
|
14
|
+
@vmc_configuration = vmc_configuration
|
15
|
+
@options = options
|
16
|
+
end
|
17
|
+
|
18
|
+
# Processing method for events. For each event calls respective
|
19
|
+
# event handler. And after processing of each event, stored event
|
20
|
+
# file is deleted.
|
21
|
+
def process!
|
22
|
+
Itchy::Log.info "[#{self.class.name}] Processing eventes stored in #{options.metadata_dir}"
|
23
|
+
|
24
|
+
archived_events do |event, event_file|
|
25
|
+
begin
|
26
|
+
|
27
|
+
begin
|
28
|
+
event_handler = Itchy::EventHandlers.const_get("#{event.type}EventHandler")
|
29
|
+
event_handler = event_handler.new(vmc_configuration, options)
|
30
|
+
event_handler.handle!(event, event_file)
|
31
|
+
rescue => ex
|
32
|
+
Itchy::Log.error "[#{self.class.name}] Due to error #{ex.message} event #{event_file}" \
|
33
|
+
"was not processed!!! Continuing with next stored event."
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
clean_event!(event, event_file)
|
39
|
+
rescue => ex
|
40
|
+
Itchy::Log.error "[#{self.class.name}] Event #{event_file} was processed, but not cleaned!!!"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Prepares archived event for processing. That means inspect directory with
|
48
|
+
# stored event files, and create VmcatcherEvent from each file.
|
49
|
+
def archived_events(&block)
|
50
|
+
arch_events = ::Dir.glob(::File.join(options.metadata_dir, '*.json'))
|
51
|
+
arch_events.sort!
|
52
|
+
Itchy::Log.debug "[#{self.class.name}] Foud events: #{arch_events.inspect}"
|
53
|
+
arch_events.each do |json|
|
54
|
+
json_short = json.split(::File::SEPARATOR).last
|
55
|
+
|
56
|
+
unless Itchy::EventHandlers::BaseEventHandler::EVENT_FILE_REGEXP =~ json_short
|
57
|
+
Itchy::Log.error "[#{self.class.name}] #{json.inspect} doesn't match the required format"
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
vmc_event_from_json = read_event(json)
|
62
|
+
block.call(vmc_event_from_json, json) if vmc_event_from_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Creates VmcatcherEvent instance.
|
67
|
+
#
|
68
|
+
# @param json json [String] path to file containing json representation
|
69
|
+
# of vmcatcher event
|
70
|
+
#
|
71
|
+
# @return [VmcatcherEvent] instance representing event
|
72
|
+
def read_event(json)
|
73
|
+
Itchy::VmcatcherEvent.new(::File.read(json))
|
74
|
+
rescue => ex
|
75
|
+
Itchy::Log.error '[]Failed to load event!!!'
|
76
|
+
return ex
|
77
|
+
end
|
78
|
+
|
79
|
+
# Deletes event file.
|
80
|
+
#
|
81
|
+
# @param event [VmcatcherEvent] event for cleaning
|
82
|
+
# @param event_file [String] path to file containing event info
|
83
|
+
def clean_event!(_event, event_file)
|
84
|
+
Itchy::Log.info "[#{self.class.name}] Cleaning up"
|
85
|
+
|
86
|
+
begin
|
87
|
+
::FileUtils.rm_f event_file
|
88
|
+
rescue => ex
|
89
|
+
Itchy::Log.fatal 'Failed to clean up event!!!'
|
90
|
+
return ex
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Checks if description directory exists and its readable.
|
95
|
+
def check_descriptor_dir
|
96
|
+
Itchy::Log.debug "[#{self.class.name}] Checking root descriptor dir #{options.descriptor_dir.inspect}"
|
97
|
+
fail ArgumentError, 'Root descriptor directory' \
|
98
|
+
'is not a directory!' unless File.directory? options.descriptor_dir
|
99
|
+
fail ArgumentError, 'Root descriptor directory' \
|
100
|
+
'is not readable!' unless File.readable? opitons.descriptor_dir
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Itchy
|
2
|
+
# Converting different image formats
|
3
|
+
class FormatConverter
|
4
|
+
# Creates and converter instance for converting image to requried format
|
5
|
+
#
|
6
|
+
# @param unpacking_dir [String] path to directory where image is stored
|
7
|
+
# @param metadata [VmcatcherEvent] metadata of event corresponding to image
|
8
|
+
# @param vmcatcher_configuration [VmcatcherConfiguration] vmcatcher configuration
|
9
|
+
def initialize(unpacking_dir, metadata, vmcatcher_configuration)
|
10
|
+
unless vmcatcher_configuration.is_a?(Itchy::VmcatcherConfiguration)
|
11
|
+
fail ArgumentError, '\'vmcatcher_configuration\' must be an instance of ' \
|
12
|
+
'Itchy::VmcatcherConfiguration!'
|
13
|
+
end
|
14
|
+
|
15
|
+
@unpacking_dir = unpacking_dir
|
16
|
+
@metadata = metadata
|
17
|
+
@vmcatcher_configuration = vmcatcher_configuration
|
18
|
+
end
|
19
|
+
|
20
|
+
# Converts image to required format. It uses Mixlib::ShelOut.
|
21
|
+
#
|
22
|
+
# @param file_format [String] actual format of the image
|
23
|
+
# @param required_format [String] required format
|
24
|
+
# @param output_dir [String] path to a directory where converted image should be stored
|
25
|
+
def convert!(file_format, required_format, output_dir)
|
26
|
+
Itchy::Log.info "[#{self.class.name}] Converting image " \
|
27
|
+
"#{@metadata.dc_identifier.inspect} from " \
|
28
|
+
"original format: #{file_format} to " \
|
29
|
+
"required format: #{required_format}."
|
30
|
+
|
31
|
+
convert_cmd = Mixlib::ShellOut.new("qemu-img convert -f #{file_format} -O #{required_format} #{@unpacking_dir}/#{@metadata.dc_identifier} #{output_dir}/#{@metadata.dc_identifier}")
|
32
|
+
convert_cmd.run_command
|
33
|
+
begin
|
34
|
+
convert_cmd.error!
|
35
|
+
rescue Mixlib::ShellOut::ShellCommandFailed, Mixlib::ShellOut::CommandTimeout,
|
36
|
+
Mixlib::Shellout::InvalidCommandOption => ex
|
37
|
+
Itchy::Log.fatal "[#{self.class.name}] Converting of image failed with " \
|
38
|
+
"error messages #{convert_cmd.stderr}."
|
39
|
+
fail Itchy::Errors::FormatConvertingError, ex
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Itchy::Helpers
|
2
|
+
# Wraps static helper methods for accessing environment variables
|
3
|
+
# provided by vmcatcher.
|
4
|
+
module VmcatcherEnvHelper
|
5
|
+
# Selects only relevant +env+ entries and throws the rest away.
|
6
|
+
# Throws Itchy::Errors::EnvLookupError if the result is
|
7
|
+
# empty.
|
8
|
+
#
|
9
|
+
# @param env [Object] environment object convertible to a hash
|
10
|
+
# @param relevant_keys [Array] an array of string keys to look for in +env+
|
11
|
+
# @return [Hash] cleaned up hash containing only relevant entries
|
12
|
+
def self.select_from_env(env, relevant_keys)
|
13
|
+
Itchy::Log.debug "[#{name}] Looking for " \
|
14
|
+
"#{relevant_keys.inspect} in #{env.inspect}"
|
15
|
+
env = env.select { |key, _| relevant_keys.include? key }
|
16
|
+
|
17
|
+
if env.blank?
|
18
|
+
Itchy::Log.fatal "[#{name}] No relevant information " \
|
19
|
+
"found in 'env'"
|
20
|
+
fail(
|
21
|
+
Itchy::Errors::EnvLookupError,
|
22
|
+
'Environment look-up failed! Result is empty!'
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
env
|
27
|
+
end
|
28
|
+
|
29
|
+
# Converts +env+ object to a hash.
|
30
|
+
#
|
31
|
+
# @param env [Object] environment object convertible to a hash
|
32
|
+
# @return [Hash] converted environment object
|
33
|
+
def self.normalize_env(env)
|
34
|
+
return env if env.is_a? Hash
|
35
|
+
|
36
|
+
fail(
|
37
|
+
Itchy::Errors::EnvLookupError,
|
38
|
+
"'env' must be convertible to a hash!"
|
39
|
+
) unless env.respond_to?(:to_hash)
|
40
|
+
|
41
|
+
env.to_hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
module Itchy
|
2
|
+
# Wraps image format conversion methods and helpers.
|
3
|
+
class ImageTransformer
|
4
|
+
# Registered image formats and archives
|
5
|
+
KNOWN_IMAGE_ARCHIVES = %w(ova tar).freeze
|
6
|
+
KNOWN_IMAGE_FORMATS = %w(cow dmg parallels qcow qcow2 raw vdi vmdk vhd).freeze
|
7
|
+
|
8
|
+
# Archive format string msg
|
9
|
+
ARCHIVE_STRING = 'POSIX tar archive'
|
10
|
+
|
11
|
+
# REGEX pattern for getting image format
|
12
|
+
FORMAT_PATTERN = /format:\s(.*?)$/
|
13
|
+
|
14
|
+
# Creates a class instance.
|
15
|
+
#
|
16
|
+
# @param options [Hash] configuration options
|
17
|
+
def initialize(options = {})
|
18
|
+
@options = options
|
19
|
+
@inputs = ([] << KNOWN_IMAGE_FORMATS << KNOWN_IMAGE_ARCHIVES).flatten
|
20
|
+
|
21
|
+
fail ArgumentError, 'Unsupported input image format enabled in configuration! ' \
|
22
|
+
"#{@inputs.inspect}" unless (@options.input_image_formats - @inputs).empty?
|
23
|
+
# fail "Unsupported output image format enabled in configuration! " \
|
24
|
+
# "#{KNOWN_IMAGE_FORMATS.inspect}" unless (@options.required_format - KNOWN_IMAGE_FORMATS).empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Transforms image(s) associated with the given event to formats
|
28
|
+
# preferred by the underlying image datastore. This process includes
|
29
|
+
# unpacking of archive & conversion of image files.
|
30
|
+
#
|
31
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
32
|
+
# @param vmcatcher_configuration [Itchy::VmcatcherConfiguration] current VMC configuration
|
33
|
+
# @return [String] directory with converted images for further processing
|
34
|
+
def transform!(metadata, vmcatcher_configuration)
|
35
|
+
Itchy::Log.info "[#{self.class.name}] Transforming image format " \
|
36
|
+
"for #{metadata.dc_identifier.inspect}"
|
37
|
+
begin
|
38
|
+
if archived?(metadata.dc_identifier.inspect)
|
39
|
+
unpacking_dir = unpack_archived!(metadata, vmcatcher_configuration)
|
40
|
+
file_format = inspect_unpacked_dir(unpacking_dir, metadata)
|
41
|
+
else
|
42
|
+
file_format = format(orig_image_file(metadata, vmcatcher_configuration))
|
43
|
+
unpacking_dir = copy_unpacked!(metadata, vmcatcher_configuration)
|
44
|
+
end
|
45
|
+
if file_format == @options.required_format
|
46
|
+
copy_same_format(unpacking_dir, metadata)
|
47
|
+
else
|
48
|
+
converter = Itchy::FormatConverter.new(unpacking_dir, metadata, vmcatcher_configuration)
|
49
|
+
converter.convert!(file_format, @options.required_format, @options.output_dir)
|
50
|
+
end
|
51
|
+
rescue Itchy::Errors::FileInspectingError, Itchy::Errors::FormatConvertingError,
|
52
|
+
Itchy::Errors::WorkingWithFilesError => ex
|
53
|
+
fail Itchy::Errors::ImageTransformingError, ex
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Checks the given format against a list of available
|
60
|
+
# image formats.
|
61
|
+
#
|
62
|
+
# @param unpacking_dir [String] name and path of the checked file
|
63
|
+
# @return [String] image format
|
64
|
+
def format(file)
|
65
|
+
image_format_tester = Mixlib::ShellOut.new("qemu-img info #{file}")
|
66
|
+
image_format_tester.run_command
|
67
|
+
begin
|
68
|
+
image_format_tester.error!
|
69
|
+
rescue => ex
|
70
|
+
Itchy::Log.error "[#{self.class.name}] Checking file format for" \
|
71
|
+
"#{file} failed!"
|
72
|
+
fail Itchy::Errors::FileInspectingError, ex
|
73
|
+
end
|
74
|
+
file_format = image_format_tester.stdout.scan(FORMAT_PATTERN)[0].flatten.first
|
75
|
+
unless KNOWN_IMAGE_FORMATS.include? file_format
|
76
|
+
Itchy::Log.error "Image format #{file_format} is unknown and not supported!"
|
77
|
+
fail Itchy::Errors::FileInspectingError
|
78
|
+
end
|
79
|
+
file_format
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
#
|
84
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
85
|
+
# @param vmcatcher_configuration [Itchy::VmcatcherConfiguration] current VMC configuration
|
86
|
+
# @return [String] output directory with unpacked files
|
87
|
+
def unpack_archived!(metadata, vmcatcher_configuration)
|
88
|
+
Itchy::Log.info "[#{self.class.name}] Unpacking image from archive " \
|
89
|
+
"for #{metadata.dc_identifier.inspect}"
|
90
|
+
|
91
|
+
unpacking_dir = prepare_image_temp_dir(metadata, vmcatcher_configuration)
|
92
|
+
tar_cmd = ::Mixlib::ShellOut.new('/bin/tar',
|
93
|
+
"--restrict -C #{unpacking_dir} " \
|
94
|
+
"-xvf #{orig_image_file(metadata, vmcatcher_configuration)}",
|
95
|
+
env: nil, cwd: '/tmp')
|
96
|
+
tar_cmd.run_command
|
97
|
+
begin
|
98
|
+
tar_cmd.error!
|
99
|
+
rescue => ex
|
100
|
+
Itchy::Log.error "Unpacking of archive failed with #{tar_cmd.stderr}"
|
101
|
+
fail Itchy::Errors::WorkingWithFilesError, ex
|
102
|
+
end
|
103
|
+
|
104
|
+
unpacking_dir
|
105
|
+
end
|
106
|
+
|
107
|
+
# Inspect unppacked .ova or .tar files. So far, it checks if there is only one
|
108
|
+
# image file of known format and if it is, it's renamed to dc_identifier end its
|
109
|
+
# format is determined. Otherwise (for now) its unsupported situation.
|
110
|
+
#
|
111
|
+
# @param directory [String] name of directory where '.ova' or '.tar' is unpacked
|
112
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
113
|
+
# @return [String] format of image or nil.
|
114
|
+
def inspect_unpacked_dir(directory, metadata, _identifier)
|
115
|
+
dir = Dir.new directory
|
116
|
+
counter = 0
|
117
|
+
files = dir['*']
|
118
|
+
files each do |file|
|
119
|
+
file_format = format("#{directory}/#{file}")
|
120
|
+
if KNOWN_IMAGE_FORMATS.include? file_format
|
121
|
+
counter += 1
|
122
|
+
# unsupported ova content (more than one disk)
|
123
|
+
return nil if counter > 1
|
124
|
+
File.new("#{directory}/#{file}", 'r').rename(file, "#{metadata.dc_identifier}.#{file_format}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
return nil if counter == 0
|
128
|
+
|
129
|
+
file_format
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
#
|
134
|
+
# @param directory [String] name of directory where image is saved
|
135
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
136
|
+
def copy_same_format(directory, metadata)
|
137
|
+
Itchy::Log.info "[#{self.class.name}] Image #{metadata.dc_identifier.inspect} " \
|
138
|
+
'is already in the required format. Copying it to output directory.'
|
139
|
+
|
140
|
+
begin
|
141
|
+
::FileUtils.ln_sf("#{directory}/#{metadata.dc_identifier}", @options.output_dir)
|
142
|
+
rescue => ex
|
143
|
+
Itchy::Log.fatal "[#{self.class.name}] Failed to create a link (copy) " \
|
144
|
+
"for #{metadata.dc_identifier.inspect}: " \
|
145
|
+
"#{ex.message}"
|
146
|
+
fail Itchy::Errors::WorkingWithFilesError, ex
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
#
|
152
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
153
|
+
# @param vmcatcher_configuration [Itchy::VmcatcherConfiguration] current VMC configuration
|
154
|
+
# @return [String] output directory with copied files
|
155
|
+
def copy_unpacked!(metadata, vmcatcher_configuration)
|
156
|
+
Itchy::Log.info "[#{self.class.name}] Copying image " \
|
157
|
+
"for #{metadata.dc_identifier.inspect}"
|
158
|
+
unpacking_dir = prepare_image_temp_dir(metadata, vmcatcher_configuration).flatten.first
|
159
|
+
begin
|
160
|
+
::FileUtils.ln_sf(
|
161
|
+
orig_image_file(metadata, vmcatcher_configuration),
|
162
|
+
unpacking_dir
|
163
|
+
)
|
164
|
+
rescue => ex
|
165
|
+
Itchy::Log.fatal "[#{self.class.name}] Failed to create a link (copy) " \
|
166
|
+
"for #{metadata.dc_identifier.inspect}: " \
|
167
|
+
"#{ex.message}"
|
168
|
+
fail Itchy::Errors::WorkingWithFilesError, ex
|
169
|
+
end
|
170
|
+
|
171
|
+
unpacking_dir
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
#
|
176
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
177
|
+
# @param vmcatcher_configuration [Itchy::VmcatcherConfiguration] current VMC configuration
|
178
|
+
# @return [String] path to the original image downloaded by VMC
|
179
|
+
def orig_image_file(metadata, vmcatcher_configuration)
|
180
|
+
"#{vmcatcher_configuration.cache_dir_cache}/#{metadata.dc_identifier}"
|
181
|
+
end
|
182
|
+
|
183
|
+
#
|
184
|
+
#
|
185
|
+
# @param metadata [Itchy::VmcatcherEvent] event metadata
|
186
|
+
# @param vmcatcher_configuration [Itchy::VmcatcherConfiguration] current VMC configuration
|
187
|
+
# @return [String] path to the newly created image directory
|
188
|
+
def prepare_image_temp_dir(metadata, vmcatcher_configuration)
|
189
|
+
temp_dir = "#{vmcatcher_configuration.cache_dir_cache}/temp/#{metadata.dc_identifier}"
|
190
|
+
|
191
|
+
begin
|
192
|
+
::FileUtils.mkdir_p temp_dir
|
193
|
+
rescue => ex
|
194
|
+
Itchy::Log.fatal "[#{self.class.name}] Failed to create a directory " \
|
195
|
+
"for #{metadata.dc_identifier.inspect}: " \
|
196
|
+
"#{ex.message}"
|
197
|
+
fail Itchy::Errors::WorkingWithFilesError, ex
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Checks if file is archived image (format ova or tar)
|
202
|
+
#
|
203
|
+
# @param file [String] inspected file name
|
204
|
+
# @return [Boolean] archived or not
|
205
|
+
def archived?(file)
|
206
|
+
image_format_tester = Mixlib::ShellOut.new("file #{file}")
|
207
|
+
image_format_tester.run_command
|
208
|
+
begin
|
209
|
+
image_format_tester.error!
|
210
|
+
rescue
|
211
|
+
Itchy::Log.error "[#{self.class.name}] Checking file format for" \
|
212
|
+
"#{file} failed with #{image_format_tester.stderr}"
|
213
|
+
fail Itchy::Errors::FileInspectingError, ex
|
214
|
+
|
215
|
+
end
|
216
|
+
temp = image_format_tester.stdout
|
217
|
+
temp.include? ARCHIVE_STRING
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/itchy/log.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Itchy
|
2
|
+
# Handles static logging from the entire application
|
3
|
+
# without the need for a traveling logger instance.
|
4
|
+
class Log
|
5
|
+
include ::Logger::Severity
|
6
|
+
|
7
|
+
attr_reader :logger, :log_prefix
|
8
|
+
|
9
|
+
SUBSCRIPTION_HANDLE = 'itchy.log'
|
10
|
+
|
11
|
+
# Creates a new itchy logger instance. Subsequent logging can be
|
12
|
+
# then performed via static Itchy::Log methods.
|
13
|
+
#
|
14
|
+
# @param log_dev [IO,String] The log device. This is a filename (String) or IO object (typically +STDOUT+,
|
15
|
+
# @param log_prefix [String] String placed in front of every logged message
|
16
|
+
# +STDERR+, or an open file).
|
17
|
+
def initialize(log_dev, log_prefix = '[itchy]')
|
18
|
+
if log_dev.is_a? ::Logger
|
19
|
+
@logger = log_dev
|
20
|
+
else
|
21
|
+
@logger = ::Logger.new(log_dev)
|
22
|
+
end
|
23
|
+
|
24
|
+
@log_prefix = log_prefix.blank? ? '' : log_prefix.strip
|
25
|
+
|
26
|
+
# subscribe to log messages and send to logger
|
27
|
+
@log_subscriber = ActiveSupport::Notifications.subscribe(self.class::SUBSCRIPTION_HANDLE) do |_name, _start, _finish, _id, payload|
|
28
|
+
@logger.log(payload[:level], "#{@log_prefix} #{payload[:message]}") if @logger
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Closes active logger subscription.
|
33
|
+
def close
|
34
|
+
ActiveSupport::Notifications.unsubscribe(@log_subscriber)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets current logging level.
|
38
|
+
#
|
39
|
+
# @param severity [::Logger::Severity] severity
|
40
|
+
def level=(severity)
|
41
|
+
@logger.level = severity
|
42
|
+
end
|
43
|
+
|
44
|
+
# Gets current logging level.
|
45
|
+
#
|
46
|
+
# @return [::Logger::Severity]
|
47
|
+
def level
|
48
|
+
@logger.level
|
49
|
+
end
|
50
|
+
|
51
|
+
# @see info
|
52
|
+
def self.debug(message)
|
53
|
+
ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, level: ::Logger::DEBUG, message: message)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Log an +INFO+ message.
|
57
|
+
#
|
58
|
+
# @param message [String] message the message to log; does not need to be a String
|
59
|
+
def self.info(message)
|
60
|
+
ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, level: ::Logger::INFO, message: message)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @see info
|
64
|
+
def self.warn(message)
|
65
|
+
ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, level: ::Logger::WARN, message: message)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @see info
|
69
|
+
def self.error(message)
|
70
|
+
ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, level: ::Logger::ERROR, message: message)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @see info
|
74
|
+
def self.fatal(message)
|
75
|
+
ActiveSupport::Notifications.instrument(self::SUBSCRIPTION_HANDLE, level: ::Logger::FATAL, message: message)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|