Project

General

Profile

Download (16.7 KB) Statistics
| Branch: | Tag: | Revision:

hammer-cli-csv / lib / hammer_cli_csv / content_hosts.rb @ f5d21adb

1 a1aa846b Tom McKay
module HammerCLICsv
2 7bea883f Grant Gainey
  class CsvCommand
3 a77acc4a Tom McKay
    class ContentHostsCommand < BaseCommand
4 780e508f Tom McKay
      include ::HammerCLIForemanTasks::Helper
5 611959a3 Tom McKay
      include ::HammerCLICsv::Utils::Subscriptions
6 780e508f Tom McKay
7 a77acc4a Tom McKay
      command_name 'content-hosts'
8
      desc         'import or export content hosts'
9 7bea883f Grant Gainey
10 c06f1783 Tom McKay
      def self.supported?
11
        true
12
      end
13
14 611959a3 Tom McKay
      option %w(--itemized-subscriptions), :flag, _('Export one subscription per row, only process update subscriptions on import')
15 c06f1783 Tom McKay
16 7bea883f Grant Gainey
      ORGANIZATION = 'Organization'
17
      ENVIRONMENT = 'Environment'
18
      CONTENTVIEW = 'Content View'
19 a77acc4a Tom McKay
      HOSTCOLLECTIONS = 'Host Collections'
20 7bea883f Grant Gainey
      VIRTUAL = 'Virtual'
21
      HOST = 'Host'
22
      OPERATINGSYSTEM = 'OS'
23
      ARCHITECTURE = 'Arch'
24
      SOCKETS = 'Sockets'
25
      RAM = 'RAM'
26
      CORES = 'Cores'
27
      SLA = 'SLA'
28
      PRODUCTS = 'Products'
29
30 c06f1783 Tom McKay
      def export(csv)
31 611959a3 Tom McKay
        if option_itemized_subscriptions?
32
          export_itemized_subscriptions csv
33 c06f1783 Tom McKay
        else
34
          export_all csv
35 0b978f6e Tom McKay
        end
36
      end
37
38 611959a3 Tom McKay
      def export_itemized_subscriptions(csv)
39
        csv << shared_headers + [Utils::Subscriptions::SUBS_NAME, Utils::Subscriptions::SUBS_TYPE,
40
                                 Utils::Subscriptions::SUBS_QUANTITY, Utils::Subscriptions::SUBS_SKU,
41
                                 Utils::Subscriptions::SUBS_CONTRACT, Utils::Subscriptions::SUBS_ACCOUNT,
42
                                 Utils::Subscriptions::SUBS_START, Utils::Subscriptions::SUBS_END]
43 c06f1783 Tom McKay
        iterate_hosts(csv) do |host|
44
          export_line = shared_columns(host)
45
          if host['subscription_facet_attributes']
46
            subscriptions = @api.resource(:host_subscriptions).call(:index, {
47
                'organization_id' => host['organization_id'],
48
                'host_id' => host['id']
49
            })['results']
50
            if subscriptions.empty?
51
              csv << export_line + [nil, nil, nil, nil, nil, nil]
52 5da9eed3 Tom McKay
            else
53 c06f1783 Tom McKay
              subscriptions.each do |subscription|
54
                subscription_type = subscription['product_id'].to_i == 0 ? 'Red Hat' : 'Custom'
55
                subscription_type += ' Guest' if subscription['type'] == 'STACK_DERIVED'
56
                subscription_type += ' Temporary' if subscription['type'] == 'UNMAPPED_GUEST'
57
                csv << export_line + [subscription['product_name'], subscription_type,
58
                                      subscription['quantity_consumed'], subscription['product_id'],
59
                                      subscription['contract_number'], subscription['account_number'],
60
                                      DateTime.parse(subscription['start_date']).strftime('%m/%d/%Y'),
61
                                      DateTime.parse(subscription['end_date']).strftime('%m/%d/%Y')]
62
              end
63 0b978f6e Tom McKay
            end
64 c06f1783 Tom McKay
          else
65
            csv << export_line + [nil, nil, nil, nil, nil, nil]
66
          end
67
        end
68
      end
69
70
      def export_all(csv)
71 611959a3 Tom McKay
        csv << shared_headers + [Utils::Subscriptions::SUBSCRIPTIONS]
72 c06f1783 Tom McKay
        iterate_hosts(csv) do |host|
73
          if host['subscription_facet_attributes']
74 0b978f6e Tom McKay
            subscriptions = CSV.generate do |column|
