Project

General

Profile

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

hammer-cli-csv / lib / hammer_cli_csv / products.rb @ f92e695a

1
module HammerCLICsv
2
  class CsvCommand
3
    class ProductsCommand < BaseCommand
4
      command_name 'products'
5
      desc         _('import or export products')
6

    
7
      option %w(--[no-]sync), :flag, _('Sync product repositories (default true)'), :default => true
8
      LABEL = 'Label'
9
      ORGANIZATION = 'Organization'
10
      REPOSITORY = 'Repository'
11
      REPOSITORY_TYPE = 'Repository Type'
12
      CONTENT_SET = 'Content Set'
13
      RELEASEVER = '$releasever'
14
      BASEARCH = '$basearch'
15
      REPOSITORY_URL = 'Repository Url'
16
      DESCRIPTION = 'Description'
17
      VERIFY_SSL = 'Verify SSL'
18
      UPSTREAM_USERNAME = 'Username'
19
      UPSTREAM_PASSWORD = 'Password'
20
      DOWNLOAD_POLICY = 'Download Policy'
21
      MIRROR_ON_SYNC = 'Mirror on Sync'
22
      UNPROTECTED = 'Publish via HTTP'
23

    
24
      def export(csv)
25
        csv << [NAME, LABEL, ORGANIZATION, DESCRIPTION, REPOSITORY, REPOSITORY_TYPE,
26
                CONTENT_SET, BASEARCH, RELEASEVER, REPOSITORY_URL, VERIFY_SSL, UNPROTECTED, MIRROR_ON_SYNC, DOWNLOAD_POLICY,
27
                UPSTREAM_USERNAME, UPSTREAM_PASSWORD]
28
        @api.resource(:organizations).call(:index, {
29
            :per_page => 999999,
30
            :search => option_search
31
        })['results'].each do |organization|
32
          next if option_organization && organization['name'] != option_organization
33
          @api.resource(:products).call(:index, {
34
              'per_page' => 999999,
35
              'enabled' => true,
36
              'organization_id' => organization['id']
37
          })['results'].each do |product|
38
            @api.resource(:repositories).call(:index, {
39
                'product_id' => product['id'],
40
                'organization_id' => organization['id']
41
            })['results'].each do |repository|
42
              repository = @api.resource(:repositories).call(:show, {:id => repository['id']})
43
              if repository['product_type'] == 'custom'
44
                repository_type = "Custom #{repository['content_type'].capitalize}"
45
                if repository['content_type'] == 'docker'
46
                  content_set = repository['docker_upstream_name']
47
                else
48
                  content_set = nil
49
                end
50
              else
51
                repository_type = "Red Hat #{repository['content_type'].capitalize}"
52
                content_set = get_content_set(organization, product, repository)
53
              end
54
              releasever = repository['minor']
55
              basearch = nil
56
              csv << [product['name'], product['label'], organization['name'],
57
                      product['description'], repository['name'], repository_type,
58
                      content_set, basearch, releasever, repository['url'],
59
                      repository['verify_ssl_on_sync'] ? 'Yes' : 'No',
60
                      repository['unprotected'] ? 'Yes' : 'No',
61
                      repository['mirror_on_sync'] ? 'Yes' : 'No', repository['download_policy'],
62
                      repository['upstream_username'],nil]
63
            end
64
          end
65
        end
66
      end
67

    
68
      def import
69
        @existing_products = {}
70
        @existing_repositories = {}
71

    
72
        thread_import do |line|
73
          create_products_from_csv(line)
74
        end
75
      end
76

    
77
      def create_products_from_csv(line)
78
        return if option_organization && line[ORGANIZATION] != option_organization
79

    
80
        count(line[COUNT]).times do |number|
81
          product = create_or_update_product(line, number)
82
          create_or_update_repository(line, number, product)
83
          puts _('done') if option_verbose?
84
        end
85

    
86
      end
87

    
88
      private
89

    
90
      def create_or_update_product(line, number)
91
        product_name = namify(line[NAME], number)
92
        get_existing_product(product_name, line)
93
        if line[REPOSITORY_TYPE] =~ /Red Hat/
94
          product = enable_red_hat_product(line, product_name)
95
        else
96
          # TODO: product label? other?
97
          params = {
98
            :name => product_name,
99
            'organization_id' => foreman_organization(:name => line[ORGANIZATION])
100
          }
101
          params[:description] = line[DESCRIPTION] if !line[DESCRIPTION].nil? &&
102
                                                      !line[DESCRIPTION].empty?
103
          product = @existing_products[line[ORGANIZATION]][product_name]
104
          if product.nil?
105
            print _("Creating product '%{name}'...") % {:name => product_name} if option_verbose?
106
            product = @api.resource(:products).call(:create, params)
107
            @existing_products[line[ORGANIZATION]][product_name] = product
108
          else
109
            print _("Updating product '%{name}'...") % {:name => product_name} if option_verbose?
