hammer_cli_csv 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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