75 5da9eed3 Tom McKay
              column << @api.resource(:host_subscriptions).call(:index, {
76 c06f1783 Tom McKay
                  'organization_id' => host['organization_id'],
77 5da9eed3 Tom McKay
                  'host_id' => host['id']
78 1909f2e6 Tom McKay
              })['results'].collect do |subscription|
79 5da9eed3 Tom McKay
                "#{subscription['quantity_consumed']}"\
80
                "|#{subscription['product_id']}"\
81
                "|#{subscription['product_name']}"\
82
                "|#{subscription['contract_number']}|#{subscription['account_number']}"
83 c1357ce1 Tom McKay
              end
84 7b00acc6 Tom McKay
            end
85 0b978f6e Tom McKay
            subscriptions.delete!("\n")
86 c06f1783 Tom McKay
          else
87
            subscriptions = nil
88 c1357ce1 Tom McKay
          end
89 c06f1783 Tom McKay
90
          csv << shared_columns(host) + [subscriptions]
91 c1357ce1 Tom McKay
        end
92
      end
93 a1aa846b Tom McKay
94 7bea883f Grant Gainey
      def import
95 9e11b4c5 Tom McKay
        remote = @server_status['plugins'].detect { |plugin| plugin['name'] == 'foreman_csv' }
96
        if remote.nil?
97
          import_locally
98
        else
99
          import_remotely
100
        end
101
      end
102
103
      def import_remotely
104 088e0854 Tom McKay
        params = {'content' => ::File.new(::File.expand_path(option_file), 'rb')}
105 780e508f Tom McKay
        headers = {:content_type => 'multipart/form-data', :multipart => true}
106
        task_progress(@api.resource(:csv).call(:import_content_hosts, params, headers))
107 9e11b4c5 Tom McKay
      end
108
109
      def import_locally
110 7bea883f Grant Gainey
        @existing = {}
111 2645da78 Tom McKay
        @hypervisor_guests = {}
112 c06f1783 Tom McKay
        @all_subscriptions = {}
113 a1aa846b Tom McKay
114 7bea883f Grant Gainey
        thread_import do |line|
115 5da9eed3 Tom McKay
          create_from_csv(line)
116 7bea883f Grant Gainey
        end
117 6bc031bd Tom McKay
118 9e11b4c5 Tom McKay
        if !@hypervisor_guests.empty?
119
          print(_('Updating hypervisor and guest associations...')) if option_verbose?
120
          @hypervisor_guests.each do |host_id, guest_ids|
121 5146ac68 Tom McKay
            @api.resource(:hosts).call(:update, {
122 5da9eed3 Tom McKay
              'id' => host_id,
123
              'host' => {
124
                'subscription_facet_attributes' => {
125 c06f1783 Tom McKay
                  'autoheal' => false,
126 5da9eed3 Tom McKay
                  'hypervisor_guest_uuids' => guest_ids
127 5146ac68 Tom McKay
                }
128 5da9eed3 Tom McKay
              }
129 9e11b4c5 Tom McKay
            })
130
          end
131 c4dbc7a3 Tom McKay
          puts _('done') if option_verbose?
132 7bea883f Grant Gainey
        end
133 6bc031bd Tom McKay
      end
134 a1aa846b Tom McKay
135 5da9eed3 Tom McKay
      def create_from_csv(line)
136 2645da78 Tom McKay
        return if option_organization && line[ORGANIZATION] != option_organization
137
138 5da9eed3 Tom McKay
        update_existing(line)
139 a1aa846b Tom McKay
140 02387fb5 Tom McKay
        count(line[COUNT]).times do |number|
141 7bea883f Grant Gainey
          name = namify(line[NAME], number)
142
143 611959a3 Tom McKay
          if option_itemized_subscriptions?
144
            update_itemized_subscriptions(name, line)
145 7bea883f Grant Gainey
          else
146 c06f1783 Tom McKay
            update_or_create(name, line)
147 7bea883f Grant Gainey
          end
148 c06f1783 Tom McKay
        end
149
      end
150 6bc031bd Tom McKay
151 c06f1783 Tom McKay
      private
152 6bc031bd Tom McKay
153 611959a3 Tom McKay
      def update_itemized_subscriptions(name, line)
154
        raise _("Content host '%{name}' must already exist with --itemized-subscriptions") % {:name => name} unless @existing.include? name
155 6bc031bd Tom McKay
156 c06f1783 Tom McKay
        print(_("Updating subscriptions for content host '%{name}'...") % {:name => name}) if option_verbose?
