hammer_cli_csv 2.0.0 → 2.1.0

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 (87) hide show
  1. checksums.yaml +5 -13
  2. data/config/cli_config.yml +9 -24
  3. data/lib/hammer_cli_csv/activation_keys.rb +220 -95
  4. data/lib/hammer_cli_csv/architectures.rb +7 -11
  5. data/lib/hammer_cli_csv/base.rb +67 -41
  6. data/lib/hammer_cli_csv/compute_profiles.rb +11 -15
  7. data/lib/hammer_cli_csv/compute_resources.rb +11 -15
  8. data/lib/hammer_cli_csv/containers.rb +10 -12
  9. data/lib/hammer_cli_csv/content_hosts.rb +285 -203
  10. data/lib/hammer_cli_csv/content_view_filters.rb +32 -36
  11. data/lib/hammer_cli_csv/content_views.rb +33 -37
  12. data/lib/hammer_cli_csv/csv.rb +17 -1
  13. data/lib/hammer_cli_csv/domains.rb +15 -19
  14. data/lib/hammer_cli_csv/export.rb +13 -5
  15. data/lib/hammer_cli_csv/host_collections.rb +20 -22
  16. data/lib/hammer_cli_csv/host_groups.rb +73 -65
  17. data/lib/hammer_cli_csv/hosts.rb +30 -34
  18. data/lib/hammer_cli_csv/import.rb +24 -10
  19. data/lib/hammer_cli_csv/installation_media.rb +74 -0
  20. data/lib/hammer_cli_csv/job_templates.rb +37 -41
  21. data/lib/hammer_cli_csv/lifecycle_environments.rb +15 -19
  22. data/lib/hammer_cli_csv/locations.rb +4 -6
  23. data/lib/hammer_cli_csv/operating_systems.rb +17 -21
  24. data/lib/hammer_cli_csv/organizations.rb +11 -13
  25. data/lib/hammer_cli_csv/partition_tables.rb +17 -21
  26. data/lib/hammer_cli_csv/products.rb +200 -92
  27. data/lib/hammer_cli_csv/provisioning_templates.rb +19 -23
  28. data/lib/hammer_cli_csv/puppet_environments.rb +8 -12
  29. data/lib/hammer_cli_csv/puppet_facts.rb +20 -24
  30. data/lib/hammer_cli_csv/puppet_reports.rb +47 -51
  31. data/lib/hammer_cli_csv/reports.rb +6 -10
  32. data/lib/hammer_cli_csv/roles.rb +12 -16
  33. data/lib/hammer_cli_csv/settings.rb +8 -6
  34. data/lib/hammer_cli_csv/smart_proxies.rb +9 -13
  35. data/lib/hammer_cli_csv/splice.rb +1 -3
  36. data/lib/hammer_cli_csv/subnets.rb +24 -28
  37. data/lib/hammer_cli_csv/subscriptions.rb +39 -91
  38. data/lib/hammer_cli_csv/sync_plans.rb +20 -24
  39. data/lib/hammer_cli_csv/users.rb +25 -29
  40. data/lib/hammer_cli_csv/utils/subscriptions.rb +130 -0
  41. data/lib/hammer_cli_csv/version.rb +1 -1
  42. data/lib/hammer_cli_csv.rb +2 -2
  43. data/test/csv_test.rb +32 -0
  44. data/test/csv_test_helper.rb +17 -2
  45. data/test/data/content-hosts.csv +3 -2
  46. data/test/data/hosts.csv +0 -11
  47. data/test/data/settings.csv +1 -0
  48. data/test/data/setup/content-hosts.csv +1 -0
  49. data/test/data/setup/content-views.csv +2 -0
  50. data/test/data/setup/lifecycle-environments.csv +5 -0
  51. data/test/data/setup/locations.csv +6 -0
  52. data/test/data/setup/organizations.csv +2 -0
  53. data/test/data/setup/subscriptions.csv +5 -0
  54. data/test/data/subscriptions.csv +15 -17
  55. data/test/export_test.rb +27 -0
  56. data/test/fixtures/vcr_cassettes/apipie.yml +18528 -0
  57. data/test/fixtures/vcr_cassettes/resources/activation_keys_import/create_and_update.yml +38033 -0
  58. data/test/fixtures/vcr_cassettes/resources/content_hosts_export/export.yml +18846 -0
  59. data/test/fixtures/vcr_cassettes/resources/content_hosts_export/export_subscriptions.yml +323 -0
  60. data/test/fixtures/vcr_cassettes/resources/content_hosts_import/create_and_update.yml +41884 -0
  61. data/test/fixtures/vcr_cassettes/resources/content_hosts_import/import_single_line.yml +38248 -0
  62. data/test/fixtures/vcr_cassettes/resources/settings_import/update_settings.yml +18832 -0
  63. data/test/fixtures/vcr_cassettes/resources/settings_import/update_settings_continue.yml +316 -0
  64. data/test/fixtures/vcr_cassettes/resources/subscriptions_import/manifest_does_not_exist.yml +18785 -0
  65. data/test/fixtures/vcr_cassettes/setup/setup_content_views/setup.yml +19090 -0
  66. data/test/fixtures/vcr_cassettes/setup/setup_lifecycle_environments/setup.yml +924 -0
  67. data/test/fixtures/vcr_cassettes/setup/setup_locations/setup.yml +21102 -0
  68. data/test/fixtures/vcr_cassettes/setup/setup_organizations/setup.yml +19278 -0
  69. data/test/fixtures/vcr_cassettes/setup/setup_subscriptions/setup.yml +419 -0
  70. data/test/import_test.rb +20 -25
  71. data/test/resources/activation_keys_test.rb +72 -0
  72. data/test/resources/content_hosts_test.rb +132 -0
  73. data/test/resources/settings_test.rb +81 -0
  74. data/test/resources/subscriptions_test.rb +56 -0
  75. data/test/setup/setup_content_views.rb +17 -0
  76. data/test/setup/setup_lifecycle_environments.rb +17 -0
  77. data/test/setup/setup_locations.rb +17 -0
  78. data/test/setup/setup_organizations.rb +17 -0
  79. data/test/setup/setup_subscriptions.rb +20 -0
  80. data/test/setup/setup_test.rb +10 -0
  81. data/test/test_runner.rb +175 -0
  82. data/test/vcr_setup.rb +54 -0
  83. metadata +77 -13
  84. data/lib/hammer_cli_csv/exception_handler.rb +0 -42
  85. data/lib/hammer_cli_csv/installation_medias.rb +0 -77
  86. data/test/content_hosts_test.rb +0 -61
  87. data/test/settings_test.rb +0 -30
