longleaf 0.1.0.pre.2

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +13 -0
  7. data/README.md +43 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/exe/longleaf +3 -0
  12. data/lib/longleaf/cli.rb +78 -0
  13. data/lib/longleaf/commands/abstract_command.rb +37 -0
  14. data/lib/longleaf/commands/register_command.rb +59 -0
  15. data/lib/longleaf/commands/validate_config_command.rb +29 -0
  16. data/lib/longleaf/errors.rb +15 -0
  17. data/lib/longleaf/events/register_event.rb +98 -0
  18. data/lib/longleaf/logging/redirecting_logger.rb +131 -0
  19. data/lib/longleaf/logging.rb +26 -0
  20. data/lib/longleaf/models/app_fields.rb +10 -0
  21. data/lib/longleaf/models/file_record.rb +25 -0
  22. data/lib/longleaf/models/md_fields.rb +18 -0
  23. data/lib/longleaf/models/metadata_record.rb +57 -0
  24. data/lib/longleaf/models/service_definition.rb +21 -0
  25. data/lib/longleaf/models/service_fields.rb +10 -0
  26. data/lib/longleaf/models/service_record.rb +27 -0
  27. data/lib/longleaf/models/storage_location.rb +37 -0
  28. data/lib/longleaf/services/application_config_deserializer.rb +46 -0
  29. data/lib/longleaf/services/application_config_manager.rb +24 -0
  30. data/lib/longleaf/services/application_config_validator.rb +18 -0
  31. data/lib/longleaf/services/configuration_validator.rb +8 -0
  32. data/lib/longleaf/services/metadata_deserializer.rb +68 -0
  33. data/lib/longleaf/services/metadata_serializer.rb +76 -0
  34. data/lib/longleaf/services/service_definition_manager.rb +36 -0
  35. data/lib/longleaf/services/service_definition_validator.rb +32 -0
  36. data/lib/longleaf/services/service_manager.rb +21 -0
  37. data/lib/longleaf/services/service_mapping_manager.rb +47 -0
  38. data/lib/longleaf/services/service_mapping_validator.rb +49 -0
  39. data/lib/longleaf/services/storage_location_manager.rb +37 -0
  40. data/lib/longleaf/services/storage_location_validator.rb +60 -0
  41. data/lib/longleaf/services/storage_path_validator.rb +16 -0
  42. data/lib/longleaf/specs/config_builder.rb +102 -0
  43. data/lib/longleaf/version.rb +3 -0
  44. data/lib/longleaf.rb +4 -0
  45. data/longleaf.gemspec +34 -0
  46. metadata +188 -0
