foreman_scc_manager 1.8.20 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +14 -10
  3. data/app/controllers/api/v2/scc_accounts_controller.rb +47 -4
  4. data/app/controllers/api/v2/scc_products_controller.rb +1 -1
  5. data/app/controllers/scc_accounts_controller.rb +47 -0
  6. data/app/lib/actions/scc_manager/subscribe_product.rb +51 -15
  7. data/app/models/scc_account.rb +1 -1
  8. data/app/views/scc_accounts/show.html.erb +7 -51
  9. data/config/routes.rb +1 -0
  10. data/db/migrate/20220429102717_populate_scc_katello_repositories.rb +5 -0
  11. data/db/migrate/20220531120722_add_product_category_to_scc_products.rb +5 -0
  12. data/lib/foreman_scc_manager/engine.rb +1 -1
  13. data/lib/foreman_scc_manager/version.rb +1 -1
  14. data/lib/tasks/republish_repositories.rake +13 -0
  15. data/package.json +54 -0
  16. data/test/controllers/scc_accounts_controller_test.rb +6 -0
  17. data/webpack/components/SCCProductPage/EmptySccProducts.js +46 -0
  18. data/webpack/components/SCCProductPage/SCCProductPage.js +96 -0
  19. data/webpack/components/SCCProductPage/SCCProductPageActions.js +27 -0
  20. data/webpack/components/SCCProductPage/SCCProductPageConstants.js +5 -0
  21. data/webpack/components/SCCProductPage/SCCProductPageReducer.js +34 -0
  22. data/webpack/components/SCCProductPage/SCCProductPageSelectors.js +7 -0
  23. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCGenericPicker/index.js +80 -0
  24. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/index.js +174 -0
  25. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/components/SCCRepoPicker/styles.scss +3 -0
  26. data/webpack/components/SCCProductPage/components/SCCProductPicker/components/SCCTreePicker/index.js +303 -0
  27. data/webpack/components/SCCProductPage/components/SCCProductPicker/index.js +235 -0
  28. data/webpack/components/SCCProductPage/components/SCCProductPicker/styles.scss +10 -0
  29. data/webpack/components/SCCProductPage/components/SCCProductPickerModal/index.js +81 -0
  30. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/index.js +113 -0
  31. data/webpack/components/SCCProductPage/components/SCCProductView/components/SCCRepoView/styles.scss +14 -0
  32. data/webpack/components/SCCProductPage/components/SCCProductView/index.js +236 -0
  33. data/webpack/components/SCCProductPage/components/common/SCCGenericExpander/index.js +58 -0
  34. data/webpack/components/SCCProductPage/components/common/SCCProductTreeExpander/index.js +21 -0
  35. data/webpack/components/SCCProductPage/components/common/SCCSubscribedProductsExpander/index.js +21 -0
  36. data/webpack/components/SCCProductPage/index.js +18 -0
  37. data/webpack/components/SCCProductPage/sccProductPage.scss +8 -0
  38. data/webpack/index.js +11 -0
  39. data/webpack/reducer.js +7 -0
  40. metadata +40 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39d299a2ed13146bd47fda51bfe3b47cccc78a2fe3984f5070b38acfa1d54bb0
4
- data.tar.gz: eb22cc8040b485c69aa0dffd09b91a5ba926e89c9d124f4d9a32800a8db7546f
3
+ metadata.gz: 73d2510abbdbe51fef59bba613502fb02ef1fe14eeb441f0bee716b17e2eba8d
4
+ data.tar.gz: c61b9f852d58033c290204508d0d4653e5379c6c8b73e6d5b58d026ea485b0f0
5
5
  SHA512:
6
- metadata.gz: bf8c718efe53da5e8f8b417e249bfb3826a080f328331bee932af7108b0fea3da7a0f6b2fc5f581dc0c9f1588e056ae19d1dad83a2e3f45e2ba776763f672a1a
7
- data.tar.gz: 83665d655bb8fa02fa9140115b3d6a604c7e18bde4c1bb4cc5d0bda505c2e1df7cb8c34fc515c62894e0964edc78ca0c773ca5ec34ed1d821ffe87304ecdd0b3
6
+ metadata.gz: d3ce92fbe191d0aaa6f10d0015ed4a3e3320892d1f45a519a965cdbd63a93e49a0fe6918fcc1b7cfb4618b8a8fc016349bfbceba5825a93ca5400dce5dd18e03
7
+ data.tar.gz: a48593aa198df8fbc5b4c88a2654facb7dc1f77f80373d3cf7a03fa46e84bd5f02af338af0a0e1dd2968da7e7afa6e44548f992e51d1ece7a0a82fa05922f0d9
data/README.md CHANGED
@@ -8,7 +8,13 @@ Foreman plugin to sync SUSE Customer Center products and repositories into Katel
8
8
 
