itchy 0.0.1
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 +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
|