@@ -0,0 +1,57 @@
1
+ require_relative 'md_fields'
2
+ require_relative 'service_record'
3
+
4
+ # Metadata record for a single file
5
+ module Longleaf
6
+ class MetadataRecord
7
+ attr_reader :deregistered, :registered
8
+ attr_reader :checksums
9
+ attr_reader :properties
10
+ attr_accessor :file_size, :last_modified
11
+
12
+ # @param properties [Hash] initial data properties for this record
13
+ # @param services [Hash] initial service property tree
14
+ # @param deregistered [String] deregistered timestamp
15
+ # @param registered [String] registered timestamp
16
+ # @param checksums [Hash] hash of checksum values
17
+ # @param file_size [Integer] size of file in bytes
18
+ # @param last_modified [String] iso8601 representation of the last modified date of file
19
+ def initialize(properties: Hash.new, services: Hash.new, deregistered: nil, registered: nil, checksums: Hash.new,
20
+ file_size: nil, last_modified: nil)
21
+ @properties = properties
22
+ @registered = registered
23
+ @deregistered = deregistered
24
+ @checksums = checksums
25
+ @services = services
26
+ @file_size = file_size
27
+ @last_modified = last_modified
28
+ end
29
+
30
+ # @return [Boolean] true if the record is deregistered
31
+ def deregistered?
32
+ !@deregistered.nil?
33
+ end
34
+
35
+ # Adds a service to this record
36
+ #
37
+ # @param name [String] identifier for the service being added
38
+ # @param service_properties [ServiceRecord] properties for populating the new service
39
+ def add_service(name, service = Longleaf::ServiceRecord.new)
40
+ raise ArgumentError.new("Value must be a ServiceRecord object when adding a service") unless service.class == Longleaf::ServiceRecord
41
+ raise IndexError.new("Service with name '#{name}' already exists") if @services.key?(name)
42
+
43
+ @services[name] = service
44
+ end
45
+
46
+ # @param name [String] name identifier of the service to retrieve
47
+ # @return [ServiceRecord] the ServiceRecord for the service identified by name, or nil
48
+ def service(name)
49
+ @services[name]
50
+ end
51
+
52
+ # @return [Array<String>] a list of name identifiers for services registered to this record
53
+ def list_services
54
+ @services.keys
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'service_fields'
2
+
3
+ # Definition of a preservation service
4
+ module Longleaf
5
+ class ServiceDefinition
6
+ attr_reader :name
7
+ attr_reader :work_script
8
+ attr_reader :frequency, :delay
9
+ attr_reader :properties
10
+
11
+ def initialize(name:, work_script:, frequency: nil, delay: nil, properties: Hash.new)
12
+ raise ArgumentError.new("Parameters name and work_script are required") unless name && work_script
13
+
14
+ @properties = properties
15
+ @name = name
16
+ @work_script = work_script
17
+ @frequency = frequency
18
+ @delay = delay
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ module Longleaf
2
+ class ServiceFields
3
+ WORK_SCRIPT = 'work_script'
4
+ FREQUENCY = 'frequency'
5
+ DELAY = 'delay'
6
+
7
+ REPLICATE_TO = 'to'
8
+ DIGEST_ALGORITHMS = 'algorithms'
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # Record for an individual service in a file's metadata record.
2
+ module Longleaf
3
+ class ServiceRecord
4
+ attr_reader :properties
5
+ attr_accessor :stale_replicas, :timestamp, :run_needed
6
+
7
+ # @param properties [Hash] initial properties for this service record
8
+ def initialize(properties: Hash.new, stale_replicas: false, timestamp: nil, run_needed: false)
9
+ raise ArgumentError.new("Service properties must be a hash") if properties.class != Hash
10
+
11
+ @properties = properties
12
+ @timestamp = timestamp
13
+ @stale_replicas = stale_replicas
14
+ @run_needed = run_needed
15
+ end
16
+
17
+ # @return the value of a service property identified by key
18
+ def [](key)
19
+ @properties[key]
20
+ end
21
+
22
+ # set the value of a service property identified by key
23
+ def []=(key, value)
24
+ @properties[key] = value
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ require 'longleaf/services/metadata_serializer'
2
+
3
+ module Longleaf
4
+ class StorageLocation
5
+ attr_reader :name
6
+ attr_reader :path
7
+ attr_reader :metadata_path
8
+
9
+ def initialize(name:, path:, metadata_path:)
10
+ raise ArgumentError.new("Parameters name, path and metadata_path are required") unless name && path && metadata_path
11
+
12
+ @path = path
13
+ @name = name
14
+ @metadata_path = metadata_path
15
+ end
16
+
17
+ # Get the path for the metadata file for the given file path located in this storage location.
18
+ # @param file_path [String] path of the file
19
+ # @raise [ArgumentError] if the file_path is not provided or is not in this storage location.
20
+ def get_metadata_path_for(file_path)
21
+ raise ArgumentError.new("A file_path parameter is required") if file_path.nil? || file_path.empty?
22
+ raise ArgumentError.new("Provided file path is not contained by storage location #{@name}: #{file_path}") \
23
+ unless file_path.start_with?(@path)
24
+
25
+ file_path.sub(/^#{@path}/, metadata_path) + MetadataSerializer::metadata_suffix
26
+ end
27
+
28
+ # Checks that the path and metadata path defined in this location are available
29
+ # @raise [StorageLocationUnavailableError] if the storage location is not available
30
+ def available?
31
+ raise StorageLocationUnavailableError.new("Path does not exist or is not a directory: #{@path}")\
32
+ unless Dir.exist?(@path)
33
+ raise StorageLocationUnavailableError.new("Metadata path does not exist or is not a directory: #{@metadata_path}")\
34
+ unless Dir.exist?(@metadata_path)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,46 @@
1
+ require 'longleaf/services/application_config_validator'
2
+ require 'longleaf/services/application_config_manager'
3
+
4
+ # Deserializer for application configuration files
5
+ module Longleaf
6
+ class ApplicationConfigDeserializer
7
+
8
+ # Deserializes a valid application configuration file as a ApplicationConfigManager option
9
+ # @param config_path [String] file path to the application configuration file
10
+ # @param format [String] encoding format of the config file
11
+ # return [Longleaf::ApplicationConfigManager] manager for the loaded configuration
12
+ def self.deserialize(config_path, format: 'yaml')
13
+ config = load(config_path, format: format)
14
+
15
+ Longleaf::ApplicationConfigValidator.validate(config)
16
+ Longleaf::ApplicationConfigManager.new(config)
17
+ end
18
+
19
+ # Deserialize a configuration file into a hash
20
+ # @param config_path [String] file path to the application configuration file
21
+ # @param format [String] encoding format of the config file
22
+ # return [Hash] hash containing the configuration
23
+ def self.load(config_path, format: 'yaml')
24
+ case format
25
+ when 'yaml'
26
+ from_yaml(config_path)
27
+ else
28
+ raise ArgumentError.new('Invalid deserialization format #{format} specified')
29
+ end
30
+ end
31
+
32
+ private
33
+ def self.from_yaml(config_path)
34
+ begin
35
+ YAML.load_file(config_path)
36
+ rescue Errno::ENOENT => err
37
+ raise Longleaf::ConfigurationError.new(
38
+ "Cannot load application configuration, file #{config_path} does not exist.")
39
+ rescue => err
40
+ raise Longleaf::ConfigurationError.new(
41
+ %Q(Failed to load application configuration due to the following reason:
42
+ #{err.message}))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'storage_location_validator'
2
+ require_relative 'storage_location_manager'
3
+ require_relative 'service_definition_validator'
4
+ require_relative 'service_definition_manager'
5
+ require_relative 'service_mapping_validator'
6
+ require_relative 'service_mapping_manager'
7
+ require_relative 'service_manager'
8
+
9
+ # Manager which loads and provides access to the configuration of the application
10
+ module Longleaf
11
+ class ApplicationConfigManager
12
+ attr_reader :service_manager
13
+ attr_reader :location_manager
14
+
15
+ def initialize(config)
16
+ @location_manager = Longleaf::StorageLocationManager.new(config)
17
+
18
+ definition_manager = Longleaf::ServiceDefinitionManager.new(config)
19
+ mapping_manager = Longleaf::ServiceMappingManager.new(config)
20
+ @service_manager = Longleaf::ServiceManager.new(
21
+ definition_manager: definition_manager, mapping_manager: mapping_manager)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'storage_location_validator'
2
+ require_relative 'service_definition_validator'
3
+ require_relative 'service_mapping_validator'
4
+
5
+ # Validator for Longleaf application configuration
6
+ module Longleaf
7
+ class ApplicationConfigValidator
8
+
9
+ # Validates the application configuration provided. Will raise ConfigurationError
10
+ # if any portion of the configuration is not syntactically or semantically valid.
11
+ # @param config [Hash] application configuration
12
+ def self.validate(config)
13
+ Longleaf::StorageLocationValidator::validate_config(config)
14
+ Longleaf::ServiceDefinitionValidator::validate_config(config)
15
+ Longleaf::ServiceMappingValidator::validate_config(config)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Longleaf
2
+ class ConfigurationValidator
3
+ protected
4
+ def self.assert(fail_message, assertion_passed)
5
+ raise ConfigurationError.new(fail_message) unless assertion_passed
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,68 @@
1
+ require 'yaml'
2
+ require_relative '../models/metadata_record'
3
+ require_relative '../models/md_fields'
4
+ require_relative '../errors'
5
+
6
+ # Service which deserializes metadata files into MetadataRecord objects
7
+ module Longleaf
8
+ class MetadataDeserializer
9
+ MDF = Longleaf::MDFields
10
+
11
+ # Deserialize a file into a MetadataRecord object
12
+ #
13
+ # @param file_path [String] path of the file to read. Required.
14
+ # @param format [String] format the file is stored in. Default is 'yaml'.
15
+ def self.deserialize(file_path:, format: 'yaml')
16
+ case format
17
+ when 'yaml'
18
+ md = from_yaml(file_path)
19
+ else
20
+ raise ArgumentError.new('Invalid deserialization format #{format} specified')
21
+ end
22
+
23
+ if !md || !md.key?(MDF::DATA) || !md.key?(MDF::SERVICES)
24
+ raise Longleaf::MetadataError.new("Invalid metadata file, did not contain data or services fields: #{file_path}")
25
+ end
26
+
27
+ data = Hash.new.merge(md[MDF::DATA])
28
+ # Extract reserved properties for submission as separate parameters
29
+ registered = data.delete(MDFields::REGISTERED_TIMESTAMP)
30
+ deregistered = data.delete(MDFields::DEREGISTERED_TIMESTAMP)
31
+ checksums = data.delete(MDFields::CHECKSUMS)
32
+ file_size = data.delete(MDFields::FILE_SIZE)
33
+ last_modified = data.delete(MDFields::LAST_MODIFIED)
34
+
35
+ services = md[MDF::SERVICES]
36
+ service_records = Hash.new
37
+ unless services.nil?
38
+ services.each do |name, props|
39
+ raise Longleaf::MetadataError.new("Value of service #{name} must be a hash") unless props.class == Hash
40
+
41
+ service_props = Hash.new.merge(props)
42
+
43
+ stale_replicas = service_props.delete(MDFields::STALE_REPLICAS)
44
+ timestamp = service_props.delete(MDFields::SERVICE_TIMESTAMP)
45
+ run_needed = service_props.delete(MDFields::RUN_NEEDED)
46
+
47
+ service_records[name] = ServiceRecord.new(
48
+ properties: service_props,
49
+ stale_replicas: stale_replicas,
50
+ timestamp: timestamp,
51
+ run_needed: run_needed)
52
+ end
53
+ end
54
+
55
+ MetadataRecord.new(properties: data,
56
+ services: service_records,
57
+ registered: registered,
58
+ deregistered: deregistered,
59
+ checksums: checksums,
60
+ file_size: file_size,
61
+ last_modified: last_modified)
62
+ end
63
+
64
+ def self.from_yaml(file_path)
65
+ YAML.load_file(file_path)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,76 @@
1
+ require 'yaml'
2
+ require 'longleaf/models/metadata_record'
3
+ require 'longleaf/models/md_fields'
4
+ require 'pathname'
5
+
6
+ # Service which serializes MetadataRecord objects
7
+ module Longleaf
8
+ class MetadataSerializer
9
+ MDF = Longleaf::MDFields
10
+
11
+ # Serialize the contents of the provided metadata record to the specified path
12
+ #
13
+ # @param metadata [MetadataRecord] metadata record to serialize. Required.
14
+ # @param file_path [String] path to write the file to. Required.
15
+ # @param format [String] format to serialize the metadata in. Default is 'yaml'.
16
+ def self.write(metadata:, file_path:, format: 'yaml')
17
+ raise ArgumentError.new('metadata parameter must be a MetadataRecord') \
18
+ unless metadata.class == Longleaf::MetadataRecord
19
+
20
+ case format
21
+ when 'yaml'
22
+ content = to_yaml(metadata)
23
+ else
24
+ raise ArgumentError.new('Invalid serialization format #{format} specified')
25
+ end
26
+
27
+ # Fill in parent directories if they do not exist
28
+ parent_dir = Pathname(file_path).parent
29
+ parent_dir.mkpath unless parent_dir.exist?
30
+
31
+ File.write(file_path, content)
32
+ end
33
+
34
+ # @param metadata [MetadataRecord] metadata record to transform
35
+ # @return [String] a yaml representation of the provided MetadataRecord
36
+ def self.to_yaml(metadata)
37
+ props = to_hash(metadata)
38
+ props.to_yaml
39
+ end
40
+
41
+ def self.to_hash(metadata)
42
+ props = Hash.new
43
+
44
+ data = Hash.new.merge(metadata.properties)
45
+ data[MDF::REGISTERED_TIMESTAMP] = metadata.registered if metadata.registered
46
+ data[MDF::DEREGISTERED_TIMESTAMP] = metadata.deregistered if metadata.deregistered
47
+ data[MDF::CHECKSUMS] = metadata.checksums unless metadata.checksums&.empty?
48
+ data[MDF::FILE_SIZE] = metadata.file_size unless metadata.file_size.nil?
49
+ data[MDF::LAST_MODIFIED] = metadata.last_modified if metadata.last_modified
50
+
51
+ props[MDF::DATA] = data
52
+
53
+ services = Hash.new
54
+ metadata.list_services.each do |name|
55
+ service = metadata.service(name)
56
+ service[MDF::STALE_REPLICAS] = service.stale_replicas if service.stale_replicas
57
+ service[MDF::SERVICE_TIMESTAMP] = service.timestamp unless service.timestamp.nil?
58
+ service[MDF::RUN_NEEDED] = service.run_needed if service.run_needed
59
+ services[name] = service.properties
60
+ end
61
+
62
+ props[MDF::SERVICES] = services
63
+
64
+ props
65
+ end
66
+
67
+ def self.metadata_suffix(format: 'yaml')
68
+ case format
69
+ when 'yaml'
70
+ '-llmd.yaml'
71
+ else
72
+ raise ArgumentError.new('Invalid serialization format #{format} specified')
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,36 @@
1
+ require_relative '../models/app_fields'
2
+ require_relative '../models/service_definition'
3
+
4
+ # Manager which loads and provides access to Longleaf::ServiceDefinition objects
5
+ module Longleaf
6
+ class ServiceDefinitionManager
7
+ SF ||= Longleaf::ServiceFields
8
+ AF ||= Longleaf::AppFields
9
+
10
+ attr_reader :services
11
+
12
+ def initialize(config)
13
+ raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
14
+
15
+ services_config = config[AF::SERVICES]
16
+ raise ArgumentError.new("Services configuration must be provided") if services_config.nil?
17
+
18
+ @services = Hash.new
19
+ config[AF::SERVICES].each do |name, properties|
20
+ work_script = properties.delete(SF::WORK_SCRIPT)
21
+ frequency = properties.delete(SF::FREQUENCY)
22
+ delay = properties.delete(SF::DELAY)
23
+ service = Longleaf::ServiceDefinition.new(
24
+ name: name,
25
+ work_script: work_script,
26
+ frequency: frequency,
27
+ delay: delay,
28
+ properties: properties)
29
+
30
+ @services[name] = service
31
+ end
32
+ @services.freeze
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ require 'pathname'
2
+ require_relative '../models/service_fields'
3
+ require_relative '../models/app_fields'
4
+ require_relative '../errors'
5
+ require_relative 'configuration_validator'
6
+
7
+ # Validates application configuration of service definitions
8
+ module Longleaf
9
+ class ServiceDefinitionValidator < ConfigurationValidator
10
+ SF ||= Longleaf::ServiceFields
11
+ AF ||= Longleaf::AppFields
12
+
13
+ # Validates configuration to ensure that it is syntactically correct and does not violate
14
+ # schema requirements.
15
+ # @param config [Hash] hash containing the application configuration
16
+ def self.validate_config(config)
17
+ assert("Configuration must be a hash, but a #{config.class} was provided", config.class == Hash)
18
+ assert("Configuration must contain a root '#{AF::SERVICES}' key", config.key?(AF::SERVICES))
19
+ services = config[AF::SERVICES]
20
+ assert("'#{AF::SERVICES}' must be a hash of services", services.class == Hash)
21
+
22
+ existing_paths = Array.new
23
+ services.each do |name, properties|
24
+ assert("Name of service definition must be a string, but was of type #{name.class}", name.instance_of?(String))
25
+ assert("Service definition '#{name}' must be a hash, but a #{properties.class} was provided", properties.is_a?(Hash))
26
+
27
+ work_script = properties[SF::WORK_SCRIPT]
28
+ assert("Service definition '#{name}' must specify a '#{SF::WORK_SCRIPT}' property", !work_script.nil? && !work_script.empty?)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,21 @@
1
+ # Manager which provides preservation service definitions based on their mappings
2
+ module Longleaf
3
+ class ServiceManager
4
+
5
+ def initialize(definition_manager:, mapping_manager:)
6
+ raise ArgumentError.new('Service definition manager required') if definition_manager.nil?
7
+ raise ArgumentError.new('Service mappings manager required') if mapping_manager.nil?
8
+ @definition_manager = definition_manager
9
+ @mapping_manager = mapping_manager
10
+ end
11
+
12
+ # Gets a list of ServiceDefinition objects which match the given criteria
13
+ # @param location [String] name of the location to lookup
14
+ # @return [Array] a list of ServiceDefinition objects associated with the location,
15
+ # or an empty list if no services match the criteria
16
+ def list_service_definitions(location: nil)
17
+ service_names = @mapping_manager.list_services(location)
18
+ service_names.collect { |name| @definition_manager.services[name] }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../models/app_fields'
2
+ require_relative '../models/service_definition'
3
+
4
+ # Manager which loads and provides access to location to service mappings
5
+ module Longleaf
6
+ class ServiceMappingManager
7
+ AF ||= Longleaf::AppFields
8
+
9
+ def initialize(config)
10
+ raise ArgumentError.new("Configuration must be provided") if config.nil? || config.empty?
11
+
12
+ mappings_config = config[AF::SERVICE_MAPPINGS]
13
+ raise ArgumentError.new("Service mappings configuration must be provided") if mappings_config.nil?
14
+
15
+ @loc_to_services = Hash.new
16
+
17
+ mappings_config.each do |mapping|
18
+ locations = mapping[AF::LOCATIONS]
19
+ services = mapping[AF::SERVICES]
20
+
21
+ locations = [locations] if locations.is_a?(String)
22
+ services = [services] if services.is_a?(String)
23
+
24
+ locations.each do |loc_name|
25
+ @loc_to_services[loc_name] = Array.new unless @loc_to_services.key?(loc_name)
26
+
27
+ service_set = @loc_to_services[loc_name]
28
+ if services.is_a?(String)
29
+ service_set.push(services)
30
+ else
31
+ service_set.concat(services)
32
+ end
33
+ end
34
+ end
35
+
36
+ @loc_to_services.each { |loc, services| services.uniq! }
37
+ @loc_to_services.freeze
38
+ end
39
+
40
+ # Gets a list of service names associated with the given location
41
+ # @param loc_name [String] name of the location to lookup
42
+ # @return [Array] a list of service names associated with the location
43
+ def list_services(loc_name)
44
+ @loc_to_services[loc_name] || []
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,49 @@
1
+ require 'pathname'
2
+ require_relative '../models/service_fields'
3
+ require_relative '../models/app_fields'
4
+ require_relative '../errors'
5
+ require_relative 'configuration_validator'
6
+
7
+ # Validates application configuration of service to location mappings
8
+ module Longleaf
9
+ class ServiceMappingValidator < ConfigurationValidator
10
+ AF ||= Longleaf::AppFields
11
+
12
+ # Validates service mapping configuration to ensure that it is syntactically and referentially correct.
13
+ # @param config [Hash] hash containing the application configuration
14
+ def self.validate_config(config)
15
+
16
+ assert("Configuration must be a hash, but a #{config.class} was provided", config.class == Hash)
17
+ assert("Configuration must contain a root '#{AF::SERVICE_MAPPINGS}' key", config.key?(AF::SERVICE_MAPPINGS))
18
+ mappings = config[AF::SERVICE_MAPPINGS]
19
+ return if mappings.nil? || mappings.empty?
20
+ assert("'#{AF::SERVICE_MAPPINGS}' must be an array of mappings", mappings.is_a?(Array))
21
+
22
+ service_names = config[AF::SERVICES].keys
23
+ location_names = config[AF::LOCATIONS].keys
24
+
25
+ existing_paths = Array.new
26
+ mappings.each do |mapping|
27
+ assert("Mapping must be a hash, but received #{mapping.inspect} instead", mapping.is_a?(Hash))
28
+
29
+ validate_mapping_field(AF::LOCATIONS, mapping, location_names)
30
+ validate_mapping_field(AF::SERVICES, mapping, service_names)
31
+ end
32
+ end
33
+
34
+ private
35
+ def self.validate_mapping_field(field, mapping, valid_values)
36
+ assert("Mapping must contain a '#{field}' field", mapping.key?(field))
37
+ field_values = mapping[field]
38
+ assert("Mapping '#{field}' field must be either a string or an array, but received '#{field_values.inspect}' instead",
39
+ field_values.is_a?(Array) || field_values.is_a?(String))
40
+ assert("Mapping must specify one or more value in the '#{field}' field", !field_values.empty?)
41
+
42
+ check_values = field_values.is_a?(String) ? [field_values] : field_values
43
+ check_values.each do |value|
44
+ assert("Mapping '#{field}' specifies value '#{value}', but no #{field} with that name exist",
45
+ valid_values.include?(value))
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ require_relative '../models/app_fields'
2
+ require_relative '../models/storage_location'
3
+
4
+ # Manager which loads and provides access to Longleaf::StorageLocation objects
5
+ module Longleaf
6
+ class StorageLocationManager
7
+ AF ||= Longleaf::AppFields
8
+
9
+ attr_reader :locations
10
+
11
+ def initialize(config)
12
+ raise ArgumentError.new("Configuration must be provided") if config&.empty?
13
+
14
+ @locations = Hash.new
15
+ config[AF::LOCATIONS].each do |name, properties|
16
+ path = properties[AF::LOCATION_PATH]
17
+ md_path = properties[AF::METADATA_PATH]
18
+ location = Longleaf::StorageLocation.new(name: name, path: path, metadata_path: md_path)
19
+
20
+ @locations[name] = location
21
+ end
22
+ @locations.freeze
23
+ end
24
+
25
+ # Get the StorageLocation object which should contain the given path
26
+ # @return [Longleaf::StorageLocation] location containing the given path
27
+ # or nil if the path is not contained by a registered location.
28
+ def get_location_by_path(path)
29
+ raise ArgumentError.new("Path parameter is required") if path.nil? || path.empty?
30
+ @locations.each do |name, location|
31
+ return location if path.start_with?(location.path)
32
+ end
33
+
34
+ nil
35
+ end
36
+ end
37
+ end