157
        host = @api.resource(:hosts).call(:show, {:id => @existing[name]})
158
        update_subscriptions(host, line, false)
159
        puts _('done') if option_verbose?
160
      end
161
162
      def update_or_create(name, line)
163
        if !@existing.include? name
164
          print(_("Creating content host '%{name}'...") % {:name => name}) if option_verbose?
165
          params = {
166
            'name' => name,
167
            'facts' => facts(name, line),
168
            'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
169
            'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW])
170
          }
171
          params['installed_products'] = products(line) if line[PRODUCTS]
172
          params['service_level'] = line[SLA] if line[SLA]
173
          host = @api.resource(:host_subscriptions).call(:create, params)
174
          @existing[name] = host['id']
175
        else
176
          print(_("Updating content host '%{name}'...") % {:name => name}) if option_verbose?
177
          params = {
178
            'id' => @existing[name],
179
            'host' => {
180
              'content_facet_attributes' => {
181
                'lifecycle_environment_id' => lifecycle_environment(line[ORGANIZATION], :name => line[ENVIRONMENT]),
182
                'content_view_id' => katello_contentview(line[ORGANIZATION], :name => line[CONTENTVIEW])
183
              },
184
              'subscription_facet_attributes' => {
185
                'facts' => facts(name, line),
186
                'installed_products' => products(line),
187
                'service_level' => line[SLA]
188
              }
189
            }
190
          }
191
          host = @api.resource(:hosts).call(:update, params)
192
        end
193
194
        if line[VIRTUAL] == 'Yes' && line[HOST]
195
          raise "Content host '#{line[HOST]}' not found" if !@existing[line[HOST]]
196
          @hypervisor_guests[@existing[line[HOST]]] ||= []
197
          @hypervisor_guests[@existing[line[HOST]]] << "#{line[ORGANIZATION]}/#{name}"
198 7bea883f Grant Gainey
        end
199 c06f1783 Tom McKay
200
        update_host_collections(host, line)
201
        update_subscriptions(host, line, true)
202
203
        puts _('done') if option_verbose?
204 a1aa846b Tom McKay
      end
205 9d9e4494 Tom McKay
206 7bea883f Grant Gainey
      def facts(name, line)
207
        facts = {}
208 fd57822a Tom McKay
        facts['system.certificate_version'] = '3.2'  # Required for auto-attach to work
209 8c7ba4af Tom McKay
        facts['network.hostname'] = name
210 7bea883f Grant Gainey
        facts['cpu.core(s)_per_socket'] = line[CORES]
211
        facts['cpu.cpu_socket(s)'] = line[SOCKETS]
212
        facts['memory.memtotal'] = line[RAM]
213
        facts['uname.machine'] = line[ARCHITECTURE]
214 fd57822a Tom McKay
        (facts['distribution.name'], facts['distribution.version']) = os_name_version(line[OPERATINGSYSTEM])
215 7bea883f Grant Gainey
        facts['virt.is_guest'] = line[VIRTUAL] == 'Yes' ? true : false
216
        facts['virt.uuid'] = "#{line[ORGANIZATION]}/#{name}" if facts['virt.is_guest']
217 fd57822a Tom McKay
        facts['cpu.cpu(s)'] = 1
218 7bea883f Grant Gainey
        facts
219 9d9e4494 Tom McKay
      end
220
221 5da9eed3 Tom McKay
      def update_host_collections(host, line)
222 c06f1783 Tom McKay
        # TODO: http://projects.theforeman.org/issues/16234
223
        # return nil if line[HOSTCOLLECTIONS].nil? || line[HOSTCOLLECTIONS].empty?
224
        # CSV.parse_line(line[HOSTCOLLECTIONS]).each do |hostcollection_name|
225
        #   @api.resource(:host_collections).call(:add_hosts, {
226
        #       'id' => katello_hostcollection(line[ORGANIZATION], :name => hostcollection_name),
227
        #       'host_ids' => [host['id']]
228
        #   })
229
        # end
230 6bc031bd Tom McKay
      end
231
232 fd57822a Tom McKay
      def os_name_version(operatingsystem)
233
        if operatingsystem.nil?
234
          name = nil
235
          version = nil
236
        elsif operatingsystem.index(' ')
237
          (name, version) = operatingsystem.split(' ')
238
        else
239
          (name, version) = ['RHEL', operatingsystem]
240
        end
241
        [name, version]
242
      end
243
244 7bea883f Grant Gainey
      def products(line)
245
        return nil if !line[PRODUCTS]
