hammer_cli_csv 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,25 +15,35 @@ module HammerCLICsv
15
15
  command_name 'provisioning-templates'
16
16
  desc 'import or export provisioning templates'
17
17
 
18
+ option %w(--organization), 'ORGANIZATION', 'Only process organization matching this name'
19
+
18
20
  ORGANIZATIONS = 'Organizations'
19
21
  LOCATIONS = 'Locations'
22
+ OPERATINGSYSTEMS = 'Operating Systems'
23
+ ASSOCIATIONS = 'Host Group / Puppet Environment Combinations'
20
24
  KIND = 'Kind'
21
25
  TEMPLATE = 'Template'
22
26
 
23
27
  def export
24
28
  CSV.open(option_csv_file || '/dev/stdout', 'wb', {:force_quotes => true}) do |csv|
25
- csv << [NAME, COUNT, ORGANIZATIONS, LOCATIONS, KIND, TEMPLATE]
29
+ csv << [NAME, COUNT, ORGANIZATIONS, LOCATIONS, OPERATINGSYSTEMS, ASSOCIATIONS, KIND, TEMPLATE]
26
30
  @api.resource(:config_templates).call(:index, {
27
31
  :per_page => 999999
28
32
  })['results'].each do |template_id|
29
33
  template = @api.resource(:config_templates).call(:show, {:id => template_id['id']})
34
+ next if template['locked']
35
+ next unless option_organization.nil? || template['organizations'].detect { |org| org['name'] == option_organization }
30
36
  name = template['name']
31
37
  count = 1
32
38
  kind = template['snippet'] ? 'snippet' : template['template_kind_name']
33
39
  organizations = export_column(template, 'organizations', 'name')
34
40
  locations = export_column(template, 'locations', 'name')
41
+ operatingsystems = export_column(template, 'operatingsystems', 'fullname')
42
+ # TODO: puppet environments for content views are not present in api
43
+ # http://projects.theforeman.org/issues/10293
44
+ associations = export_associations(template)
35
45
  unless name == 'Boot disk iPXE - generic host' || name == 'Boot disk iPXE - host'
36
- csv << [name, count, organizations, locations, kind, template['template']]
46
+ csv << [name, count, organizations, locations, operatingsystems, associations, kind, template['template']]
37
47
  end
38
48
  end
39
49
  end
@@ -56,81 +66,103 @@ module HammerCLICsv
56
66
  organizations = collect_column(line[ORGANIZATIONS]) do |organization|
57
67
  foreman_organization(:name => organization)
58
68
  end
69
+ if option_organization
70
+ org_id = foreman_organization(:name => option_organization)
71
+ return if org_id.nil? || !organizations.include?(org_id)
72
+ organizations = [org_id]
73
+ end
59
74
  locations = collect_column(line[LOCATIONS]) do |location|
60
75
  foreman_location(:name => location)
61
76
  end
77
+ operatingsystems = collect_column(line[OPERATINGSYSTEMS]) do |operatingsystem|
78
+ foreman_operatingsystem(:name => operatingsystem)
79
+ end
62
80
 
63
81
  line[COUNT].to_i.times do |number|
64
82
  name = namify(line[NAME], number)
65
83
  if !@existing.include? name
66
- print "Creating provisioning template '#{name}'..." if option_verbose?
84
+ print _("Creating provisioning template '%{name}'...") % {:name => name } if option_verbose?
67
85
  template_id = @api.resource(:config_templates).call(:create, {
68
- 'name' => name,
69
- 'snippet' => line[KIND] == 'snippet',
70
- 'template_kind_id' => line[KIND] == 'snippet' ? nil : foreman_template_kind(:name => line[KIND]),
71
- 'organization_ids' => organizations,
72
- 'location_ids' => locations,
73
- 'template' => line[TEMPLATE]
86
+ 'config_template' => {
87
+ 'name' => name,
88
+ 'snippet' => line[KIND] == 'snippet',
89
+ 'template_kind_id' => line[KIND] == 'snippet' ? nil : foreman_template_kind(:name => line[KIND]),
90
+ 'operatingsystem_ids' => operatingsystems,
91
+ 'location_ids' => locations,
92
+ 'template' => line[TEMPLATE]
93
+ }
74
94
  })['id']
75
95
  else
