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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +38 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +16 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +203 -0
  7. data/README.md +16 -0
  8. data/Rakefile +25 -0
  9. data/bin/itchy +142 -0
  10. data/config/itchy.yml +32 -0
  11. data/itchy.gemspec +41 -0
  12. data/lib/itchy/errors/abstract_env_error.rb +4 -0
  13. data/lib/itchy/errors/env_lookup_error.rb +4 -0
  14. data/lib/itchy/errors/event_processing_error.rb +4 -0
  15. data/lib/itchy/errors/file_inspecting_error.rb +4 -0
  16. data/lib/itchy/errors/format_converting_error.rb +4 -0
  17. data/lib/itchy/errors/image_transforming_error.rb +4 -0
  18. data/lib/itchy/errors/not_found_error.rb +4 -0
  19. data/lib/itchy/errors/not_implemented_error.rb +4 -0
  20. data/lib/itchy/errors/unknown_event_error.rb +4 -0
  21. data/lib/itchy/errors/working_with_files_error.rb +4 -0
  22. data/lib/itchy/errors/wrong_state_error.rb +4 -0
  23. data/lib/itchy/errors.rb +5 -0
  24. data/lib/itchy/event_handlers/available_postfix_event_handler.rb +43 -0
  25. data/lib/itchy/event_handlers/available_prefix_event_handler.rb +10 -0
  26. data/lib/itchy/event_handlers/base_event_handler.rb +82 -0
  27. data/lib/itchy/event_handlers/expire_postfix_event_handler.rb +30 -0
  28. data/lib/itchy/event_handlers/expire_prefix_event_handler.rb +9 -0
  29. data/lib/itchy/event_handlers/process_postfix_event_handler.rb +9 -0
  30. data/lib/itchy/event_handlers/process_prefix_event_handler.rb +10 -0
  31. data/lib/itchy/event_handlers/subscription_image_new_event_handler.rb +9 -0
  32. data/lib/itchy/event_handlers.rb +8 -0
  33. data/lib/itchy/event_processer.rb +103 -0
  34. data/lib/itchy/format_converter.rb +43 -0
  35. data/lib/itchy/helpers/vmcatcher_env_helper.rb +44 -0
  36. data/lib/itchy/helpers.rb +5 -0
  37. data/lib/itchy/image_transformer.rb +220 -0
  38. data/lib/itchy/log.rb +78 -0
  39. data/lib/itchy/metadata_archiver.rb +59 -0
  40. data/lib/itchy/settings.rb +35 -0
  41. data/lib/itchy/version.rb +3 -0
  42. data/lib/itchy/vmcatcher_configuration.rb +62 -0
  43. data/lib/itchy/vmcatcher_env.rb +58 -0
  44. data/lib/itchy/vmcatcher_event.rb +71 -0
  45. data/lib/itchy.rb +26 -0
  46. data/spec/itchy/event_handlers/available_postfix_event_handler_spec.rb +0 -0
  47. data/spec/itchy/event_handlers/expire_postfix_event_handler_spec.rb +0 -0
  48. data/spec/itchy/event_processer_spec.rb +0 -0
  49. data/spec/itchy/format_converter_spec.rb +0 -0
  50. data/spec/itchy/image_transformer_spec.rb +0 -0
  51. data/spec/itchy/metadata_archiver_spec.rb +0 -0
  52. data/spec/itchy/vmcatcher_configuration_spec.rb +0 -0
  53. data/spec/itchy/vmcatcher_env_spec.rb +1 -0
  54. data/spec/itchy/vmcatcher_event_spec.rb +0 -0
  55. data/spec/spec_helper.rb +11 -0
  56. 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,5 @@
1
+ # Wrapper for all available helpers.
2
+ module Itchy::Helpers; end
3
+
4
+ # Load all available helpers
5
+ Dir.glob(File.join(File.dirname(__FILE__), 'helpers', '*.rb')) { |helper_file| require helper_file.chomp('.rb') }
@@ -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