@@ -1,11 +1,20 @@
1
+ #require 'hammer_cli_csv/utils/subscription_utils'
2
+
1
3
  module HammerCLICsv
2
4
  class CsvCommand
3
5
  class ContentHostsCommand < BaseCommand
4
6
  include ::HammerCLIForemanTasks::Helper
7
+ include ::HammerCLICsv::Utils::Subscriptions
5
8
 
6
9
  command_name 'content-hosts'
7
10
  desc 'import or export content hosts'
8
11
 
12
+ def self.supported?
13
+ true
14
+ end
15
+
16
+ option %w(--itemized-subscriptions), :flag, _('Export one subscription per row, only process update subscriptions on import')
17
+
9
18
  ORGANIZATION = 'Organization'
10
19
  ENVIRONMENT = 'Environment'
11
20
  CONTENTVIEW = 'Content View'
@@ -19,123 +28,68 @@ module HammerCLICsv
19
28
  CORES = 'Cores'
20
29
  SLA = 'SLA'
21
30
  PRODUCTS = 'Products'
22
- SUBSCRIPTIONS = 'Subscriptions'
23
-
24
- def export
25
- CSV.open(option_file || '/dev/stdout', 'wb', {:force_quotes => false}) do |csv|
26
- csv << [NAME, ORGANIZATION, ENVIRONMENT, CONTENTVIEW, HOSTCOLLECTIONS, VIRTUAL, HOST,
27
- OPERATINGSYSTEM, ARCHITECTURE, SOCKETS, RAM, CORES, SLA, PRODUCTS, SUBSCRIPTIONS]
28
- if @server_status['release'] == 'Headpin'
29
- export_sam csv
30
- else
31
- export_foretello csv
32
- end
33
- end
34
- end
35
-
36
- def export_sam(csv)
37
- guests_hypervisor = {}
38
- host_ids = []
39
31
 