246 5da9eed3 Tom McKay
        CSV.parse_line(line[PRODUCTS]).collect do |product_details|
247 7bea883f Grant Gainey
          product = {}
248 5da9eed3 Tom McKay
          (product['product_id'], product['product_name']) = product_details.split('|')
249 fd57822a Tom McKay
          product['arch'] = line[ARCHITECTURE]
250
          product['version'] = os_name_version(line[OPERATINGSYSTEM])[1]
251 7bea883f Grant Gainey
          product
252
        end
253 9d9e4494 Tom McKay
      end
254
255 c06f1783 Tom McKay
      def update_subscriptions(host, line, remove_existing)
256 5146ac68 Tom McKay
        existing_subscriptions = @api.resource(:host_subscriptions).call(:index, {
257 5da9eed3 Tom McKay
            'host_id' => host['id']
258 fd57822a Tom McKay
        })['results']
259 c06f1783 Tom McKay
        if remove_existing && existing_subscriptions.length != 0
260
          existing_subscriptions.map! do |existing_subscription|
261
            {:id => existing_subscription['id'], :quantity => existing_subscription['quantity_consumed']}
262
          end
263 5146ac68 Tom McKay
          @api.resource(:host_subscriptions).call(:remove_subscriptions, {
264 5da9eed3 Tom McKay
            'host_id' => host['id'],
265 5146ac68 Tom McKay
            'subscriptions' => existing_subscriptions
266 fd57822a Tom McKay
          })
267 c06f1783 Tom McKay
          existing_subscriptions = []
268
        end
269
270 611959a3 Tom McKay
        if line[Utils::Subscriptions::SUBS_NAME].nil? && line[Utils::Subscriptions::SUBS_SKU].nil?
271 c06f1783 Tom McKay
          all_in_one_subscription(host, existing_subscriptions, line)
272
        else
273
          single_subscription(host, existing_subscriptions, line)
274 fd57822a Tom McKay
        end
275 c06f1783 Tom McKay
      end
276
277
      def single_subscription(host, existing_subscriptions, line)
278
        already_attached = false
279 611959a3 Tom McKay
        if line[Utils::Subscriptions::SUBS_SKU]
280 c06f1783 Tom McKay
          already_attached = existing_subscriptions.detect do |subscription|
281 611959a3 Tom McKay
            line[Utils::Subscriptions::SUBS_SKU] == subscription['product_id']
282 c06f1783 Tom McKay
          end
283 611959a3 Tom McKay
        elsif line[Utils::Subscriptions::SUBS_NAME]
284 c06f1783 Tom McKay
          already_attached = existing_subscriptions.detect do |subscription|
285 611959a3 Tom McKay
            line[Utils::Subscriptions::SUBS_NAME] == subscription['name']
286 c06f1783 Tom McKay
          end
287
        end
288
        if already_attached
289
          print _(" '%{name}' already attached...") % {:name => already_attached['name']}
290
          return
291
        end
292
293
        available_subscriptions = @api.resource(:subscriptions).call(:index, {
294
          'organization_id' => host['organization_id'],
295
          'host_id' => host['id'],
296
          'available_for' => 'host',
297
          'match_host' => true
298
        })['results']
299
300
        matches = matches_by_sku_and_name([], line, available_subscriptions)
301
        matches = matches_by_type(matches, line)
302
        matches = matches_by_account(matches, line)
303
        matches = matches_by_contract(matches, line)
304
        matches = matches_by_quantity(matches, line)
305 fd57822a Tom McKay
306 c06f1783 Tom McKay
        raise _("No matching subscriptions") if matches.empty?
307
308
        match = matches[0]
309
        print _(" attaching '%{name}'...") % {:name => match['name']} if option_verbose?
310
311
        @api.resource(:host_subscriptions).call(:add_subscriptions, {
312
            'host_id' => host['id'],
313
            'subscriptions' => existing_subscriptions + [match]
314
        })
315
      end
316
317
      def all_in_one_subscription(host, existing_subscriptions, line)
318 fd57822a Tom McKay
        return if line[SUBSCRIPTIONS].nil? || line[SUBSCRIPTIONS].empty?
319
320
        subscriptions = CSV.parse_line(line[SUBSCRIPTIONS], {:skip_blanks => true}).collect do |details|
321 611959a3 Tom McKay
          (amount, sku, name, contract, account) = split_subscription_details(details)
