shu-san-scripts 0.2.0 → 0.2.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.
- data/README.rdoc +6 -4
- data/lib/SANStore.rb +24 -0
- data/lib/SANStore/cli.rb +29 -0
- data/lib/SANStore/cli/base.rb +103 -0
- data/lib/SANStore/cli/commands.rb +31 -0
- data/lib/SANStore/cli/commands/delete_vol.rb +113 -0
- data/lib/SANStore/cli/commands/help.rb +106 -0
- data/lib/SANStore/cli/commands/list_vols.rb +103 -0
- data/lib/SANStore/cli/commands/new_vol.rb +134 -0
- data/lib/SANStore/cli/logger.rb +92 -0
- data/lib/SANStore/cri.rb +12 -0
- data/lib/SANStore/cri/base.rb +153 -0
- data/lib/SANStore/cri/command.rb +104 -0
- data/lib/SANStore/cri/core_ext.rb +8 -0
- data/lib/SANStore/cri/core_ext/string.rb +41 -0
- data/lib/SANStore/cri/option_parser.rb +186 -0
- data/lib/SANStore/helpers/uuid.rb +35 -0
- data/lib/SANStore/iSCSI/comstar.rb +239 -0
- data/lib/SANStore/zfs/zfs.rb +59 -0
- metadata +22 -4
@@ -0,0 +1,41 @@
|
|
1
|
+
module Cri::CoreExtensions
|
2
|
+
|
3
|
+
module String
|
4
|
+
|
5
|
+
# Word-wraps and indents the string.
|
6
|
+
#
|
7
|
+
# +width+:: The maximal width of each line. This also includes indentation,
|
8
|
+
# i.e. the actual maximal width of the text is width-indentation.
|
9
|
+
#
|
10
|
+
# +indentation+:: The number of spaces to indent each wrapped line.
|
11
|
+
def wrap_and_indent(width, indentation)
|
12
|
+
# Split into paragraphs
|
13
|
+
paragraphs = self.split("\n").map { |p| p.strip }.reject { |p| p == '' }
|
14
|
+
|
15
|
+
# Wrap and indent each paragraph
|
16
|
+
paragraphs.map do |paragraph|
|
17
|
+
# Initialize
|
18
|
+
lines = []
|
19
|
+
line = ''
|
20
|
+
|
21
|
+
# Split into words
|
22
|
+
paragraph.split(/\s/).each do |word|
|
23
|
+
# Begin new line if it's too long
|
24
|
+
if (line + ' ' + word).length >= width
|
25
|
+
lines << line
|
26
|
+
line = ''
|
27
|
+
end
|
28
|
+
|
29
|
+
# Add word to line
|
30
|
+
line += (line == '' ? '' : ' ' ) + word
|
31
|
+
end
|
32
|
+
lines << line
|
33
|
+
|
34
|
+
# Join lines
|
35
|
+
lines.map { |l| ' '*indentation + l }.join("\n")
|
36
|
+
end.join("\n\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module Cri
|
2
|
+
|
3
|
+
# Cri::OptionParser is used for parsing commandline options.
|
4
|
+
class OptionParser
|
5
|
+
|
6
|
+
# Error that will be raised when an unknown option is encountered.
|
7
|
+
class IllegalOptionError < RuntimeError ; end
|
8
|
+
|
9
|
+
# Error that will be raised when an option without argument is
|
10
|
+
# encountered.
|
11
|
+
class OptionRequiresAnArgumentError < RuntimeError ; end
|
12
|
+
|
13
|
+
# Parses the commandline arguments in +arguments_and_options+, using the
|
14
|
+
# commandline option definitions in +definitions+.
|
15
|
+
#
|
16
|
+
# +arguments_and_options+ is an array of commandline arguments and
|
17
|
+
# options. This will usually be +ARGV+.
|
18
|
+
#
|
19
|
+
# +definitions+ contains a list of hashes defining which options are
|
20
|
+
# allowed and how they will be handled. Such a hash has three keys:
|
21
|
+
#
|
22
|
+
# :short:: The short name of the option, e.g. +a+. Do not include the '-'
|
23
|
+
# prefix.
|
24
|
+
#
|
25
|
+
# :long:: The long name of the option, e.g. +all+. Do not include the '--'
|
26
|
+
# prefix.
|
27
|
+
#
|
28
|
+
# :argument:: Whether this option's argument is required (:required),
|
29
|
+
# optional (:optional) or forbidden (:forbidden).
|
30
|
+
#
|
31
|
+
# A sample array of definition hashes could look like this:
|
32
|
+
#
|
33
|
+
# [
|
34
|
+
# { :short => 'a', :long => 'all', :argument => :forbidden },
|
35
|
+
# { :short => 'p', :long => 'port', :argument => :required },
|
36
|
+
# ]
|
37
|
+
#
|
38
|
+
# During parsing, two errors can be raised:
|
39
|
+
#
|
40
|
+
# IllegalOptionError:: An unrecognised option was encountered, i.e. an
|
41
|
+
# option that is not present in the list of option
|
42
|
+
# definitions.
|
43
|
+
#
|
44
|
+
# OptionRequiresAnArgumentError:: An option was found that did not have a
|
45
|
+
# value, even though this value was
|
46
|
+
# required.
|
47
|
+
#
|
48
|
+
# What will be returned, is a hash with two keys, :arguments and :options.
|
49
|
+
# The :arguments value contains a list of arguments, and the :options
|
50
|
+
# value contains a hash with key-value pairs for each option. Options
|
51
|
+
# without values will have a +nil+ value instead.
|
52
|
+
#
|
53
|
+
# For example, the following commandline options (which should not be
|
54
|
+
# passed as a string, but as an array of strings):
|
55
|
+
#
|
56
|
+
# foo -xyz -a hiss -s -m please --level 50 --father=ani -n luke squeak
|
57
|
+
#
|
58
|
+
# with the following option definitions:
|
59
|
+
#
|
60
|
+
# [
|
61
|
+
# { :short => 'x', :long => 'xxx', :argument => :forbidden },
|
62
|
+
# { :short => 'y', :long => 'yyy', :argument => :forbidden },
|
63
|
+
# { :short => 'z', :long => 'zzz', :argument => :forbidden },
|
64
|
+
# { :short => 'a', :long => 'all', :argument => :forbidden },
|
65
|
+
# { :short => 's', :long => 'stuff', :argument => :optional },
|
66
|
+
# { :short => 'm', :long => 'more', :argument => :optional },
|
67
|
+
# { :short => 'l', :long => 'level', :argument => :required },
|
68
|
+
# { :short => 'f', :long => 'father', :argument => :required },
|
69
|
+
# { :short => 'n', :long => 'name', :argument => :required }
|
70
|
+
# ]
|
71
|
+
#
|
72
|
+
# will be translated into:
|
73
|
+
#
|
74
|
+
# {
|
75
|
+
# :arguments => [ 'foo', 'hiss', 'squeak' ],
|
76
|
+
# :options => {
|
77
|
+
# :xxx => true,
|
78
|
+
# :yyy => true,
|
79
|
+
# :zzz => true,
|
80
|
+
# :all => true,
|
81
|
+
# :stuff => true,
|
82
|
+
# :more => 'please',
|
83
|
+
# :level => '50',
|
84
|
+
# :father => 'ani',
|
85
|
+
# :name => 'luke'
|
86
|
+
# }
|
87
|
+
# }
|
88
|
+
def self.parse(arguments_and_options, definitions)
|
89
|
+
# Don't touch original argument
|
90
|
+
unprocessed_arguments_and_options = arguments_and_options.dup
|
91
|
+
|
92
|
+
# Initialize
|
93
|
+
arguments = []
|
94
|
+
options = {}
|
95
|
+
|
96
|
+
# Determines whether we've passed the '--' marker or not
|
97
|
+
no_more_options = false
|
98
|
+
|
99
|
+
loop do
|
100
|
+
# Get next item
|
101
|
+
e = unprocessed_arguments_and_options.shift
|
102
|
+
break if e.nil?
|
103
|
+
|
104
|
+
# Handle end-of-options marker
|
105
|
+
if e == '--'
|
106
|
+
no_more_options = true
|
107
|
+
# Handle incomplete options
|
108
|
+
elsif e =~ /^--./ and !no_more_options
|
109
|
+
# Get option key, and option value if included
|
110
|
+
if e =~ /^--([^=]+)=(.+)$/
|
111
|
+
option_key = $1
|
112
|
+
option_value = $2
|
113
|
+
else
|
114
|
+
option_key = e[2..-1]
|
115
|
+
option_value = nil
|
116
|
+
end
|
117
|
+
|
118
|
+
# Find definition
|
119
|
+
definition = definitions.find { |d| d[:long] == option_key }
|
120
|
+
raise IllegalOptionError.new(option_key) if definition.nil?
|
121
|
+
|
122
|
+
if [ :required, :optional ].include?(definition[:argument])
|
123
|
+
# Get option value if necessary
|
124
|
+
if option_value.nil?
|
125
|
+
option_value = unprocessed_arguments_and_options.shift
|
126
|
+
if option_value.nil? || option_value =~ /^-/
|
127
|
+
if definition[:argument] == :required
|
128
|
+
raise OptionRequiresAnArgumentError.new(option_key)
|
129
|
+
else
|
130
|
+
unprocessed_arguments_and_options.unshift(option_value)
|
131
|
+
option_value = true
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Store option
|
137
|
+
options[definition[:long].to_sym] = option_value
|
138
|
+
else
|
139
|
+
# Store option
|
140
|
+
options[definition[:long].to_sym] = true
|
141
|
+
end
|
142
|
+
# Handle -xyz options
|
143
|
+
elsif e =~ /^-./ and !no_more_options
|
144
|
+
# Get option keys
|
145
|
+
option_keys = e[1..-1].scan(/./)
|
146
|
+
|
147
|
+
# For each key
|
148
|
+
option_keys.each do |option_key|
|
149
|
+
# Find definition
|
150
|
+
definition = definitions.find { |d| d[:short] == option_key }
|
151
|
+
raise IllegalOptionError.new(option_key) if definition.nil?
|
152
|
+
|
153
|
+
if option_keys.length > 1 and definition[:argument] == :required
|
154
|
+
# This is a combined option and it requires an argument, so complain
|
155
|
+
raise OptionRequiresAnArgumentError.new(option_key)
|
156
|
+
elsif [ :required, :optional ].include?(definition[:argument])
|
157
|
+
# Get option value
|
158
|
+
option_value = unprocessed_arguments_and_options.shift
|
159
|
+
if option_value.nil? || option_value =~ /^-/
|
160
|
+
if definition[:argument] == :required
|
161
|
+
raise OptionRequiresAnArgumentError.new(option_key)
|
162
|
+
else
|
163
|
+
unprocessed_arguments_and_options.unshift(option_value)
|
164
|
+
option_value = true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Store option
|
169
|
+
options[definition[:long].to_sym] = option_value
|
170
|
+
else
|
171
|
+
# Store option
|
172
|
+
options[definition[:long].to_sym] = true
|
173
|
+
end
|
174
|
+
end
|
175
|
+
# Handle normal arguments
|
176
|
+
else
|
177
|
+
arguments << e
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
{ :options => options, :arguments => arguments }
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Copyright (c) 2010-2011 David Love
|
2
|
+
#
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
5
|
+
# above copyright notice and this permission notice appear in all copies.
|
6
|
+
#
|
7
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
14
|
+
#
|
15
|
+
|
16
|
+
# @author David Love
|
17
|
+
|
18
|
+
require "uuidtools"
|
19
|
+
|
20
|
+
# Extendes the {UUIDTools::UUID} class, adding the ability to
|
21
|
+
# generate sequential UUID's
|
22
|
+
class UUIDTools::UUID
|
23
|
+
|
24
|
+
# Increments the internal UUID representation, returning a
|
25
|
+
# new UUID that is different from, but greater, than the
|
26
|
+
# current sequence number
|
27
|
+
def next
|
28
|
+
next_uuid = self.to_i
|
29
|
+
next_uuid += 1
|
30
|
+
|
31
|
+
# Return the newly created UUID
|
32
|
+
return UUIDTools::UUID::parse_int(next_uuid)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# Copyright (c) 2010-2011 David Love
|
2
|
+
#
|
3
|
+
# Permission to use, copy, modify, and/or distribute this software for
|
4
|
+
# any purpose with or without fee is hereby granted, provided that the
|
5
|
+
# above copyright notice and this permission notice appear in all copies.
|
6
|
+
#
|
7
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
8
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
9
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
10
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
11
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
12
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
13
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
14
|
+
#
|
15
|
+
|
16
|
+
# @author David Love
|
17
|
+
|
18
|
+
# Defines a class capable of creating iSCSI shares using the new
|
19
|
+
# COMStar framework. This classes uses the various command line tools,
|
20
|
+
# not the C API.
|
21
|
+
class COMStar
|
22
|
+
|
23
|
+
# Create a new, ready to use, iSCSI target. Functionally this is command
|
24
|
+
# is equivalent to the old "shareiscsi=on" ZFS volume property.
|
25
|
+
def self.new_target(volume_path, volume_guid)
|
26
|
+
|
27
|
+
# Create a new disk target for the ZFS volume
|
28
|
+
SANStore::CLI::Logger.instance.log_level(:low, :create, "New SCSI block device at /dev/zvol/rdsk/#{volume_path}")
|
29
|
+
disk_target = %x[sbdadm create-lu /dev/zvol/rdsk/#{volume_path}]
|
30
|
+
|
31
|
+
# Get the ID of the new disk target from the output of the
|
32
|
+
# target creation command
|
33
|
+
id = disk_target.split(/$/)[4].split[0]
|
34
|
+
SANStore::CLI::Logger.instance.log_level(:low, :info, "Using #{id} as the logical unit identifier")
|
35
|
+
|
36
|
+
# Modify the just created logical unit to include the path of the
|
37
|
+
# volume backing it. This makes it much easier to get rid of the
|
38
|
+
# relevant volume later
|
39
|
+
SANStore::CLI::Logger.instance.log_level(:low, :update, "Storing the volume GUID as the logical unit alias")
|
40
|
+
modify_lu = %x[stmfadm modify-lu --lu-prop alias=#{volume_guid} #{id}]
|
41
|
+
|
42
|
+
# Link the new disk target to the iSCSI framework
|
43
|
+
SANStore::CLI::Logger.instance.log_level(:low, :update, "Attaching logical unit #{id} into the iSCSI framework")
|
44
|
+
vol_frame = %x[stmfadm add-view #{id}]
|
45
|
+
|
46
|
+
# Create the target...
|
47
|
+
SANStore::CLI::Logger.instance.log_level(:low, :create, "iSCSI block target")
|
48
|
+
target = %x[itadm create-target]
|
49
|
+
target_name = target.split[1]
|
50
|
+
|
51
|
+
# Store the volume GUID as the alias so we can find it later
|
52
|
+
SANStore::CLI::Logger.instance.log_level(:low, :update, "Storing the volume GUID as the iSCSI target alias")
|
53
|
+
%x[itadm modify-target --alias #{volume_guid} #{target_name}]
|
54
|
+
|
55
|
+
# Return the target name to the caller
|
56
|
+
return target_name
|
57
|
+
end
|
58
|
+
|
59
|
+
# Delete an iSCSI target. This returns the name of the underlying ZFS store in case the
|
60
|
+
# caller wants to delete that as well
|
61
|
+
def self.delete_target(target_name)
|
62
|
+
|
63
|
+
# Before we kill the target, get the store GUID so that we can clean up properly
|
64
|
+
target_map = self.TargetToGUIDMap
|
65
|
+
target_guid = target_map[target_name]
|
66
|
+
|
67
|
+
# Get the maps of the LU identifers to the underlying stores
|
68
|
+
vol_map = self.LUToVolMap
|
69
|
+
|
70
|
+
# Look for the store with the GUID of the target we want to delete
|
71
|
+
SANStore::CLI::Logger.instance.log_level(:low, :info, "Looking for the name of the volume #{target_guid} backing #{target_name}")
|
72
|
+
vol_name = ""
|
73
|
+
vol_store = ""
|
74
|
+
vol_map.each{|key, value|
|
75
|
+
if value[:guid] == target_guid then
|
76
|
+
# We have found the right entry, so update the name of the
|
77
|
+
# volume store and the name of the volume backing it
|
78
|
+
vol_name = key
|
79
|
+
vol_store = value[:data_file]
|
80
|
+
break
|
81
|
+
end
|
82
|
+
}
|
83
|
+
|
84
|
+
# Abort if we can't find what we are looking for
|
85
|
+
if vol_name.empty? or vol_store.empty? then
|
86
|
+
SANStore::CLI::Logger.instance.log_level(:high, :error, "Could not delete the file system for the iSCSI target. The target has been removed, but the file system is still around!")
|
87
|
+
return ""
|
88
|
+
end
|
89
|
+
|
90
|
+
# Delete the target (and force any clients off the soon to die share)
|
91
|
+
SANStore::CLI::Logger.instance.log_level(:low, :warning, "Closing all sessions for #{target_name}")
|
92
|
+
target = %x[itadm delete-target -f #{target_name}]
|
93
|
+
|
94
|
+
# Now unlink the SCSI logical unit, so that we can free the underlying ZFS volume
|
95
|
+
SANStore::CLI::Logger.instance.log_level(:low, :delete, "Removing logical units associated with the deleted target")
|
96
|
+
disk_target = %x[sbdadm delete-lu #{vol_name}]
|
97
|
+
|
98
|
+
# The file store is now ready for deletion. We will tell the caller what to remove, but we
|
99
|
+
# don't actually do this ourselves (file systems are someone elses problem)
|
100
|
+
return vol_store
|
101
|
+
end
|
102
|
+
|
103
|
+
# List the current iSCSI targets defined on this host
|
104
|
+
def self.list_vols
|
105
|
+
raw_list = %x[itadm list-target]
|
106
|
+
|
107
|
+
# Create a hash for the final list of targets
|
108
|
+
target_list = Array.new
|
109
|
+
|
110
|
+
# Run through the raw list of targets
|
111
|
+
target_array = raw_list.split(/$/)
|
112
|
+
target_array.delete_at(0)
|
113
|
+
|
114
|
+
target_array.each{|row|
|
115
|
+
row_fragments = row.split
|
116
|
+
row_hash = Hash.new
|
117
|
+
|
118
|
+
row_hash[:name] = row_fragments[0]
|
119
|
+
row_hash[:state] = row_fragments[1]
|
120
|
+
row_hash[:sessions] = row_fragments[2]
|
121
|
+
|
122
|
+
target_list << row_hash
|
123
|
+
}
|
124
|
+
|
125
|
+
# return the list to the caller
|
126
|
+
return target_list
|
127
|
+
end
|
128
|
+
|
129
|
+
# Walks over the list of iSCSI targets, looking for the store GUID (stored in the target alias field).
|
130
|
+
# Returns a map of the target names and associated aliases
|
131
|
+
def self.TargetToGUIDMap
|
132
|
+
|
133
|
+
# Get the raw map
|
134
|
+
SANStore::CLI::Logger.instance.log_level(:low, :info, "Finding the GUID's associated with iSCSI targets")
|
135
|
+
raw_list = %x[stmfadm list-target -v]
|
136
|
+
raw_map = raw_list.split(/$/)
|
137
|
+
|
138
|
+
# Create a hash from this map
|
139
|
+
map_hash = Hash.new
|
140
|
+
map_index = nil
|
141
|
+
map_entry = nil
|
142
|
+
raw_index = 0
|
143
|
+
|
144
|
+
while raw_index < raw_map.length do
|
145
|
+
# Is this line the start of a new target entry
|
146
|
+
if raw_map[raw_index].index(/Target\:/) then
|
147
|
+
|
148
|
+
# Store the old entry
|
149
|
+
unless map_entry.nil? then
|
150
|
+
map_hash[map_index] = map_entry
|
151
|
+
end
|
152
|
+
|
153
|
+
# Create a new map entry
|
154
|
+
map_entry = String.new
|
155
|
+
map_index = raw_map[raw_index].partition(/Target\:/)[2].strip
|
156
|
+
|
157
|
+
else
|
158
|
+
|
159
|
+
# Split the line to find the key and value
|
160
|
+
entry = raw_map[raw_index].partition(/\s?\:\s/)
|
161
|
+
|
162
|
+
case entry[0].strip
|
163
|
+
when "Alias"
|
164
|
+
map_entry = entry[2].strip
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
raw_index += 1
|
170
|
+
end
|
171
|
+
|
172
|
+
# Add the last entry
|
173
|
+
unless map_entry.nil? then
|
174
|
+
map_hash[map_index] = map_entry
|
175
|
+
end
|
176
|
+
|
177
|
+
# Return the hash map
|
178
|
+
return map_hash
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
# Walks over the list of Logical Units, working out where the ZFS volume
|
183
|
+
# backing the LU is. Returns a hash, giving the correct backing store for
|
184
|
+
# each LU
|
185
|
+
def self.LUToVolMap
|
186
|
+
|
187
|
+
# Get the raw map
|
188
|
+
SANStore::CLI::Logger.instance.log_level(:low, :info, "Finding the backing stores for the logical units")
|
189
|
+
raw_list = %x[stmfadm list-lu -v]
|
190
|
+
raw_map = raw_list.split(/$/)
|
191
|
+
|
192
|
+
# Create a hash from this map
|
193
|
+
map_hash = Hash.new
|
194
|
+
map_index = nil
|
195
|
+
map_entry = nil
|
196
|
+
raw_index = 0
|
197
|
+
|
198
|
+
while raw_index < raw_map.length do
|
199
|
+
# Is this line the start of a new LU entry
|
200
|
+
if raw_map[raw_index].index(/LU Name\:/) then
|
201
|
+
|
202
|
+
# Store the old entry
|
203
|
+
unless map_entry.nil? then
|
204
|
+
map_hash[map_index] = map_entry
|
205
|
+
end
|
206
|
+
|
207
|
+
# Create a new map entry
|
208
|
+
map_entry = Hash.new
|
209
|
+
map_index = raw_map[raw_index].partition(/LU Name\:/)[2].strip
|
210
|
+
|
211
|
+
else
|
212
|
+
|
213
|
+
# Split the line to find the key and value
|
214
|
+
entry = raw_map[raw_index].partition(/\s?\:\s/)
|
215
|
+
|
216
|
+
case entry[0].strip
|
217
|
+
when "Alias"
|
218
|
+
map_entry[:guid] = entry[2].strip
|
219
|
+
when "Data File"
|
220
|
+
map_entry[:data_file] = entry[2].strip
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
raw_index += 1
|
226
|
+
end
|
227
|
+
|
228
|
+
# Add the last entry
|
229
|
+
unless map_entry.nil? then
|
230
|
+
map_hash[map_index] = map_entry
|
231
|
+
end
|
232
|
+
|
233
|
+
# Return the hash map
|
234
|
+
return map_hash
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|