40
- @headpin.get(:organizations).each do |organization|
41
- next if option_organization && organization['name'] != option_organization
42
- host_ids = @headpin.get("organizations/#{organization['label']}/systems").collect do |host|
43
- host['guests'].each { |guest| guests_hypervisor[guest['uuid']] = host['name'] }
44
- host['uuid']
45
- end
32
+ def export(csv)
33
+ if option_itemized_subscriptions?
34
+ export_itemized_subscriptions csv
35
+ else
36
+ export_all csv
46
37
  end
38
+ end
47
39
 
48
- host_ids.each do |host_id|
49
- host = @headpin.get("systems/#{host_id}")
50
- host_subscriptions = @headpin.get("systems/#{host_id}/subscriptions")['entitlements']
51
-
52
- name = host['name']
53
- organization_name = host['owner']['displayName']
54
- environment = host['environment']['name']
55
- contentview = host['content_view']['name']
56
- hostcollections = nil
57
- virtual = host['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
58
- hypervisor = guests_hypervisor[host['uuid']]
59
- if host['facts']['distribution.name']
60
- operatingsystem = "#{host['facts']['distribution.name']} "
61
- operatingsystem += host['facts']['distribution.version'] if host['facts']['distribution.version']
62
- operatingsystem.strip!
63
- end
64
- architecture = host['facts']['uname.machine']
65
- sockets = host['facts']['cpu.cpu_socket(s)']
66
- ram = host['facts']['memory.memtotal']
67
- cores = host['facts']['cpu.core(s)_per_socket'] || 1
68
- sla = host['serviceLevel']
69
-
70
- products = CSV.generate do |column|
71
- column << host['installedProducts'].collect do |product|
72
- "#{product['productId']}|#{product['productName']}"
73
- end
74
- end
75
- products.delete!("\n")
76
-
77
- subscriptions = CSV.generate do |column|
78
- column << host_subscriptions.collect do |subscription|
79
- "#{subscription['quantity']}|#{subscription['productId']}|#{subscription['poolName']}"
40
+ def export_itemized_subscriptions(csv)
41
+ csv << shared_headers + [Utils::Subscriptions::SUBS_NAME, Utils::Subscriptions::SUBS_TYPE,
42
+ Utils::Subscriptions::SUBS_QUANTITY, Utils::Subscriptions::SUBS_SKU,
43
+ Utils::Subscriptions::SUBS_CONTRACT, Utils::Subscriptions::SUBS_ACCOUNT,
44
+ Utils::Subscriptions::SUBS_START, Utils::Subscriptions::SUBS_END]
45
+ iterate_hosts(csv) do |host|
46
+ export_line = shared_columns(host)
47
+ if host['subscription_facet_attributes']
48
+ subscriptions = @api.resource(:host_subscriptions).call(:index, {
49
+ 'organization_id' => host['organization_id'],
50
+ 'host_id' => host['id']
51
+ })['results']
52
+ if subscriptions.empty?
53
+ csv << export_line + [nil, nil, nil, nil, nil, nil]
54
+ else
55
+ subscriptions.each do |subscription|
56
+ subscription_type = subscription['product_id'].to_i == 0 ? 'Red Hat' : 'Custom'
57
+ subscription_type += ' Guest' if subscription['type'] == 'STACK_DERIVED'
58
+ subscription_type += ' Temporary' if subscription['type'] == 'UNMAPPED_GUEST'
59
+ csv << export_line + [subscription['product_name'], subscription_type,
60
+ subscription['quantity_consumed'], subscription['product_id'],
61
+ subscription['contract_number'], subscription['account_number'],
62
+ DateTime.parse(subscription['start_date']).strftime('%m/%d/%Y'),
63
+ DateTime.parse(subscription['end_date']).strftime('%m/%d/%Y')]
64
+ end
80
65
  end
66
+ else
67
+ csv << export_line + [nil, nil, nil, nil, nil, nil]
81
68
  end
82
- subscriptions.delete!("\n")
83
-
84
- csv << [name, organization_name, environment, contentview, hostcollections, virtual, hypervisor,
85
- operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
86
69
  end
87
70
  end
88
71
 
89
- def export_foretello(csv)
90
- @api.resource(:organizations).call(:index, {:per_page => 999999})['results'].each do |organization|
91
- next if option_organization && organization['name'] != option_organization
92
-
93
- @api.resource(:systems).call(:index, {
94
- 'per_page' => 999999,
95
- 'organization_id' => foreman_organization(:name => organization['name'])
96
- })['results'].each do |host|
97
- host = @api.resource(:systems).call(:show, {
98
- 'id' => host['uuid'],
99
- 'fields' => 'full'
100
- })
101
-
102
- name = host['name']
103
- organization_name = organization['name']
104
- environment = host['environment']['label']
105
- contentview = host['content_view']['name']
106
- hostcollections = CSV.generate do |column|
107
- column << host['hostCollections'].collect do |hostcollection|
108
- hostcollection['name']
109
- end
110
- end
111
- hostcollections.delete!("\n")
112
- virtual = host['facts']['virt.is_guest'] == 'true' ? 'Yes' : 'No'
113
- hypervisor_host = host['virtual_host'].nil? ? nil : host['virtual_host']['name']
114
- operatingsystem = "#{host['facts']['distribution.name']} " if host['facts']['distribution.name']
115
- operatingsystem += host['facts']['distribution.version'] if host['facts']['distribution.version']
116
- architecture = host['facts']['uname.machine']
117
- sockets = host['facts']['cpu.cpu_socket(s)']
118
- ram = host['facts']['memory.memtotal']
119
- cores = host['facts']['cpu.core(s)_per_socket'] || 1
120
- sla = ''
121
- products = CSV.generate do |column|
122
- column << host['installedProducts'].collect do |product|
123
- "#{product['productId']}|#{product['productName']}"
124
- end
125
- end
126
- products.delete!("\n")
72
+ def export_all(csv)
73
+ csv << shared_headers + [Utils::Subscriptions::SUBSCRIPTIONS]
74
+ iterate_hosts(csv) do |host|
75
+ if host['subscription_facet_attributes']
127
76
  subscriptions = CSV.generate do |column|
128
- column << @api.resource(:subscriptions).call(:index, {
129
- 'organization_id' => organization['id'],
130
- 'system_id' => host['uuid']
77
+ column << @api.resource(:host_subscriptions).call(:index, {
78
+ 'organization_id' => host['organization_id'],
79
+ 'host_id' => host['id']
131
80
  })['results'].collect do |subscription|
132
- "#{subscription['consumed']}|#{subscription['product_id']}|#{subscription['product_name']}"
81
+ "#{subscription['quantity_consumed']}"\
82
+ "|#{subscription['product_id']}"\
83
+ "|#{subscription['product_name']}"\
84
+ "|#{subscription['contract_number']}|#{subscription['account_number']}"
133
85
  end
134
86
  end
135
87
  subscriptions.delete!("\n")
136
- csv << [name, organization_name, environment, contentview, hostcollections, virtual, hypervisor_host,
137
- operatingsystem, architecture, sockets, ram, cores, sla, products, subscriptions]
88
+ else
89
+ subscriptions = nil
138
90
  end
91
+
92
+ csv << shared_columns(host) + [subscriptions]
139
93
  end
140
94
  end
141
95
 
@@ -157,111 +111,99 @@ module HammerCLICsv
157
111
  def import_locally
158
112
  @existing = {}
159
113
  @hypervisor_guests = {}
114
+ @all_subscriptions = {}
160
115
 
161
116
  thread_import do |line|
162
- create_content_hosts_from_csv(line)
117
+ create_from_csv(line)
163
118
  end
164
119
 
165
120
  if !@hypervisor_guests.empty?
166
121
  print(_('Updating hypervisor and guest associations...')) if option_verbose?
167
122
  @hypervisor_guests.each do |host_id, guest_ids|
168
123
  @api.resource(:hosts).call(:update, {
169
- 'id' => host_id,
170
- 'host' => {
171
- 'guest_ids' => guest_ids
124
+ 'id' => host_id,
125
+ 'host' => {
126
+ 'subscription_facet_attributes' => {
127
+ 'autoheal' => false,
128
+ 'hypervisor_guest_uuids' => guest_ids
172
129
  }
130
+ }
173
131
  })
174
132
  end
175
133
  puts _('done') if option_verbose?
176
134
  end
177
135
  end
178
136
 
179
- def create_content_hosts_from_csv(line)
137
+ def create_from_csv(line)
180
138
  return if option_organization && line[ORGANIZATION] != option_organization
181
139
 
182
- if !@existing[line[ORGANIZATION]]
183
- @existing[line[ORGANIZATION]] = true
184
- # Fetching all content hosts is too slow and times out due to the complexity of the data
185
- # rendered in the json.
186
- # http://projects.theforeman.org/issues/6307
187
- total = @api.resource(:hosts).call(:index, {
188
- 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
189
- 'per_page' => 1
190
- })['total'].to_i
191
- (total / 20 + 2).to_i.times do |page|
192
- @api.resource(:hosts).call(:index, {
193
- 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
194
- 'page' => page + 1,
195
- 'per_page' => 20
196
- })['results'].each do |host|
197
- @existing[host['name']] = {
198
- :host => host['id'],
199
- :subscription => host['subscription']['id'],
200
- :content => host['content']['id']
201
- }
202
- end
203
- end
204
- end
140
+ update_existing(line)
205
141
 
206
142
  count(line[COUNT]).times do |number|
207
143
  name = namify(line[NAME], number)
208
144
 
209
- if !@existing.include? name
210
- print(_("Creating content host '%{name}'...") % {:name => name}) if option_verbose?
211
- host_id = @api.resource(:host_subscriptions).call(:register, {
212
- 'name' => name,
213
- 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
214
- 'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
215
- 'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW]),
216
- 'facts' => facts(name, line),
217
- 'installed_products' => products(line),
218
- 'service_level' => line[SLA],
219
- 'type' => 'system'
220
- })['id']
221
- @existing[name] = host_id
145
+ if option_itemized_subscriptions?
146
+ update_itemized_subscriptions(name, line)
222
147
  else
223
- # TODO: remove passing facet IDs to update
224
- # Bug #13849 - updating a host's facet should not require the facet id to be included in facet params
225
- # http://projects.theforeman.org/issues/13849
226
- print(_("Updating content host '%{name}'...") % {:name => name}) if option_verbose?
227
- host_id = @api.resource(:hosts).call(:update, {
228
- 'id' => @existing[name][:host],
229
- 'host' => {
230
- 'name' => name,
231
- 'content_facet_attributes' => {
232
- 'id' => @existing[name][:content],
233
- 'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
234
- 'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW])
235
- },
236
- 'subscription_facet_attributes' => {
237
- 'id' => @existing[name][:subscription],
238
- 'facts' => facts(name, line),
239
- # TODO: PUT /hosts subscription_facet_attributes missing "installed_products"
240
- # http://projects.theforeman.org/issues/13854
241
- #'installed_products' => products(line),
242
- 'service_level' => line[SLA]
243
- }
244
- }
245
- })['host_id']
148
+ update_or_create(name, line)
246
149
  end
150
+ end
151
+ end
247
152
 
248
- if line[VIRTUAL] == 'Yes' && line[HOST]
249
- raise "Content host '#{line[HOST]}' not found" if !@existing[line[HOST]]
250
- @hypervisor_guests[@existing[line[HOST]]] ||= []
251
- @hypervisor_guests[@existing[line[HOST]]] << @existing[name]
252
- end
153
+ private
253
154
 
254
- update_host_facts(host_id, line)
255
- update_host_collections(host_id, line)
256
- update_subscriptions(host_id, line)
155
+ def update_itemized_subscriptions(name, line)
156
+ raise _("Content host '%{name}' must already exist with --itemized-subscriptions") % {:name => name} unless @existing.include? name
257
157
 
258
- puts _('done') if option_verbose?
259
- end
260
- rescue RuntimeError => e
261
- raise "#{e}\n #{line}"
158
+ print(_("Updating subscriptions for content host '%{name}'...") % {:name => name}) if option_verbose?
159
+ host = @api.resource(:hosts).call(:show, {:id => @existing[name]})
160
+ update_subscriptions(host, line, false)
161
+ puts _('done') if option_verbose?
262
162
  end
263
163
 
264
- private
164
+ def update_or_create(name, line)
165
+ if !@existing.include? name
166
+ print(_("Creating content host '%{name}'...") % {:name => name}) if option_verbose?
167
+ params = {
168
+ 'name' => name,
169
+ 'facts' => facts(name, line),
170
+ 'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
171
+ 'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW])
172
+ }
173
+ params['installed_products'] = products(line) if line[PRODUCTS]
174
+ params['service_level'] = line[SLA] if line[SLA]
175
+ host = @api.resource(:host_subscriptions).call(:create, params)
176
+ @existing[name] = host['id']
177
+ else
178
+ print(_("Updating content host '%{name}'...") % {:name => name}) if option_verbose?
179
+ params = {
180
+ 'id' => @existing[name],
181
+ 'host' => {
182
+ 'content_facet_attributes' => {
183
+ 'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
184
+ 'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW])
185
+ },
186
+ 'subscription_facet_attributes' => {
187
+ 'facts' => facts(name, line),
188
+ 'installed_products' => products(line),
189
+ 'service_level' => line[SLA]
190
+ }
191
+ }
192
+ }
193
+ host = @api.resource(:hosts).call(:update, params)
194
+ end
195
+
196
+ if line[VIRTUAL] == 'Yes' && line[HOST]
197
+ raise "Content host '#{line[HOST]}' not found" if !@existing[line[HOST]]
198
+ @hypervisor_guests[@existing[line[HOST]]] ||= []
199
+ @hypervisor_guests[@existing[line[HOST]]] << "#{line[ORGANIZATION]}/#{name}"
200
+ end
201
+
202
+ update_host_collections(host, line)
203
+ update_subscriptions(host, line, true)
204
+
205
+ puts _('done') if option_verbose?
206
+ end
265
207
 
266
208
  def facts(name, line)
267
209
  facts = {}
@@ -278,17 +220,15 @@ module HammerCLICsv
278
220
  facts
279
221
  end
280
222
 
281
- def update_host_facts(host_id, line)
282
- end
283
-
284
- def update_host_collections(host_id, line)
285
- return nil if !line[HOSTCOLLECTIONS]
286
- CSV.parse_line(line[HOSTCOLLECTIONS]).each do |hostcollection_name|
287
- @api.resource(:host_collections).call(:add_hosts, {
288
- 'id' => katello_hostcollection(line[ORGANIZATION], :name => hostcollection_name),
289
- 'hosts_ids' => [host_id]
290
- })
291
- end
223
+ def update_host_collections(host, line)
224
+ # TODO: http://projects.theforeman.org/issues/16234
225
+ # return nil if line[HOSTCOLLECTIONS].nil? || line[HOSTCOLLECTIONS].empty?
226
+ # CSV.parse_line(line[HOSTCOLLECTIONS]).each do |hostcollection_name|
227
+ # @api.resource(:host_collections).call(:add_hosts, {
228
+ # 'id' => katello_hostcollection(line[ORGANIZATION], :name => hostcollection_name),
229
+ # 'host_ids' => [host['id']]
230
+ # })
231
+ # end
292
232
  end
293
233
 
294
234
  def os_name_version(operatingsystem)
@@ -305,44 +245,186 @@ module HammerCLICsv
305
245
 
306
246
  def products(line)
307
247
  return nil if !line[PRODUCTS]
308
- products = CSV.parse_line(line[PRODUCTS]).collect do |product_details|
248
+ CSV.parse_line(line[PRODUCTS]).collect do |product_details|
309
249
  product = {}
310
- # TODO: these get passed straight through to candlepin; probably would be better to process in server
311
- # to allow underscore product_id here
312
- (product['productId'], product['productName']) = product_details.split('|')
250
+ (product['product_id'], product['product_name']) = product_details.split('|')
313
251
  product['arch'] = line[ARCHITECTURE]
314
252
  product['version'] = os_name_version(line[OPERATINGSYSTEM])[1]
315
253
  product
316
254
  end
317
- products
318
255
  end
319
256
 
320
- def update_subscriptions(host_id, line)
257
+ def update_subscriptions(host, line, remove_existing)
321
258
  existing_subscriptions = @api.resource(:host_subscriptions).call(:index, {
322
- 'host_id' => host_id
259
+ 'host_id' => host['id']
323
260
  })['results']
324
- if existing_subscriptions.length != 0
261
+ if remove_existing && existing_subscriptions.length != 0
262
+ existing_subscriptions.map! do |existing_subscription|
263
+ {:id => existing_subscription['id'], :quantity => existing_subscription['quantity_consumed']}
264
+ end
325
265
  @api.resource(:host_subscriptions).call(:remove_subscriptions, {
326
- 'host_id' => host_id,
266
+ 'host_id' => host['id'],
327
267
  'subscriptions' => existing_subscriptions
328
268
  })
269
+ existing_subscriptions = []
270
+ end
271
+
272
+ if line[Utils::Subscriptions::SUBS_NAME].nil? && line[Utils::Subscriptions::SUBS_SKU].nil?
273
+ all_in_one_subscription(host, existing_subscriptions, line)
274
+ else
275
+ single_subscription(host, existing_subscriptions, line)
276
+ end
277
+ end
278
+
279
+ def single_subscription(host, existing_subscriptions, line)
280
+ already_attached = false
281
+ if line[Utils::Subscriptions::SUBS_SKU]
282
+ already_attached = existing_subscriptions.detect do |subscription|
283
+ line[Utils::Subscriptions::SUBS_SKU] == subscription['product_id']
284
+ end
285
+ elsif line[Utils::Subscriptions::SUBS_NAME]
286
+ already_attached = existing_subscriptions.detect do |subscription|
287
+ line[Utils::Subscriptions::SUBS_NAME] == subscription['name']
288
+ end
289
+ end
290
+ if already_attached
291
+ print _(" '%{name}' already attached...") % {:name => already_attached['name']}
292
+ return
329
293
  end
330
294
 
295
+ available_subscriptions = @api.resource(:subscriptions).call(:index, {
296
+ 'organization_id' => host['organization_id'],
297
+ 'host_id' => host['id'],
298
+ 'available_for' => 'host',
299
+ 'match_host' => true
300
+ })['results']
301
+
302
+ matches = matches_by_sku_and_name([], line, available_subscriptions)
303
+ matches = matches_by_type(matches, line)
304
+ matches = matches_by_account(matches, line)
305
+ matches = matches_by_contract(matches, line)
306
+ matches = matches_by_quantity(matches, line)
307
+
308
+ raise _("No matching subscriptions") if matches.empty?
309
+
310
+ match = matches[0]
311
+ print _(" attaching '%{name}'...") % {:name => match['name']} if option_verbose?
312
+
313
+ @api.resource(:host_subscriptions).call(:add_subscriptions, {
314
+ 'host_id' => host['id'],
315
+ 'subscriptions' => existing_subscriptions + [match]
316
+ })
317
+ end
318
+
319
+ def all_in_one_subscription(host, existing_subscriptions, line)
331
320
  return if line[SUBSCRIPTIONS].nil? || line[SUBSCRIPTIONS].empty?
332
321
 
333
322
  subscriptions = CSV.parse_line(line[SUBSCRIPTIONS], {:skip_blanks => true}).collect do |details|
334
- (amount, sku, name) = details.split('|')
323
+ (amount, sku, name, contract, account) = split_subscription_details(details)
335
324
  {
336
- :id => katello_subscription(line[ORGANIZATION], :name => name),
325
+ :id => get_subscription(line[ORGANIZATION], :name => name, :contract => contract,
326
+ :account => account),
337
327
  :quantity => (amount.nil? || amount.empty? || amount == 'Automatic') ? 0 : amount.to_i
338
328
  }
339
329
  end
340
330
 
341
331
  @api.resource(:host_subscriptions).call(:add_subscriptions, {
342
- 'host_id' => host_id,
332
+ 'host_id' => host['id'],
343
333
  'subscriptions' => subscriptions
344
334
  })
345
335
  end
336
+
337
+ def update_existing(line)
338
+ if !@existing[line[ORGANIZATION]]
339
+ @existing[line[ORGANIZATION]] = true
340
+ # Fetching all content hosts can be too slow and times so page
341
+ # http://projects.theforeman.org/issues/6307
342
+ total = @api.resource(:hosts).call(:index, {
343
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
344
+ 'per_page' => 1
345
+ })['total'].to_i
346
+ (total / 20 + 1).to_i.times do |page|
347
+ @api.resource(:hosts).call(:index, {
348
+ 'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
349
+ 'page' => page + 1,
350
+ 'per_page' => 20
351
+ })['results'].each do |host|
352
+ if host['subscription_facet_attributes']
353
+ @existing[host['name']] = host['id']
354
+ end
355
+ end
356
+ end
357
+ end
358
+ end
359
+
360
+ def iterate_hosts(csv)
361
+ hypervisors = []
362
+ hosts = []
363
+ @api.resource(:organizations).call(:index, {:per_page => 999999})['results'].each do |organization|
364
+ next if option_organization && organization['name'] != option_organization
365
+
366
+ @api.resource(:hosts).call(:index, {
367
+ 'per_page' => 999999,
368
+ 'organization_id' => foreman_organization(:name => organization['name'])
369
+ })['results'].each do |host|
370
+ host = @api.resource(:hosts).call(:show, {
371
+ 'id' => host['id']
372
+ })
373
+ host['facts'] ||= {}
374
+ if host['subscription_facet_attributes']['virtual_guests'].empty?
375
+ hosts.push(host)
376
+ else
377
+ hypervisors.push(host)
378
+ end
379
+ end
380
+ end
381
+ hypervisors.each do |host|
382
+ yield host
383
+ end
384
+ hosts.each do |host|
385
+ yield host
386
+ end
387
+ end
388
+
389
+ def shared_headers
390
+ [NAME, ORGANIZATION, ENVIRONMENT, CONTENTVIEW, HOSTCOLLECTIONS, VIRTUAL, HOST,
391
+ OPERATINGSYSTEM, ARCHITECTURE, SOCKETS, RAM, CORES, SLA, PRODUCTS]
392
+ end
393
+
394
+ def shared_columns(host)
395
+ name = host['name']
396
+ organization_name = host['organization_name']
397
+ if host['content_facet_attributes']
398
+ environment = host['content_facet_attributes']['lifecycle_environment']['name']
399
+ contentview = host['content_facet_attributes']['content_view']['name']
400
+ hostcollections = export_column(host['content_facet_attributes'], 'host_collections', 'name')
401
+ else
402
+ environment = nil
403
+ contentview = nil
404
+ hostcollections = nil
405
+ end
406
+ if host['subscription_facet_attributes']
407
+ hypervisor_host = host['subscription_facet_attributes']['virtual_host'].nil? ? nil : host['subscription_facet_attributes']['virtual_host']['name']
408
+ products = export_column(host['subscription_facet_attributes'], 'installed_products') do |product|
409
+ "#{product['productId']}|#{product['productName']}"
410
+ end
411
+ else
412
+ hypervisor_host = nil
413
+ products = nil
414
+ end
415
+ virtual = host['facts']['virt::is_guest'] == 'true' ? 'Yes' : 'No'
416
+ operatingsystem = host['facts']['distribution::name'] if host['facts']['distribution::name']
417
+ operatingsystem += " #{host['facts']['distribution::version']}" if host['facts']['distribution::version']
418
+ architecture = host['facts']['uname::machine']
419
+ sockets = host['facts']['cpu::cpu_socket(s)']
420
+ ram = host['facts']['memory::memtotal']
421
+ cores = host['facts']['cpu::core(s)_per_socket'] || 1
422
+ sla = ''
423
+
424
+ [name, organization_name, environment, contentview, hostcollections, virtual, hypervisor_host,
425
+ operatingsystem, architecture, sockets, ram, cores, sla, products]
426
+ end
427
+
346
428
  end
347
429
  end
348
430
  end