76
- print "Updating provisioning template '#{name}'..." if option_verbose?
96
+ print _("Updating provisioning template '%{name}'...") % {:name => name} if option_verbose?
77
97
  template_id = @api.resource(:config_templates).call(:update, {
78
98
  'id' => @existing[name],
79
- 'name' => name,
80
- 'snippet' => line[KIND] == 'snippet',
81
- 'template_kind_id' => line[KIND] == 'snippet' ? nil : foreman_template_kind(:name => line[KIND]),
82
- 'organization_ids' => organizations,
83
- 'location_ids' => locations,
84
- 'template' => line[TEMPLATE]
99
+ 'config_template' => {
100
+ 'name' => name,
101
+ 'snippet' => line[KIND] == 'snippet',
102
+ 'template_kind_id' => line[KIND] == 'snippet' ? nil : foreman_template_kind(:name => line[KIND]),
103
+ 'operatingsystem_ids' => operatingsystems,
104
+ 'location_ids' => locations,
105
+ 'template' => line[TEMPLATE]
106
+ }
85
107
  })['id']
86
108
  end
87
109
  @existing[name] = template_id
88
110
 
89
111
  # Update associated resources
90
- template_organizations ||= {}
112
+ @template_organizations ||= {}
91
113
  organizations.each do |organization_id|
