itchy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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