110
            params[:id] = product['id']
111
            @api.resource(:products).call(:update, params)
112
          end
113
        end
114

    
115
        return product
116
      end
117

    
118
      # rubocop:disable CyclomaticComplexity
119
      def create_or_update_repository(line, number, product)
120
        repository_name = namify(line[REPOSITORY], number)
121
        repository = get_repository(line, product['name'], product['id'], repository_name)
122

    
123
        params = {
124
            'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
125
            'name' => repository_name,
126
            'url' => line[REPOSITORY_URL]
127
        }
128
        params['verify_ssl'] = line[VERIFY_SSL] == 'Yes' ? true : false if !line[VERIFY_SSL].nil? && !line[VERIFY_SSL].empty?
129
        params['unprotected'] = line[UNPROTECTED] == 'Yes' ? true : false if !line[UNPROTECTED].nil? && !line[UNPROTECTED].empty?
130
        params['mirror_on_sync'] = line[MIRROR_ON_SYNC] == 'Yes' ? true : false if !line[MIRROR_ON_SYNC].nil? && !line[MIRROR_ON_SYNC].empty?
131
        params['download_policy'] = line[DOWNLOAD_POLICY] if !line[DOWNLOAD_POLICY].nil? && !line[DOWNLOAD_POLICY].empty?
132
        params['upstream_username'] = line[UPSTREAM_USERNAME] if !line[UPSTREAM_USERNAME].nil? && !line[UPSTREAM_USERNAME].empty?
133
        params['upstream_password'] = line[UPSTREAM_PASSWORD] if !line[UPSTREAM_PASSWORD].nil? && !line[UPSTREAM_PASSWORD].empty?
134
        if line[REPOSITORY_TYPE] == 'Custom Docker'
135
          params['docker_upstream_name'] = line[CONTENT_SET]
136
        end
137

    
138
        if !repository
139
          if line[REPOSITORY_TYPE] =~ /Red Hat/
140
            raise _("Red Hat product '%{product_name}' does not have repository '%{repository_name}'") %
141
              {:product_name => product['name'], :repository_name => repository_name}
142
          end
143
          params['label'] = labelize(repository_name)
144
          params['product_id'] = product['id']
145
          params['content_type'] = content_type(line[REPOSITORY_TYPE])
146

    
147
          print _("Creating repository '%{repository_name}'...") % {:repository_name => repository_name} if option_verbose?
148
          repository = @api.resource(:repositories).call(:create, params)
149
          @existing_repositories[line[ORGANIZATION] + product['name']][repository_name] = repository
150
        else
151
          print _("Updating repository '%{repository_name}'...") % {:repository_name => repository_name} if option_verbose?
152
          params['id'] = repository['id']
153
          repository = @api.resource(:repositories).call(:update, params)
154
        end
155

    
156
        sync_repository(line, product['name'], repository)
157
      end
158
      # rubocop:enable CyclomaticComplexity
159

    
160
      def get_existing_product(product_name, line)
161
        @existing_products[line[ORGANIZATION]] = {}
162
        @api.resource(:products).call(:index, {
163
            'per_page' => 999999,
164
            'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
165
            'enabled' => true,
166
            'search' => "name=\"#{product_name}\""
167
        })['results'].each do |product|
168
          @existing_products[line[ORGANIZATION]][product['name']] = product
169

    
170
          @api.resource(:repositories).call(:index, {
171
              'page_size' => 999999, 'paged' => true,
172
              'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
173
              'product_id' => product['id'],
174
              'enabled' => true,
175
              'library' => true
176
          })['results'].each do |repository|
177
            @existing_repositories[line[ORGANIZATION] + product['name']] ||= {}
178
            @existing_repositories[line[ORGANIZATION] + product['name']][repository['name']] = repository
179
          end
180
        end
181
      end
182

    
183
      def get_repository(line, product_name, product_id, repository_name)
184
        @existing_repositories[line[ORGANIZATION] + product_name] ||= {}
185
        if !@existing_repositories[line[ORGANIZATION] + product_name][repository_name]
186
          @api.resource(:repositories).call(:index, {
187
              'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
188
              'library' => true,
189
              'all' => false,
190
              'product_id' => product_id
191
          })['results'].each do |repository|
192
            @existing_repositories[line[ORGANIZATION] + product_name][repository['name']] = repository
193
          end
194
        end
195
        @existing_repositories[line[ORGANIZATION] + product_name][repository_name]
196
      end
197

    
198
      def enable_red_hat_product(line, product_name)
199
        product = @existing_products[line[ORGANIZATION]][product_name]
200
        unless product
201
          product = @api.resource(:products).call(:index, {
202
              'per_page' => 999999,
203
              'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
204
              'name' => product_name
205
          })['results'][0]
206
          raise _("Red Hat product '%{product_name}' does not exist") %
207
              {:product_name => product_name} if product.nil?
208
          @existing_repositories[line[ORGANIZATION] + product['name']] = {}
