foreman_scc_manager 1.8.20 → 2.0.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 (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;