Project

General

Profile

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

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

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
      RELEASE = 'Release'
14
      REPOSITORY_URL = 'Repository Url'
15
      DESCRIPTION = 'Description'
16
      VERIFY_SSL = 'Verify SSL'
17
      UPSTREAM_USERNAME = 'Username'
18
      UPSTREAM_PASSWORD = 'Password'
19
      DOWNLOAD_POLICY = 'Download Policy'
20
      MIRROR_ON_SYNC = 'Mirror on Sync'
21
      UNPROTECTED = 'Publish via HTTP'
22

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

    
65
      def import
66
        @existing_products = {}
67
        @existing_repositories = {}
68

    
69
        thread_import do |line|
70
          create_products_from_csv(line)
71
        end
72
      end
73

    
74
      def create_products_from_csv(line)
75
        return if option_organization && line[ORGANIZATION] != option_organization
76

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

    
83
      end
84

    
85
      private
86

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

    
112
        return product
113
      end
114

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

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

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

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

    
153
        sync_repository(line, product['name'], repository)
154
      end
155
      # rubocop:enable CyclomaticComplexity
156

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

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

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

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

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

    
219
        repository = repository_set['repositories'].find do |repo|
220
          repo['name'] == line[REPOSITORY]
221
        end
222

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

    
230
          basearch,releasever = parse_basearch_releasever(line[REPOSITORY])
231
          params = {
232
              'id' => product_content['content']['id'],
233
              'product_id' => product['id'],
234
              'basearch' => basearch,
235
              'releasever' => releasever
236
          }
237
          @api.resource(:repository_sets).call(:enable, params)
238
          puts _('done') if option_verbose?
239
        else
240
          puts _('Repository %{name} already enabled') % {:name => repository['name']} if option_verbose?
241
        end
242
        product
243
      end
244

    
245
      # basearch and releasever are required for repo set enable. The repository ends with, for example,
246
      # "x86_64 6.1" or "ia64 6 Server"
247
      def parse_basearch_releasever(content_set)
248
        pieces = content_set.split
249
        if pieces[-1] == 'Server'
250
          return pieces[-3], "#{pieces[-2]}#{pieces[-1]}"
251
        else
252
          return pieces[-2], pieces[-1]
253
        end
254
      end
255

    
256
      def content_type(repository_type)
257
        case repository_type
258
        when /yum/i
259
          'yum'
260
        when /puppet/i
261
          'puppet'
262
        when /docker/i
263
          'docker'
264
        else
265
          raise "Unrecognized repository type '#{repository_type}'"
266
        end
267
      end
268

    
269
      def sync_repository(line, name, repository)
270
        if (HammerCLI::Settings.get(:csv, :products_sync) == true || HammerCLI::Settings.get(:csv, :products_sync).nil?) &&
271
            option_sync?
272
          if option_verbose?
273
            print _("syncing repository '%{repository_name}' in product '%{name}'...") %
274
              {:repository_name => repository['name'], :name => name}
275
          end
276
          if repository['last_sync']
277
            print _("previously synced, skipping...") if option_verbose?
278
          else
279
            exec_sync_repository(line, repository)
280
          end
281
        end
282
      end
283

    
284
      def exec_sync_repository(line, repository)
285
        args = %W{ --server #{ @server } --username #{ @username } --password #{ @password }
286
                   repository synchronize
287
                   --id #{ repository['id'] }
288
                   --organization-id #{ foreman_organization(:name => line[ORGANIZATION]) } }
289
        hammer.run(args)
290

    
291
      end
292

    
293
      def get_content_set(organization, product, repository)
294
        organization_id = organization['id']
295
        product_id = product['id']
296
        @content_sets ||={}
297
        @content_sets[organization_id] ||= {}
298
        if @content_sets[organization_id][product_id].nil?
299
          @content_sets[organization_id][product_id] = {}
300
          @api.resource(:repository_sets).call(:index, {
301
              'per_page' => 999999,
302
              'organization_id' => organization_id,
303
              'product_id' => product_id
304
          })['results'].each do |repository_set|
305
            content_set = repository_set['name']
306
            repository_set['repositories'].each do |repo|
307
              @content_sets[organization_id][product_id][repo['id']] = content_set
308
            end
309
          end
310
        end
311
        content_set = @content_sets[organization_id][product_id][repository['id']]
312

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

    
315
        content_set
316
      end
317
    end
318
  end
319
end