9
9
  ## Installation
10
10
 
11
- This plugin installation is not supported by foreman-installer and has to be installed manually:
11
+ This plugin installation is supported since Foreman 3.4 / Katello 4.6 by the foreman-installer, but only with the scenario Katello:
12
+
13
+ ```sh
14
+ foreman-install --scenario katello --enable-foreman-plugin-scc-manager
15
+ ```
16
+
17
+ You can also install it manually:
12
18
 
13
19
  ```sh
14
20
  yum install tfm-rubygem-foreman_scc_manager
@@ -20,18 +26,16 @@ foreman-installer
20
26
 
21
27
  | Foreman Version | Katello Version | Plugin Version |
22
28
  | --------------- | --------------- | -------------- |
29
+ | 3.5 | 4.5 | ~> 2.0.0 |
30
+ | 3.1 | 4.3 | ~> 1.8.20\* |
31
+ | 3.0 | 4.2 | ~> 1.8.20 |
32
+ | 2.5 | 4.1 | ~> 1.8.20 |
23
33
  | 2.3 | 3.18 | ~> 1.8.9 |
24
34
  | 2.1 | 3.16 | ~> 1.8.5 |
25
35
  | 2.0 | 3.16 | ~> 1.8.4 |
26
36
  | 1.24 | 3.14 | ~> 1.8.0 |
27
- | 1.22 | 3.12 | ~> 1.7.0 |
28
- | 1.21 | 3.10 | ~> 1.6.0 |
29
- | 1.20 | 3.9 | ~> 1.6.0 |
30
- | 1.19 | 3.8 | ~> 1.5.1 |
31
- | 1.18 | 3.7 | ~> 1.5.0 |
32
- | 1.17 | 3.6 | >= 1.3.1 |
33
- | 1.16 | 3.5 | <= 1.3.0 |
34
- | 1.15 | 3.4 | ~> 1.1.0 |
37
+
38
+ \* If you are using foreman_scc_manager in version 1.8.20 and then upgrade to Katello 4.3, you need to manually run the following rake task on your Foreman instance: `foreman-rake foreman_scc_manager:setup_authentication_tokens`.
35
39
 
36
40
  ## Documentation
37
41
 
@@ -47,7 +51,7 @@ Fork and send a Pull Request. Thanks!
47
51
 
48
52
  ## Copyright
49
53
 
50
- Copyright (c) 2021 ATIX AG - https://atix.de
54
+ Copyright (c) 2022 ATIX AG - https://atix.de
51
55
 
52
56
  This program is free software: you can redistribute it and/or modify
53
57
  it under the terms of the GNU General Public License as published by
@@ -10,7 +10,7 @@ module Api
10
10
  api_base_url '/api/v2'
11
11
  end
12
12
 
13
- before_action :find_resource, :only => [:show, :update, :destroy, :sync, :bulk_subscribe]
13
+ before_action :find_resource, :only => [:show, :update, :destroy, :sync, :bulk_subscribe, :bulk_subscribe_with_repos]
14
14
 
15
15
  api :GET, '/scc_accounts/', N_('List all scc_accounts')
16
16
  param :organization_id, :identifier, :required => true
@@ -105,12 +105,16 @@ module Api
105
105
  param :id, :identifier_dottable, :required => true
106
106
  param :scc_subscribe_product_ids, Array, :required => true
107
107
  def bulk_subscribe
108
- scc_products_to_subscribe = @scc_account.scc_products.where(:id => params[:scc_subscribe_product_ids])
109
108
  respond_to do |format|
110
- if scc_products_to_subscribe.count > 0
109
+ if params[:scc_subscribe_product_ids].count > 0
110
+ # we need to pass two parameters to the product subscribe task,
111
+ # the product itself and an array of repo ids
112
+ # if the id array is empty, all repositories will be subscribed to
113
+ scc_products = @scc_account.scc_products.where(:id => params[:scc_subscribe_product_ids])
111
114
  subscribe_task = ForemanTasks.async_task(::Actions::BulkAction,
112
115
  ::Actions::SccManager::SubscribeProduct,
113
- scc_products_to_subscribe)
116
+ scc_products,
117
+ {})
114
118
  format.json { render json: subscribe_task.to_json, status: :ok }
115
119
  else
116
120
  format.json { render json: { error: 'No Product selected' }, status: :expectation_failed }
@@ -122,6 +126,43 @@ module Api
122
126
  render json: { error: ('Lock on SCC account already taken: %s' % e).to_s }, status: :unprocessable_entity
123
127
  end
124
128
 
129
+ def_param_group :scc_product_data do
130
+ param :scc_product_data, Array, :required => true, :desc => 'Array of Hash elements. One hash element contains an scc_product_id and a repository_list.' do
131
+ param :scc_product_id, Integer, :required => true, :desc => 'Product ID of SCC product'
132
+ param :repository_list, Array, of: Integer, :required => false,
133
+ :desc => 'List of SCC repositories belonging to the SCC product. If the list is empty, all repositories will be subscribed to.'
134
+ end
135
+ end
136
+
137
+ api :PUT, '/scc_accounts/:id/bulk_subscribe_with_repos/', N_('Bulk subscription of scc_products with individual repository selection for scc_account.')
138
+ param :id, :identifier_dottable, :required => true
139
+ param_group :scc_product_data
140
+ def bulk_subscribe_with_repos
141
+ respond_to do |format|
142
+ # if we want to subscribe to specific repos, we need to pass the product and the
143
+ # corresponding repository ids instead of scc products only
144
+ if params[:scc_product_data].count > 0
145
+ scc_products = @scc_account.scc_products.where(:id => params[:scc_product_data].pluck(:scc_product_id))
146
+ if scc_products.empty?
147
+ format.json { render json: { error: _('The selected products cannot be found for this SCC account.') }, status: :unprocessable_entity }
148
+ else
149
+ action_args = params[:scc_product_data].map { |p| { p['scc_product_id'] => p['repository_list'] } }.inject(:merge)
150
+ subscribe_task = ForemanTasks.async_task(::Actions::BulkAction,
151
+ ::Actions::SccManager::SubscribeProduct,
152
+ scc_products,
153
+ action_args)
154
+ format.json { render json: subscribe_task.to_json, status: :ok }
155
+ end
156
+ else
157
+ format.json { render json: { error: 'No Product selected' }, status: :expectation_failed }
158
+ end
159
+ end
160
+ rescue ::Foreman::Exception => e
161
+ render json: { error: ('Failed to add task to queue: %s' % e).to_s }, status: :unprocessable_entity
162
+ rescue ForemanTasks::Lock::LockConflict => e
163
+ render json: { error: ('Lock on SCC account already taken: %s' % e).to_s }, status: :unprocessable_entity
164
+ end
165
+
125
166
  private
126
167
 
127
168
  def scc_account_params
@@ -152,6 +193,8 @@ module Api
152
193
  :sync
153
194
  when 'bulk_subscribe'
154
195
  :bulk_subscribe
196
+ when 'bulk_subscribe_with_repos'
197
+ :bulk_subscribe_with_repos
155
198
  else
156
199
  super
157
200
  end
@@ -41,7 +41,7 @@ module Api
41
41
  param :id, :identifier_dottable, :required => true
42
42
  param :scc_account_id, :identifier_dottable, :required => true
43
43
  def subscribe
44
- subcribe_task = ForemanTasks.async_task(::Actions::SccManager::SubscribeProduct, @scc_product) if @scc_product
44
+ subcribe_task = ForemanTasks.async_task(::Actions::SccManager::SubscribeProduct, @scc_product, {}) if @scc_product
45
45
  respond_to do |format|
46
46
  if subcribe_task
47
47
  format.json { render json: subcribe_task.to_json, status: :ok }
@@ -1,5 +1,6 @@
1
1
  class SccAccountsController < ApplicationController
2
2
  helper_method :scc_filtered_products
3
+ helper_method :create_nested_product_tree
3
4
  before_action :find_organization
4
5
  before_action :find_resource, only: %i[show edit update destroy sync bulk_subscribe]
5
6
  before_action :find_available_gpg_keys, only: %i[new edit update create]
@@ -97,6 +98,52 @@ class SccAccountsController < ApplicationController
97
98
  redirect_to scc_accounts_path
98
99
  end
99
100
 
101
+ def create_nested_product_tree(scc_products, subscribed_only:)
102
+ if subscribed_only
103
+ scc_products_base = scc_products.where(:product_type => 'base').where.not(:product_id => nil).includes([:scc_extensions])
104
+ else
105
+ scc_products_base = scc_products.where(:product_type => 'base').includes([:scc_extensions])
106
+ end
107
+ tree = []
108
+ scc_products_base.each do |p|
109
+ tree.push(get_product_tree_hash(p))
110
+ end
111
+
112
+ tree
113
+ end
114
+
115
+ def scc_product_hash(scc_product)
116
+ scc_product_json = scc_product.as_json(:only => [:scc_id, :id, :arch, :version, :product_id, :subscription_valid],
117
+ include: { :scc_repositories => { :only => [:id, :name, :subscription_valid] } })
118
+ .merge('name' => scc_product.pretty_name, 'product_category' => scc_product.product_category)
119
+ # find if and which Katello root repository is associated with this SCC product
120
+ repo_ids_katello = scc_product.product.blank? || scc_product.product.root_repository_ids.blank? ? nil : scc_product.product.root_repository_ids
121
+ scc_product_json['scc_repositories'].each do |repo|
122
+ # byebug
123
+ if repo_ids_katello.blank?
124
+ repo['katello_root_repository_id'] = nil
125
+ else
126
+ repo_ids_scc = scc_product.scc_repositories.find(repo['id']).katello_root_repository_ids
127
+ repo['katello_root_repository_id'] = repo_ids_scc.blank? ? nil : (repo_ids_scc & repo_ids_katello).first
128
+ end
129
+ end
130
+ scc_product_json
131
+ end
132
+
133
+ def get_product_tree_hash(scc_product_base)
134
+ if scc_product_base.scc_extensions.blank?
135
+ scc_product_hash(scc_product_base)
136
+ else
137
+ children = []
138
+ scc_product_base.scc_extensions.each do |ext|
139
+ children.push(get_product_tree_hash(ext))
140
+ end
141
+ scc_product_base_hash = scc_product_hash(scc_product_base)
142
+ scc_product_base_hash['children'] = children
143
+ scc_product_base_hash
144
+ end
145
+ end
146
+
100
147
  private
101
148
 
102
149
  def find_available_gpg_keys
@@ -1,22 +1,54 @@
1
1
  module Actions
2
2
  module SccManager
3
3
  class SubscribeProduct < Actions::EntryAction
4
- def plan(scc_product)
5
- raise _('Product already subscribed!') if scc_product.product
4
+ # scc_product is an ActiveRecord object of Class SccProduct
5
+ # scc_repos_to_subscribe is a hash with the product id as keys and an array of
6
+ # repos to subscribe as values
7
+ # rubocop:disable Metrics/MethodLength
8
+ def plan(scc_product, scc_repos_to_subscribe)
9
+ if scc_product.product
10
+ ::Foreman::Logging.logger('foreman_scc_manager')
11
+ .info("SccProduct '#{scc_product.friendly_name}' is already subscribed to.")
12
+ else
13
+ ::Foreman::Logging.logger('foreman_scc_manager')
14
+ .info("Initiating subscription for SccProduct '#{scc_product.friendly_name}'.")
15
+ end
6
16
 
7
- ::Foreman::Logging.logger('foreman_scc_manager')
8
- .info("Initiating subscription for SccProduct '#{scc_product.friendly_name}'.")
9
17
  sequence do
10
- product_create_action = plan_action(CreateProduct,
11
- :product_name => scc_product.pretty_name,
12
- :product_description => scc_product.pretty_description,
13
- :organization_id => scc_product.organization.id,
14
- :gpg_key => scc_product.scc_account.katello_gpg_key_id)
18
+ if scc_product.product_id.nil?
19
+ product_create_action = plan_action(CreateProduct,
20
+ :product_name => scc_product.pretty_name,
21
+ :product_description => scc_product.pretty_description,
22
+ :organization_id => scc_product.organization.id,
23
+ :gpg_key => scc_product.scc_account.katello_gpg_key_id)
24
+ end
15
25
  katello_repos = {}
16
- scc_product.scc_repositories.each do |repo|
26
+ # we need to set the repositories to subscribe to
27
+ scc_repos = []
28
+ if scc_repos_to_subscribe.empty?
29
+ scc_repos = scc_product.scc_repositories
30
+ else
31
+ # at this point, we need to make sure that the repository list is valid
32
+ # we want to subscribe only to repos that we have not subscribed before
33
+ repo_ids_katello = scc_product.product.blank? || scc_product.product.root_repository_ids.blank? ? nil : scc_product.product.root_repository_ids
34
+ scc_repos = scc_product.scc_repositories.where(id: scc_repos_to_subscribe[scc_product.id])
35
+ unless repo_ids_katello.nil? || scc_repos.empty?
36
+ # remove repo if Katello repo is already associated
37
+ scc_repos.reject { |repo| (repo.katello_root_repositories & repo_ids_katello).present? }
38
+ end
39
+ end
40
+ if scc_repos.empty?
41
+ ::Foreman::Logging.logger('foreman_scc_manager')
42
+ .info('The repositories you have selected are either already subscribed to or invalid.')
43
+ else
44
+ ::Foreman::Logging.logger('foreman_scc_manager')
45
+ .info("Subscribing to SCC repositories '#{scc_repos.pluck(:id)}'.
46
+ If you requested more repositories, please check if those are already subscribed to or invalid.")
47
+ end
48
+ scc_repos.each do |repo|
17
49
  arch = scc_product.arch || 'noarch'
18
50
  repo_create_action = plan_action(CreateRepository,
19
- :product_id => product_create_action.output[:product_id],
51
+ :product_id => scc_product.product_id || product_create_action.output[:product_id],
20
52
  :uniq_name => repo.uniq_name(scc_product),
21
53
  :pretty_repo_name => repo.pretty_name,
22
54
  :url => repo.url,
@@ -24,18 +56,22 @@ module Actions
24
56
  :arch => arch)
25
57
  katello_repos[repo.id] = repo_create_action.output[:katello_root_repository_id]
26
58
  end
59
+
27
60
  # connect action to resource (=> make parameters accessable in input)
28
- action_subject(scc_product, product_id: product_create_action.output[:product_id])
61
+ action_subject(scc_product, product_id: product_create_action.output[:product_id]) if scc_product.product_id.nil?
29
62
  input.update(katello_repos: katello_repos)
30
63
  plan_self
31
64
  end
32
65
  end
66
+ # rubocop:enable Metrics/MethodLength
33
67
 
34
68
  def finalize
35
69
  # connect Scc products and Katello products
36
- scc_product = SccProduct.find(input[:scc_product][:id])
37
- product = ::Katello::Product.find(input[:product_id])
38
- scc_product.update!(product: product)
70
+ # it may happen that we only append repos to an existing product
71
+ unless input[:scc_product].nil?
72
+ scc_product = SccProduct.find(input[:scc_product][:id])
73
+ scc_product.update!(product_id: input[:product_id])
74
+ end
39
75
  # extract Katello repo ids from input hash and store to database
40
76
  input[:katello_repos].each do |scc_repo_id, katello_root_repository_id|
41
77
  SccKatelloRepository.find_or_create_by(scc_repository_id: scc_repo_id, katello_root_repository_id: katello_root_repository_id)
@@ -219,8 +219,8 @@ class SccAccount < ApplicationRecord
219
219
  cached_product.product_type = up['product_type']
220
220
  cached_product.scc_repositories =
221
221
  scc_repositories.where(scc_id: up['repositories'].map { |repo| repo['id'] })
222
- # name should be set after friendly_name because it depends on friendly_name
223
222
  cached_product.name = cached_product.pretty_name
223
+ cached_product.product_category = up['name']
224
224
  cached_product.description = cached_product.pretty_description
225
225
  cached_product.subscription_valid = true
226
226
  cached_product.save!
@@ -1,52 +1,8 @@
1
1
  <% title (_("Product Selection for Account %s") % @scc_account) %>
2
-
3
- <% javascript 'foreman_scc_manager/scc_accounts' %>
4
- <%= form_for([:bulk_subscribe, @scc_account], method: :put) do |f| %>
5
- <% def render_list_node(f, scc_product, parent_id = "") %>
6
- <li>
7
- <span class="scc_product_checkbox" id="<%= "scc_product_span_#{parent_id + "_" + scc_product.id.to_s}" %>" <%= "data-parent=scc_product_span_#{parent_id}" if parent_id != "" %>>
8
- <% if scc_product.product %>
9
- <%= check_box_tag("scc_account[scc_unsubscribe_product_ids][]", scc_product.id, true, disabled: true) %>
10
- <%= link_to(scc_product.pretty_name, "/products/#{scc_product.product_id}") %>
11
- <% else %>
12
- <%= check_box_tag("scc_account[scc_subscribe_product_ids][]", scc_product.id, false) %>
13
- <%= scc_product.pretty_name %>
14
- <% end %>
15
- <% unless scc_product.subscription_valid? %>
16
- <span style="color:red">
17
- <%= _('(WARNING: Please check your SUSE subscription)') %>
18
- </span>
19
- <% end %>
20
- </span>
21
- <% if scc_product.scc_extensions.any? %>
22
- <ul style='padding-left: 20px;'>
23
- <% scc_filtered_products(scc_product.scc_extensions, 'extension').each do |scc_extension| %>
24
- <% render_list_node(f, scc_extension, parent_id + "_" + scc_product.id.to_s) %>
25
- <% end %>
26
- </ul>
27
- <% end %>
28
- </li>
29
- <% end %>
30
-
31
- <%= f.hidden_field :prevent_missing, value: 1 %>
32
- <ul class="nav nav-tabs" data-tabs="tabs">
33
- <li class="active"><a href="#primary" data-toggle="tab"><%= _("SUSE Customer Center") %></a></li>
34
- </ul>
35
-
36
- <div class="tab-content">
37
- <div class="tab-pane active" id="primary">
38
- <ul>
39
- <% if @scc_account.synced %>
40
- <% scc_filtered_products(@scc_account.scc_products).each do |scc_product| %>
41
- <% render_list_node(f, scc_product) %>
42
- <% end %>
43
- <% else %>
44
- <%= _('Please sync your SUSE subscriptions first.') %>
45
- <% submit_disabled = true %>
46
- <% end %>
47
- </ul>
48
- </div>
49
- <%= submit_or_cancel f, false, {disabled: submit_disabled || false} %>
50
- </div>
51
- <% end %>
52
-
2
+ <%= webpacked_plugins_js_for :foreman_scc_manager %>
3
+ <%= webpacked_plugins_css_for :foreman_scc_manager %>
4
+ <%= react_component('SCCProductPage', {
5
+ :canCreate => authorized_for(action: :sync),
6
+ :sccAccountId => @scc_account.id,
7
+ :sccProductsInit => create_nested_product_tree(@scc_account.scc_products, subscribed_only: false)
8
+ }) %>
data/config/routes.rb CHANGED
@@ -32,6 +32,7 @@ Rails.application.routes.draw do
32
32
  put 'test_connection'
33
33
  put 'sync'
34
34
  put 'bulk_subscribe'
35
+ put 'bulk_subscribe_with_repos'
35
36
  end
36
37
  end
37
38
  constraints(:scc_account_id => /[^\/]+/) do
@@ -3,6 +3,11 @@ class PopulateSccKatelloRepositories < ActiveRecord::Migration[6.0]
3
3
  belongs_to :product, class_name: 'Katello::Product'
4
4
  has_and_belongs_to_many :scc_repositories
5
5
  end
6
+ class SccRepository < ApplicationRecord
7
+ has_many :scc_katello_repositories
8
+ has_many :katello_root_repositories, through: :scc_katello_repositories
9
+ has_and_belongs_to_many :scc_products
10
+ end
6
11
 
7
12
  def up
8
13
  SccProduct.where.not(product_id: nil).each do |scc_p|
@@ -0,0 +1,5 @@
1
+ class AddProductCategoryToSccProducts < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :scc_products, :product_category, :string, null: true
4
+ end
5
+ end
@@ -38,7 +38,7 @@ module ForemanSccManager
38
38
 
39
39
  permission :use_scc_accounts,
40
40
  { :scc_accounts => [:bulk_subscribe],
41
- :'api/v2/scc_accounts' => [:bulk_subscribe] },
41
+ :'api/v2/scc_accounts' => [:bulk_subscribe, :bulk_subscribe_with_repos] },
42
42
  :resource_type => 'SccAccount'
43
43
 
44
44
  permission :new_scc_accounts,
@@ -1,3 +1,3 @@
1
1
  module ForemanSccManager
2
- VERSION = '1.8.20'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -0,0 +1,13 @@
1
+ namespace :foreman_scc_manager do
2
+ desc 'Republish all SCC-repositories.'
3
+ task :republish_scc_repositories => ['dynflow:client', 'katello:check_ping'] do
4
+ needing_publish = SccKatelloRepository.joins(:katello_root_repository)
5
+ .joins(:katello_root_repository => :repositories)
6
+ .pluck("#{Katello::Repository.table_name}.id")
7
+ if needing_publish.any?
8
+ ForemanTasks.async_task(::Actions::Katello::Repository::BulkMetadataGenerate, Katello::Repository.where(:id => needing_publish))
9
+ else
10
+ puts 'Skipped. No repository found which was created by the SCC plugin.'
11
+ end
12
+ end
13
+ end
data/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "foreman_scc_manager",
3
+ "version": "1.8.16",
4
+ "description": "Foreman plugin to sync SUSE Customer Center products and repositories into Katello ",
5
+ "main": "index.js",
6
+ "directories": {
7
+ "lib": "lib",
8
+ "test": "test"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1",
12
+ "create-react-component": "yo react-domain"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+ssh://git@github.com/ATIX-AG/foreman_scc_manager.git"
17
+ },
18
+ "peerDependencies": {
19
+ "@theforeman/vendor": "^4.14.0"
20
+ },
21
+ "devDependencies": {
22
+ "@babel/core": "^7.7.0",
23
+ "babel-eslint": "^10.0.0",
24
+ "babel-loader": "^8.0.0",
25
+ "@theforeman/builder": "^4.14.0",
26
+ "@theforeman/eslint-plugin-foreman": "^8.16.0",
27
+ "@theforeman/find-foreman": "^4.14.0",
28
+ "@theforeman/stories": "^4.14.0",
29
+ "@theforeman/test": "^4.14.0",
30
+ "@theforeman/vendor-dev": "^4.14.0",
31
+ "eslint": "^6.7.2",
32
+ "eslint-plugin-spellcheck": "0.0.17",
33
+ "eslint-plugin-react": "^7.27.1",
34
+ "eslint-plugin-react-hooks": "^4.3.0",
35
+ "prettier": "^2.5.1",
36
+ "react-redux-test-utils": "^0.2.0"
37
+ },
38
+ "keywords": [
39
+ "SUSE",
40
+ "Katello",
41
+ "products",
42
+ "repositories"
43
+ ],
44
+ "author": "ATIX AG",
45
+ "license": "GPL-3.0",
46
+ "bugs": {
47
+ "url": "https://github.com/ATIX-AG/foreman_scc_manager/issues"
48
+ },
49
+ "homepage": "https://github.com/ATIX-AG/foreman_scc_manager#readme",
50
+ "dependencies": {
51
+ "react-native-elements": "^3.4.2",
52
+ "react-native-vector-icons": "^9.0.0"
53
+ }
54
+ }
@@ -2,6 +2,12 @@ require 'test_plugin_helper'
2
2
 
3
3
  class SccAccountsControllerTest < ActionController::TestCase
4
4
  def setup
5
+ # rubocop: disable Lint/SuppressedException
6
+ begin
7
+ ::Katello::ContentCredential
8
+ rescue NameError
9
+ end
10
+ # rubocop: enable Lint/SuppressedException
5
11
  @scc_account = scc_accounts(:one)
6
12
  end
7
13
 
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+ import PropTypes from 'prop-types';
4
+ import { Button } from '@patternfly/react-core';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import EmptyState from 'foremanReact/components/common/EmptyState';
7
+ import { syncSccAccountAction } from './SCCProductPageActions';
8
+
9
+ export const EmptySccProducts = ({ canCreate, sccAccountId }) => {
10
+ const dispatch = useDispatch();
11
+ const onSyncStart = (evt) => {
12
+ dispatch(syncSccAccountAction(sccAccountId));
13
+ };
14
+
15
+ const content = __(
16
+ `Please synchronize your SUSE account before you can subscribe to SUSE products.`
17
+ );
18
+ return (
19
+ <>
20
+ <EmptyState
21
+ icon="th"
22
+ iconType="fa"
23
+ header={__('SUSE Customer Center')}
24
+ description={<div dangerouslySetInnerHTML={{ __html: content }} />}
25
+ documentation={{
26
+ url: 'https://docs.orcharhino.com/or/docs/sources/usage_guides/managing_sles_systems_guide.html#mssg_adding_scc_accounts',
27
+ }}
28
+ />
29
+ <Button onClick={onSyncStart} className="btn btn-primary">
30
+ {__('Synchronize SUSE Account')}
31
+ </Button>
32
+ </>
33
+ );
34
+ };
35
+
36
+ EmptySccProducts.propTypes = {
37
+ canCreate: PropTypes.bool,
38
+ sccAccountId: PropTypes.number,
39
+ };
40
+
41
+ EmptySccProducts.defaultProps = {
42
+ canCreate: false,
43
+ sccAccountId: undefined,
44
+ };
45
+
46
+ export default EmptySccProducts;
@@ -0,0 +1,96 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useState } from 'react';
3
+ import { Stack, StackItem } from '@patternfly/react-core';
4
+ import { useDispatch } from 'react-redux';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';
7
+ import SCCProductView from './components/SCCProductView';
8
+ import EmptySccProducts from './EmptySccProducts';
9
+ import SCCProductPicker from './components/SCCProductPicker';
10
+ import SCCProductPickerModal from './components/SCCProductPickerModal';
11
+ import { SCCPRODUCTPAGE_SUMMARY_MODAL_ID } from './SCCProductPageConstants';
12
+ import './sccProductPage.scss';
13
+
14
+ const SCCProductPage = ({
15
+ canCreate,
16
+ sccAccountId,
17
+ sccProductsInit,
18
+ ...props
19
+ }) => {
20
+ const dispatch = useDispatch();
21
+ const [productToEdit, setProductToEdit] = useState(0);
22
+ const [reposToSubscribe, setReposToSubscribe] = useState([]);
23
+ const [subscriptionTaskId, setSubscriptionTaskId] = useState();
24
+
25
+ const editProductTree = (productId) => {
26
+ setProductToEdit(productId);
27
+ };
28
+
29
+ const { setModalOpen, setModalClosed } = useForemanModal({
30
+ id: SCCPRODUCTPAGE_SUMMARY_MODAL_ID,
31
+ });
32
+
33
+ const handleSubscribeCallback = (
34
+ subscriptionTaskIdFromAction,
35
+ reposToSubscribeFromAction
36
+ ) => {
37
+ setSubscriptionTaskId(subscriptionTaskIdFromAction);
38
+ const newReposToSubscribe = [];
39
+ Object.keys(reposToSubscribeFromAction).forEach((k) => {
40
+ const repo = {
41
+ productName: reposToSubscribeFromAction[k].productName,
42
+ repoNames: reposToSubscribeFromAction[k].repoNames,
43
+ };
44
+ newReposToSubscribe.push(repo);
45
+ });
46
+ setReposToSubscribe(newReposToSubscribe);
47
+ dispatch(setModalOpen({ id: SCCPRODUCTPAGE_SUMMARY_MODAL_ID }));
48
+ };
49
+
50
+ return sccProductsInit.length > 0 ? (
51
+ <Stack>
52
+ <StackItem>
53
+ <SCCProductPickerModal
54
+ id={SCCPRODUCTPAGE_SUMMARY_MODAL_ID}
55
+ title={__('The subscription task has been started successfully')}
56
+ taskId={subscriptionTaskId}
57
+ reposToSubscribe={reposToSubscribe}
58
+ />
59
+ </StackItem>
60
+ <StackItem>
61
+ <SCCProductView
62
+ sccProducts={sccProductsInit.filter(
63
+ (prod) => prod.product_id !== null
64
+ )}
65
+ subscriptionTaskId={subscriptionTaskId}
66
+ editProductTreeGlobal={editProductTree}
67
+ />
68
+ </StackItem>
69
+ <br />
70
+ <StackItem />
71
+ <StackItem>
72
+ <SCCProductPicker
73
+ sccProducts={sccProductsInit}
74
+ sccAccountId={sccAccountId}
75
+ editProductId={productToEdit}
76
+ handleSubscribeCallback={handleSubscribeCallback}
77
+ />
78
+ </StackItem>
79
+ </Stack>
80
+ ) : (
81
+ <EmptySccProducts sccAccountId={sccAccountId} canCreate={canCreate} />
82
+ );
83
+ };
84
+
85
+ SCCProductPage.propTypes = {
86
+ canCreate: PropTypes.bool,
87
+ sccAccountId: PropTypes.number.isRequired,
88
+ sccProductsInit: PropTypes.array,
89
+ };
90
+
91
+ SCCProductPage.defaultProps = {
92
+ canCreate: false,
93
+ sccProductsInit: [],
94
+ };
95
+
96
+ export default SCCProductPage;