92
- if template_organizations[organization_id].nil?
93
- template_organizations[organization_id] = @api.resource(:organizations).call(:show, {
114
+ if @template_organizations[organization_id].nil?
115
+ @template_organizations[organization_id] = @api.resource(:organizations).call(:show, {
94
116
  'id' => organization_id
95
117
  })['config_templates'].collect do |template|
96
118
  template['id']
97
119
  end
98
120
  end
99
- if !template_organizations[organization_id].include? template_id
100
- template_organizations[organization_id] += [template_id]
121
+ if !@template_organizations[organization_id].include? template_id
122
+ @template_organizations[organization_id] << template_id
101
123
  @api.resource(:organizations).call(:update, {
102
124
  'id' => organization_id,
103
125
  'organization' => {
104
- 'config_template_ids' => template_organizations[organization_id]
126
+ 'config_template_ids' => @template_organizations[organization_id]
105
127
  }
106
128
  })
107
129
  end
108
130
  end
109
- template_locations ||= {}
131
+ @template_locations ||= {}
110
132
  locations.each do |location_id|
111
- if template_locations[location_id].nil?
112
- template_locations[location_id] = @api.resource(:locations).call(:show, {
133
+ if @template_locations[location_id].nil?
134
+ @template_locations[location_id] = @api.resource(:locations).call(:show, {
113
135
  'id' => location_id
114
136
  })['config_templates'].collect do |template|
115
137
  template['id']
116
138
  end
117
139
  end
118
- if !template_locations[location_id].include? template_id
119
- template_locations[location_id] += [template_id]
140
+ if !@template_locations[location_id].include? template_id
141
+ @template_locations[location_id] += [template_id]
120
142
  @api.resource(:locations).call(:update, {
121
143
  'id' => location_id,
122
144
  'location' => {
123
- 'config_template_ids' => template_locations[location_id]
145
+ 'config_template_ids' => @template_locations[location_id]
124
146
  }
125
147
  })
126
148
  end
127
149
  end
128
150
 
129
- print "done\n" if option_verbose?
151
+ puts _('done') if option_verbose?
130
152
  end
131
153
  rescue RuntimeError => e
132
154
  raise "#{e}\n #{line[NAME]}"
133
155
  end
156
+
157
+ def export_associations(template)
158
+ return '' unless template['template_combinations']
159
+ values = CSV.generate do |column|
160
+ column << template['template_combinations'].collect do |combo|
161
+ "#{combo['hostgroup_name']}|#{combo['environment_name']}"
162
+ end
163
+ end
164
+ values.delete!("\n")
165
+ end
134
166
  end
135
167
  end
136
168
  end
@@ -84,9 +84,7 @@ module HammerCLICsv
84
84
  @existing_roles[name] = role['id']
85
85
  else
86
86
  print "Updating role '#{name}'..." if option_verbose?
87
- @api.resource(:roles).call(:update, {
88
- 'id' => @existing_roles[name]
89
- })
87
+ # Nothing to update on the role object itself, just filters below
90
88
  end
91
89
 
92
90
  filter_id = foreman_filter(name, line[RESOURCE], search)
@@ -104,11 +102,13 @@ module HammerCLICsv
104
102
  print " updating filter #{line[RESOURCE]}..."
105
103
  @api.resource(:filters).call(:update, {
106
104
  'id' => filter_id,
107
- 'search' => search,
108
- 'unlimited' => search.nil? || search.empty?,
109
- 'organization_ids' => organizations,
110
- 'location_ids' => locations,
111
- 'permission_ids' => permissions
105
+ 'filter' => {
106
+ 'search' => search,
107
+ 'unlimited' => search.nil? || search.empty?,
108
+ 'organization_ids' => organizations,
109
+ 'location_ids' => locations,
110
+ 'permission_ids' => permissions
111
+ }
112
112
  })
113
113
  end
114
114
 
@@ -0,0 +1,376 @@
1
+ # Copyright 2013-2014 Red Hat, Inc.
2
+ #
3
+ # This software is licensed to you under the GNU General Public
4
+ # License as published by the Free Software Foundation; either version
5
+ # 2 of the License (GPLv2) or (at your option) any later version.
6
+ # There is NO WARRANTY for this software, express or implied,
7
+ # including the implied warranties of MERCHANTABILITY,
8
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
9
+ # have received a copy of GPLv2 along with this software; if not, see
10
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
11
+
12
+ require 'openssl'
13
+ require 'date'
14
+
15
+ module HammerCLICsv
16
+ class CsvCommand
17
+ class SpliceCommand < BaseCommand
18
+ command_name 'splice'
19
+ desc 'import Satellite-5 splice data'
20
+
21
+ option %w(--organization), 'ORGANIZATION', 'Only process organization matching this name'
22
+ option %w(--dir), 'DIR',
23
+ 'Directory of Splice exported CSV files (default pwd)'
24
+ option %w(--mapping-dir), 'DIR',
25
+ 'Directory of Splice product mapping files (default /usr/share/rhsm/product/RHEL-6)'
26
+
27
+ UUID = 'server_id'
28
+ ORGANIZATION = 'organization'
29
+ ORGANIZATION_ID = 'org_id'
30
+ NAME = 'name'
31
+ HOSTNAME = 'hostname'
32
+ IP_ADDRESS = 'ip_address'
33
+ IPV6_ADDRESS = 'ipv6_address'
34
+ REGISTERED_BY = 'registered_by'
35
+ REGISTRATION_TIME = 'registration_time'
36
+ LAST_CHECKIN_TIME = 'last_checkin_time'
37
+ PRODUCTS = 'software_channel'
38
+ ENTITLEMENTS = 'entitlements'
39
+ HOSTCOLLECTIONS = 'system_group'
40
+ VIRTUAL_HOST = 'virtual_host'
41
+ ARCHITECTURE = 'architecture'
42
+ HARDWARE = 'hardware'
43
+ MEMORY = 'memory'
44
+ SOCKETS = 'sockets'
45
+ IS_VIRTUALIZED = 'is_virtualized'
46
+
47
+ def import
48
+ @existing = {}
49
+ load_product_mapping
50
+ preload_host_guests
51
+
52
+ filename = option_dir + '/splice-export'
53
+ thread_import(false, filename, NAME) do |line|
54
+ create_content_hosts_from_csv(line) unless line[UUID][0] == '#'
55
+ end
56
+
57
+ update_host_guests
58
+ delete_unfound_hosts(@existing)
59
+ end
60
+
61
+ def create_content_hosts_from_csv(line)
62
+ return if option_organization && line[ORGANIZATION] != option_organization
63
+
64
+ if !@existing[line[ORGANIZATION]]
65
+ create_organization(line)
66
+ @existing[line[ORGANIZATION]] = {}
67
+
68
+ # Fetching all content hosts is too slow and times out due to the complexity of the data
69
+ # rendered in the json.
70
+ # http://projects.theforeman.org/issues/6307
71
+ total = @api.resource(:systems).call(:index, {
72
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
73
+ 'per_page' => 1
74
+ })['total'].to_i
75
+ (total / 20 + 2).to_i.times do |page|
76
+ @api.resource(:systems).call(:index, {
77
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
78
+ 'page' => page,
79
+ 'per_page' => 20
80
+ })['results'].each do |host|
81
+ @existing[line[ORGANIZATION]][host['name']] = host['uuid'] if host
82
+ end
83
+ end
84
+ end
85
+
86
+ name = "#{line[NAME]}-#{line[UUID]}"
87
+ #checkin_time = Time.parse(line[LAST_CHECKIN_TIME]).strftime("%a, %d %b %Y %H:%M:%S %z")
88
+ checkin_time = if line[LAST_CHECKIN_TIME].casecmp('now').zero?
89
+ DateTime.now.strftime("%a, %d %b %Y %H:%M:%S %z")
90
+ else
91
+ DateTime.parse(line[LAST_CHECKIN_TIME]).strftime("%a, %d %b %Y %H:%M:%S %z")
92
+ end
93
+
94
+ if !@existing[line[ORGANIZATION]].include? name
95
+ print(_("Creating content host '%{name}'...") % {:name => name}) if option_verbose?
96
+ host_id = @api.resource(:systems).call(:create, {
97
+ 'name' => name,
98
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
99
+ 'environment_id' => lifecycle_environment(line[ORGANIZATION], :name => 'Library'),
100
+ 'content_view_id' => katello_contentview(line[ORGANIZATION], :name => 'Default Organization View'),
101
+ 'last_checkin' => checkin_time,
102
+ 'facts' => facts(name, line),
103
+ 'installed_products' => products(line),
104
+ 'type' => 'system'
105
+ })['uuid']
106
+
107
+ # last_checkin is not updated in candlepin on creation
108
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1212122
109
+ @api.resource(:systems).call(:update, {
110
+ 'id' => host_id,
111
+ 'system' => {
112
+ 'last_checkin' => checkin_time
113
+ },
114
+ 'last_checkin' => checkin_time
115
+ })
116
+
117
+ else
118
+ print(_("Updating content host '%{name}'...") % {:name => name}) if option_verbose?
119
+ host_id = @api.resource(:systems).call(:update, {
120
+ 'id' => @existing[line[ORGANIZATION]][name],
121
+ 'system' => {
122
+ 'name' => name,
123
+ 'environment_id' => lifecycle_environment(line[ORGANIZATION], :name => 'Library'),
124
+ 'content_view_id' => katello_contentview(line[ORGANIZATION], :name => 'Default Organization View'),
125
+ 'last_checkin' => checkin_time,
126
+ 'facts' => facts(name, line),
127
+ 'installed_products' => products(line)
128
+ },
129
+ 'installed_products' => products(line), # TODO: http://projects.theforeman.org/issues/9191
130
+ 'last_checkin' => checkin_time
131
+ })['uuid']
132
+
133
+ @existing[line[ORGANIZATION]].delete(name) # Remove to indicate found
134
+ end
135
+
136
+ if @hosts.include? line[UUID]
137
+ @hosts[line[UUID]] = host_id
138
+ elsif @guests.include? line[UUID]
139
+ @guests[line[UUID]] = "#{line[ORGANIZATION]}/#{name}"
140
+ end
141
+
142
+ update_host_collections(host_id, line)
143
+
144
+ puts _('done') if option_verbose?
145
+ rescue RuntimeError => e
146
+ raise "#{e}\n #{line}"
147
+ end
148
+
149
+ private
150
+
151
+ def facts(name, line)
152
+ facts = {}
153
+ facts['system.certificate_version'] = '3.2' # Required for auto-attach to work
154
+ facts['network.hostname'] = line[NAME]
155
+ facts['network.ipv4_address'] = line[IP_ADDRESS]
156
+ facts['network.ipv6_address'] = line[IPV6_ADDRESS]
157
+ facts['memory.memtotal'] = line[MEMORY]
158
+ facts['uname.machine'] = line[ARCHITECTURE]
159
+ facts['virt.is_guest'] = line[IS_VIRTUALIZED] == 'Yes' ? true : false
160
+ facts['virt.uuid'] = "#{line[ORGANIZATION]}/#{name}" if facts['virt.is_guest']
161
+
162
+ # 1 CPUs 1 Sockets; eth0 10.11....
163
+ hardware = line[HARDWARE].split(' ')
164
+ if hardware[1] == 'CPUs'
165
+ facts['cpu.cpu(s)'] = hardware[0] unless hardware[0] == 'unknown'
166
+ facts['cpu.cpu_socket(s)'] = hardware[2] unless hardware[0] == 'unknown'
167
+ # facts['cpu.core(s)_per_socket'] Not present in data
168
+ end
169
+
170
+ facts
171
+ end
172
+
173
+ def update_host_collections(host_id, line)
174
+ return nil if !line[HOSTCOLLECTIONS]
175
+
176
+ @existing_hostcollections ||= {}
177
+ if @existing_hostcollections[line[ORGANIZATION]].nil?
178
+ @existing_hostcollections[line[ORGANIZATION]] = {}
179
+ @api.resource(:host_collections).call(:index, {
180
+ :per_page => 999999,
181
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION])
182
+ })['results'].each do |hostcollection|
183
+ @existing_hostcollections[line[ORGANIZATION]][hostcollection['name']] = hostcollection['id']
184
+ end
185
+ end
186
+
187
+ CSV.parse_line(line[HOSTCOLLECTIONS], {:col_sep => ';'}).each do |hostcollection_name|
188
+ if @existing_hostcollections[line[ORGANIZATION]][hostcollection_name].nil?
189
+ hostcollection_id = @api.resource(:host_collections).call(:create, {
190
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
191
+ 'name' => hostcollection_name,
192
+ 'unlimited_content_hosts' => true,
193
+ 'max_content_hosts' => nil
194
+ })['id']
195
+ @existing_hostcollections[line[ORGANIZATION]][hostcollection_name] = hostcollection_id
196
+ end
197
+
198
+ @api.resource(:host_collections).call(:add_systems, {
199
+ 'id' => @existing_hostcollections[line[ORGANIZATION]][hostcollection_name],
200
+ 'system_ids' => [host_id]
201
+ })
202
+ end
203
+ end
204
+
205
+ def products(line)
206
+ return nil if !line[PRODUCTS]
207
+ products = CSV.parse_line(line[PRODUCTS], {:col_sep => ';'}).collect do |channel|
208
+ product = @product_mapping[channel]
209
+ if product.nil?
210
+ # puts _("WARNING: No product found for channel '%{name}'") % {:name => channel}
211
+ next
212
+ end
213
+ product
214
+ end
215
+ products.compact
216
+ end
217
+
218
+ def preload_host_guests
219
+ @hosts = {}
220
+ @guests = {}
221
+ return unless option_dir && File.exists?(option_dir + "/host-guests")
222
+ host_guest_file = option_dir + "/host-guests"
223
+
224
+ CSV.foreach(host_guest_file, {
225
+ :skip_blanks => true,
226
+ :headers => :first_row,
227
+ :return_headers => false
228
+ }) do |line|
229
+ @hosts[line['server_id']] = nil
230
+ CSV.parse_line(line['guests'], {:col_sep => ';'}).each do |guest|
231
+ @guests[guest] = nil
232
+ end
233
+ end
234
+ end
235
+
236
+ def update_host_guests
237
+ return unless option_dir && File.exists?(option_dir + "/host-guests")
238
+ return if @hosts.empty?
239
+ host_guest_file = option_dir + "/host-guests"
240
+
241
+ print _('Updating hypervisor and guest associations...') if option_verbose?
242
+
243
+ CSV.foreach(host_guest_file, {
244
+ :skip_blanks => true,
245
+ :headers => :first_row,
246
+ :return_headers => false
247
+ }) do |line|
248
+ host_id = @hosts[line['server_id']]
249
+ next if host_id.nil?
250
+ guest_ids = CSV.parse_line(line['guests'], {:col_sep => ';'}).collect do |guest|
251
+ @guests[guest]
252
+ end
253
+
254
+ @api.resource(:systems).call(:update, {
255
+ 'id' => host_id,
256
+ 'guest_ids' => guest_ids
257
+ })
258
+ end
259
+
260
+ puts _("done") if option_verbose?
261
+ end
262
+
263
+ def update_subscriptions(host_id, line)
264
+ existing_subscriptions = @api.resource(:subscriptions).call(:index, {
265
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
266
+ 'per_page' => 999999,
267
+ 'system_id' => host_id
268
+ })['results']
269
+ if existing_subscriptions.length > 0
270
+ @api.resource(:subscriptions).call(:destroy, {
271
+ 'system_id' => host_id,
272
+ 'id' => existing_subscriptions[0]['id']
273
+ })
274
+ end
275
+
276
+ return if line[SUBSCRIPTIONS].nil? || line[SUBSCRIPTIONS].empty?
277
+
278
+ subscriptions = CSV.parse_line(line[SUBSCRIPTIONS], {:skip_blanks => true}).collect do |details|
279
+ (amount, sku, name) = details.split('|')
280
+ {
281
+ :id => katello_subscription(line[ORGANIZATION], :name => name),
282
+ :quantity => (amount.nil? || amount.empty? || amount == 'Automatic') ? 0 : amount.to_i
283
+ }
284
+ end
285
+
286
+ @api.resource(:subscriptions).call(:create, {
287
+ 'system_id' => host_id,
288
+ 'subscriptions' => subscriptions
289
+ })
290
+ end
291
+
292
+ def create_organization(line)
293
+ if !@existing_organizations
294
+ @existing_organizations = {}
295
+ @api.resource(:organizations).call(:index, {
296
+ :per_page => 999999
297
+ })['results'].each do |organization|
298
+ @existing_organizations[organization['name']] = organization['id'] if organization
299
+ end
300
+ end
301
+
302
+ if !@existing_organizations[line[ORGANIZATION]]
303
+ print _("Creating organization '%{name}'... ") % {:name => line[ORGANIZATION]} if option_verbose?
304
+ @api.resource(:organizations).call(:create, {
305
+ 'name' => line[ORGANIZATION],
306
+ 'organization' => {
307
+ 'name' => line[ORGANIZATION],
308
+ 'label' => "splice-#{line[ORGANIZATION_ID]}",
309
+ 'description' => _('Satellite-5 Splice')
310
+ }
311
+ })
312
+ puts _('done')
313
+ end
314
+ end
315
+
316
+ def delete_unfound_hosts(hosts)
317
+ hosts.keys.each do |organization|
318
+ hosts[organization].values.each do |host_id|
319
+ print _("Deleting content host with id '%{id}'...") % {:id => host_id}
320
+ @api.resource(:systems).call(:destroy, {:id => host_id})
321
+ puts _('done')
322
+ end
323
+ end
324
+ end
325
+
326
+
327
+ def load_product_mapping
328
+ @product_mapping = {}
329
+
330
+ mapping_dir = (option_mapping_dir || '/usr/share/rhsm/product/RHEL-6')
331
+ File.open(mapping_dir + '/channel-cert-mapping.txt', 'r') do |file|
332
+ file.each_line do |line|
333
+ # '<product name>: <file name>\n'
334
+ (product_name, file_name) = line.split(':')
335
+ @product_mapping[product_name] = {:file => "#{mapping_dir}/#{file_name[1..-2]}"}
336
+ OpenSSL::X509::Certificate.new(File.read(@product_mapping[product_name][:file])).extensions.each do |extension|
337
+ if extension.oid.start_with?("1.3.6.1.4.1.2312.9.1.")
338
+ oid_parts = extension.oid.split('.')
339
+ @product_mapping[product_name][:productId] = oid_parts[-2].to_i
340
+ case oid_parts[-1]
341
+ when /1/
342
+ @product_mapping[product_name][:productName] = extension.value[2..-1] #.sub(/\A\.+/,'')
343
+ when /2/
344
+ @product_mapping[product_name][:version] = extension.value[2..-1] #.sub(/\A\.+/,'')
345
+ when /3/
346
+ @product_mapping[product_name][:arch] = extension.value[2..-1] #.sub(/\A\.+/,'')
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
352
+
353
+ channel_file = option_dir + '/cloned-channels'
354
+ return unless File.exists? channel_file
355
+ unmatched_channels = []
356
+ CSV.foreach(channel_file, {
357
+ :skip_blanks => true,
358
+ :headers => :first_row,
359
+ :return_headers => false
360
+ }) do |line|
361
+ if @product_mapping[line['original_channel_label']]
362
+ @product_mapping[line['new_channel_label']] = @product_mapping[line['original_channel_label']]
363
+ else
364
+ unmatched_channels << line
365
+ end
366
+ end
367
+
368
+ # Second pass through
369
+ unmatched_channels.each do |line|
370
+ next if @product_mapping[line['original_channel_label']].nil?
371
+ @product_mapping[line['new_channel_label']] = @product_mapping[line['original_channel_label']]
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end