322 fd57822a Tom McKay
          {
323 611959a3 Tom McKay
            :id => get_subscription(line[ORGANIZATION], :name => name, :contract => contract,
324
                                                        :account => account),
325 2645da78 Tom McKay
            :quantity => (amount.nil? || amount.empty? || amount == 'Automatic') ? 0 : amount.to_i
326 fd57822a Tom McKay
          }
327 7bea883f Grant Gainey
        end
328 fd57822a Tom McKay
329 5146ac68 Tom McKay
        @api.resource(:host_subscriptions).call(:add_subscriptions, {
330 5da9eed3 Tom McKay
            'host_id' => host['id'],
331 fd57822a Tom McKay
            'subscriptions' => subscriptions
332
        })
333 9d9e4494 Tom McKay
      end
334 5da9eed3 Tom McKay
335
      def update_existing(line)
336
        if !@existing[line[ORGANIZATION]]
337
          @existing[line[ORGANIZATION]] = true
338
          # Fetching all content hosts can be too slow and times so page
339
          # http://projects.theforeman.org/issues/6307
340
          total = @api.resource(:hosts).call(:index, {
341
              'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
342
              'per_page' => 1
343
          })['total'].to_i
344
          (total / 20 + 1).to_i.times do |page|
345
            @api.resource(:hosts).call(:index, {
346
                'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
347
                'page' => page + 1,
348
                'per_page' => 20
349
            })['results'].each do |host|
350
              if host['subscription_facet_attributes']
351
                @existing[host['name']] = host['id']
352
              end
353
            end
354
          end
355
        end
356
      end
357 c06f1783 Tom McKay
358
      def iterate_hosts(csv)
359
        hypervisors = []
360
        hosts = []
361
        @api.resource(:organizations).call(:index, {:per_page => 999999})['results'].each do |organization|
362
          next if option_organization && organization['name'] != option_organization
363
364
          @api.resource(:hosts).call(:index, {
365
              'per_page' => 999999,
366
              'organization_id' => foreman_organization(:name => organization['name'])
367
          })['results'].each do |host|
368
            host = @api.resource(:hosts).call(:show, {
369
                'id' => host['id']
370
            })
371
            host['facts'] ||= {}
372
            if host['subscription_facet_attributes']['virtual_guests'].empty?
373
              hosts.push(host)
374
            else
375
              hypervisors.push(host)
376
            end
377
          end
378
        end
379
        hypervisors.each do |host|
380
          yield host
381
        end
382
        hosts.each do |host|
383
          yield host
384
        end
385
      end
386
387
      def shared_headers
388
        [NAME, ORGANIZATION, ENVIRONMENT, CONTENTVIEW, HOSTCOLLECTIONS, VIRTUAL, HOST,
389
         OPERATINGSYSTEM, ARCHITECTURE, SOCKETS, RAM, CORES, SLA, PRODUCTS]
390
      end
391
392
      def shared_columns(host)
393
        name = host['name']
394
        organization_name = host['organization_name']
395
        if host['content_facet_attributes']
396
          environment = host['content_facet_attributes']['lifecycle_environment']['name']
397
          contentview = host['content_facet_attributes']['content_view']['name']
398
          hostcollections = export_column(host['content_facet_attributes'], 'host_collections', 'name')
399
        else
400
          environment = nil
401
          contentview = nil
402
          hostcollections = nil
403
        end
404
        if host['subscription_facet_attributes']
405
          hypervisor_host = host['subscription_facet_attributes']['virtual_host'].nil? ? nil : host['subscription_facet_attributes']['virtual_host']['name']
406
          products = export_column(host['subscription_facet_attributes'], 'installed_products') do |product|
407
            "#{product['productId']}|#{product['productName']}"
408
          end
409
        else
410
          hypervisor_host = nil
411
          products = nil
412
        end
413
        virtual = host['facts']['virt::is_guest'] == 'true' ? 'Yes' : 'No'
414
        operatingsystem = host['facts']['distribution::name'] if host['facts']['distribution::name']
415
        operatingsystem += " #{host['facts']['distribution::version']}" if host['facts']['distribution::version']
416
        architecture = host['facts']['uname::machine']
417
        sockets = host['facts']['cpu::cpu_socket(s)']
418
        ram = host['facts']['memory::memtotal']
419
        cores = host['facts']['cpu::core(s)_per_socket'] || 1
420
        sla = ''
421
422
        [name, organization_name, environment, contentview, hostcollections, virtual, hypervisor_host,
423
         operatingsystem, architecture, sockets, ram, cores, sla, products]
424
      end
425
426 9d9e4494 Tom McKay
    end
427 a1aa846b Tom McKay
  end
428
end