209
        end
210
        product = @api.resource(:products).call(:show, {:id => product['id']})
211

    
212
        results = @api.resource(:repository_sets).call(:index, {
213
            'per_page' => 999999,
214
            'organization_id' => foreman_organization(:name => line[ORGANIZATION]),
215
            'product_id' => product['id'],
216
            'name' => line[CONTENT_SET]
217
        })['results']
218
        raise "No match for content set '#{line[CONTENT_SET]}'" if results.length == 0
219
        raise "Multiple matches for content set '#{line[CONTENT_SET]}'" if results.length != 1
220
        repository_set = results[0]
221

    
222
        repository = repository_set['repositories'].find do |repo|
223
          repo['name'] == line[REPOSITORY]
224
        end
225

    
226
        if repository.nil?
227
          print _('Enabling repository %{name}...') % {:name => line[REPOSITORY]} if option_verbose?
228
          product_content = product['product_content'].find do |content|
229
            content['content']['name'] == line[CONTENT_SET]
230
          end
231
          raise "No match for content set '#{line[CONTENT_SET]}'" if !product_content
232

    
233
          basearch,releasever = parse_basearch_releasever(line)
234
          params = {
235
              'id' => product_content['content']['id'],
236
              'product_id' => product['id']
237
          }
238
          params['basearch'] = basearch if !basearch.nil? && repository_set['contentUrl'] =~ /\$basearch/
239
          params['releasever'] = releasever if !releasever.nil? && repository_set['contentUrl'] =~ /\$releasever/
240
          @api.resource(:repository_sets).call(:enable, params)
241
          puts _('done') if option_verbose?
242
        else
243
          puts _('Repository %{name} already enabled') % {:name => repository['name']} if option_verbose?
244
        end
245
        product
246
      end
247

    
248
      # basearch and releasever are required for repo set enable. The repository ends with, for example,
249
      # "x86_64 6.1" or "ia64 6 Server"
250
      def parse_basearch_releasever(line)
251
        basearch = line[BASEARCH] if !line[BASEARCH].nil? && !line[BASEARCH].empty?
252
        releasever = line[RELEASEVER] if !line[RELEASEVER].nil? && !line[RELEASEVER].empty?
253
        content_set = line[REPOSITORY]
254
        pieces = content_set.split
255
        if pieces[-1] == 'Server'
256
          basearch = pieces[-3] unless basearch
257
          releasever = "#{pieces[-2]}#{pieces[-1]}" unless releasever
258
        else
259
          basearch = pieces[-2] unless basearch
260
          releasever = pieces[-1] unless releasever
261
        end
262
        return basearch,releasever
263
      end
264

    
265
      def content_type(repository_type)
266
        case repository_type
267
        when /yum/i
268
          'yum'
269
        when /puppet/i
270
          'puppet'
271
        when /docker/i
272
          'docker'
273
        else
274
          raise "Unrecognized repository type '#{repository_type}'"
275
        end
276
      end
277

    
278
      def sync_repository(line, name, repository)
279
        if (HammerCLI::Settings.get(:csv, :products_sync) == true || HammerCLI::Settings.get(:csv, :products_sync).nil?) &&
280
            option_sync?
281
          if option_verbose?
282
            print _("syncing repository '%{repository_name}' in product '%{name}'...") %
283
              {:repository_name => repository['name'], :name => name}
284
          end
285
          if repository['last_sync']
286
            print _("previously synced, skipping...") if option_verbose?
287
          else
288
            exec_sync_repository(line, repository)
289
          end
290
        end
291
      end
292

    
293
      def exec_sync_repository(line, repository)
294
        args = %W{ --server #{ @server } --username #{ @username } --password #{ @password }
295
                   repository synchronize
296
                   --id #{ repository['id'] }
297
                   --organization-id #{ foreman_organization(:name => line[ORGANIZATION]) } }
298
        hammer.run(args)
299

    
300
      end
301

    
302
      def get_content_set(organization, product, repository)
303
        organization_id = organization['id']
304
        product_id = product['id']
305
        @content_sets ||={}
306
        @content_sets[organization_id] ||= {}
307
        if @content_sets[organization_id][product_id].nil?
308
          @content_sets[organization_id][product_id] = {}
309
          @api.resource(:repository_sets).call(:index, {
310
              'per_page' => 999999,
311
              'organization_id' => organization_id,
312
              'product_id' => product_id
313
          })['results'].each do |repository_set|
314
            content_set = repository_set['name']
315
            repository_set['repositories'].each do |repo|
316
              @content_sets[organization_id][product_id][repo['id']] = content_set
317
            end
318
          end
319
        end
320
        content_set = @content_sets[organization_id][product_id][repository['id']]
321

    
322
        raise "Content set for repository '#{repository['name']}' not found in product '#{product['name']}" unless content_set
323

    
324
        content_set
325
      end
326
    end
327
  end
328
end