foreman_scc_manager 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +619 -0
- data/README.md +38 -0
- data/Rakefile +47 -0
- data/app/assets/javascripts/foreman_scc_manager/scc_accounts.js.coffee +46 -0
- data/app/controllers/scc_accounts_controller.rb +118 -0
- data/app/controllers/scc_products_controller.rb +24 -0
- data/app/helpers/scc_accounts_helper.rb +2 -0
- data/app/helpers/scc_product_helper.rb +2 -0
- data/app/lib/actions/scc_manager/subscribe_product.rb +72 -0
- data/app/lib/actions/scc_manager/sync.rb +36 -0
- data/app/lib/actions/scc_manager/sync_products.rb +37 -0
- data/app/lib/actions/scc_manager/sync_repositories.rb +36 -0
- data/app/lib/scc_manager.rb +37 -0
- data/app/models/scc_account.rb +100 -0
- data/app/models/scc_extending.rb +4 -0
- data/app/models/scc_product.rb +46 -0
- data/app/models/scc_repository.rb +25 -0
- data/app/views/scc_accounts/_form.html.erb +36 -0
- data/app/views/scc_accounts/edit.html.erb +3 -0
- data/app/views/scc_accounts/index.html.erb +38 -0
- data/app/views/scc_accounts/new.html.erb +3 -0
- data/app/views/scc_accounts/show.html.erb +41 -0
- data/config/routes.rb +17 -0
- data/db/migrate/20170221100619_create_scc_accounts.rb +11 -0
- data/db/migrate/20170227103408_create_scc_products.rb +16 -0
- data/db/migrate/20170301131641_create_scc_repositories.rb +18 -0
- data/db/migrate/20170301141330_create_scc_products_scc_repositories_join_table.rb +8 -0
- data/db/migrate/20170301163451_add_product_type_to_scc_product.rb +5 -0
- data/db/migrate/20170302082912_remove_repositories_from_scc_products.rb +5 -0
- data/db/migrate/20170302121542_create_scc_extendings.rb +12 -0
- data/db/migrate/20170303085304_add_organization_to_scc_account.rb +22 -0
- data/db/migrate/20170303131704_add_product_id_to_scc_product.rb +6 -0
- data/db/migrate/20170307092057_add_synced_to_scc_account.rb +5 -0
- data/db/migrate/20170418132648_add_name_to_scc_account.rb +5 -0
- data/db/migrate/20170505063726_add_sync_status_to_scc_account.rb +5 -0
- data/lib/foreman_scc_manager.rb +4 -0
- data/lib/foreman_scc_manager/engine.rb +80 -0
- data/lib/foreman_scc_manager/version.rb +3 -0
- data/lib/tasks/foreman_scc_manager_tasks.rake +47 -0
- data/locale/Makefile +60 -0
- data/locale/action_names.rb +61 -0
- data/locale/de/LC_MESSAGES/foreman_scc_manager.mo +0 -0
- data/locale/de/foreman_scc_manager.po +265 -0
- data/locale/en/LC_MESSAGES/foreman_scc_manager.mo +0 -0
- data/locale/en/foreman_scc_manager.po +265 -0
- data/locale/foreman_scc_manager.pot +345 -0
- data/locale/gemspec.rb +2 -0
- data/test/factories/foreman_scc_manager_factories.rb +5 -0
- data/test/test_plugin_helper.rb +6 -0
- data/test/unit/foreman_scc_manager_test.rb +11 -0
- metadata +154 -0
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# ForemanSccManager
|
2
|
+
|
3
|
+
Foreman plugin to sync SUSE Customer Center products and repositories into Katello
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
|
8
|
+
for how to install Foreman plugins
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
*Usage here*
|
13
|
+
|
14
|
+
## TODO
|
15
|
+
|
16
|
+
*Todo list here*
|
17
|
+
|
18
|
+
## Contributing
|
19
|
+
|
20
|
+
Fork and send a Pull Request. Thanks!
|
21
|
+
|
22
|
+
## Copyright
|
23
|
+
|
24
|
+
Copyright (c) 2017 ATIX AG
|
25
|
+
|
26
|
+
This program is free software: you can redistribute it and/or modify
|
27
|
+
it under the terms of the GNU General Public License as published by
|
28
|
+
the Free Software Foundation, either version 3 of the License, or
|
29
|
+
(at your option) any later version.
|
30
|
+
|
31
|
+
This program is distributed in the hope that it will be useful,
|
32
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
33
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
34
|
+
GNU General Public License for more details.
|
35
|
+
|
36
|
+
You should have received a copy of the GNU General Public License
|
37
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
38
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'ForemanSccManager'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
task default: :test
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'rubocop/rake_task'
|
40
|
+
RuboCop::RakeTask.new
|
41
|
+
rescue => _
|
42
|
+
puts 'Rubocop not loaded.'
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default do
|
46
|
+
Rake::Task['rubocop'].execute
|
47
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
scc_products_after_checked = (target) ->
|
2
|
+
if target.parentNode.dataset.parent
|
3
|
+
parent = $("#" + target.parentNode.dataset.parent + " input")[0]
|
4
|
+
if !parent.checked && !parent.disabled
|
5
|
+
parent.checked = true
|
6
|
+
scc_products_after_checked(parent)
|
7
|
+
|
8
|
+
scc_products_after_unchecked = (target) ->
|
9
|
+
$("span.scc_product_checkbox input", target.parentNode.parentNode).each (index, child) ->
|
10
|
+
if child.checked && !child.disabled
|
11
|
+
child.checked = false
|
12
|
+
scc_products_after_unchecked(child)
|
13
|
+
|
14
|
+
$ ->
|
15
|
+
$("body").on "change", "span.scc_product_checkbox input", (event) ->
|
16
|
+
target = event.target
|
17
|
+
if target.checked
|
18
|
+
scc_products_after_checked target
|
19
|
+
else
|
20
|
+
scc_products_after_unchecked target
|
21
|
+
$("body").on "click", "a.edit_deferrer", (event) ->
|
22
|
+
event.preventDefault()
|
23
|
+
$("a.edit_deferree", $(event.target).closest("tr"))[0].click()
|
24
|
+
$("body").on "click", "#test_scc_connection_btn", (event) ->
|
25
|
+
$('.tab-error').removeClass('tab-error')
|
26
|
+
$('#connection_test_result')[0].innerHTML = ''
|
27
|
+
$('#test_scc_connection_indicator').show()
|
28
|
+
$.ajax event.target.parentNode.dataset['url'],
|
29
|
+
type: 'PUT'
|
30
|
+
dataType: 'JSON'
|
31
|
+
data: $('form').serialize()
|
32
|
+
success: (result) ->
|
33
|
+
$('#test_scc_connection_btn').addClass('btn-success')
|
34
|
+
$('#test_scc_connection_btn').removeClass('btn-default')
|
35
|
+
$('#test_scc_connection_btn').removeClass('btn-danger')
|
36
|
+
error: (result) ->
|
37
|
+
$('#test_scc_connection_btn').addClass('btn-danger')
|
38
|
+
$('#test_scc_connection_btn').removeClass('btn-default')
|
39
|
+
$('#test_scc_connection_btn').removeClass('btn-success')
|
40
|
+
$('#scc_account_login').closest('.form-group').addClass('tab-error')
|
41
|
+
$('#scc_account_password').closest('.form-group').addClass('tab-error')
|
42
|
+
$('#scc_account_base_url').closest('.form-group').addClass('tab-error')
|
43
|
+
$('#connection_test_result')[0].innerHTML = 'Connection test failed!'
|
44
|
+
$('#scc_account_login').focus()
|
45
|
+
complete: (result) ->
|
46
|
+
$('#test_scc_connection_indicator').hide()
|
@@ -0,0 +1,118 @@
|
|
1
|
+
class SccAccountsController < ApplicationController
|
2
|
+
before_filter :find_resource, only: [:show, :edit, :update, :destroy, :sync, :bulk_subscribe]
|
3
|
+
include Api::TaxonomyScope
|
4
|
+
include Foreman::Controller::AutoCompleteSearch
|
5
|
+
|
6
|
+
# GET /scc_accounts
|
7
|
+
def index
|
8
|
+
@scc_accounts = resource_base.search_for(params[:search], order: params[:order])
|
9
|
+
.paginate(page: params[:page])
|
10
|
+
end
|
11
|
+
|
12
|
+
# GET /scc_accounts/new
|
13
|
+
def new
|
14
|
+
@scc_account = SccAccount.new
|
15
|
+
@scc_account.organization = Organization.current
|
16
|
+
end
|
17
|
+
|
18
|
+
# POST /scc_accounts
|
19
|
+
def create
|
20
|
+
@scc_account = SccAccount.new(scc_account_params)
|
21
|
+
if @scc_account.save
|
22
|
+
process_success
|
23
|
+
else
|
24
|
+
process_error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# GET /scc_accounts/1/edit
|
29
|
+
def edit; end
|
30
|
+
|
31
|
+
# POST /scc_accounts/test_connection
|
32
|
+
def test_connection
|
33
|
+
@scc_account = SccAccount.new(scc_account_params)
|
34
|
+
if params[:scc_account_id].present? && scc_account_params[:password].empty?
|
35
|
+
@scc_account.password = SccAccount.find_by!(id: params[:scc_account_id]).password
|
36
|
+
end
|
37
|
+
respond_to do |format|
|
38
|
+
if @scc_account.test_connection
|
39
|
+
format.json { render json: nil, status: :ok }
|
40
|
+
else
|
41
|
+
format.json { render json: nil, status: 404 }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# PATCH/PUT /scc_accounts/1
|
47
|
+
def update
|
48
|
+
if @scc_account.update(scc_account_params)
|
49
|
+
process_success
|
50
|
+
else
|
51
|
+
process_error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# DELETE /scc_accounts/1
|
56
|
+
def destroy
|
57
|
+
if @scc_account.destroy
|
58
|
+
process_success
|
59
|
+
else
|
60
|
+
process_error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# PUT /scc_accounts/1/sync
|
65
|
+
def sync
|
66
|
+
begin
|
67
|
+
sync_task = ForemanTasks.async_task(::Actions::SccManager::Sync, @scc_account)
|
68
|
+
notice _("Sync task started.")
|
69
|
+
rescue ::Foreman::Exception => e
|
70
|
+
error _("Failed to add task to queue: %s") % e.to_s
|
71
|
+
rescue ForemanTasks::Lock::LockConflict => e
|
72
|
+
error _("Lock on SCC account already taken: %s") % e.to_s
|
73
|
+
ensure
|
74
|
+
redirect_to scc_accounts_path
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def bulk_subscribe
|
79
|
+
begin
|
80
|
+
scc_products_to_subscribe =
|
81
|
+
@scc_account.scc_products.where(id: scc_bulk_subscribe_params[:scc_subscribe_product_ids])
|
82
|
+
ForemanTasks.async_task(::Actions::BulkAction,
|
83
|
+
::Actions::SccManager::SubscribeProduct,
|
84
|
+
scc_products_to_subscribe)
|
85
|
+
notice _("Task to subscribe products started.")
|
86
|
+
rescue ::Foreman::Exception => e
|
87
|
+
error _("Failed to add task to queue: %s") % e.to_s
|
88
|
+
rescue ForemanTasks::Lock::LockConflict => e
|
89
|
+
error _("Lock on SCC account already taken: %s") % e.to_s
|
90
|
+
ensure
|
91
|
+
redirect_to scc_accounts_path
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Use callbacks to share common setup or constraints between actions.
|
98
|
+
# Only allow a trusted parameter "white list" through.
|
99
|
+
def scc_account_params
|
100
|
+
params[:scc_account].delete(:password) if params[:scc_account][:password].blank?
|
101
|
+
params.require(:scc_account).permit(:name, :login, :password, :base_url, :organization_id)
|
102
|
+
end
|
103
|
+
|
104
|
+
def scc_bulk_subscribe_params
|
105
|
+
params.require(:scc_account).permit(:scc_subscribe_product_ids => [])
|
106
|
+
end
|
107
|
+
|
108
|
+
def action_permission
|
109
|
+
case params[:action]
|
110
|
+
when 'sync', 'test_connection'
|
111
|
+
:sync
|
112
|
+
when 'bulk_subscribe'
|
113
|
+
:bulk_subscribe
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class SccProductsController < ApplicationController
|
2
|
+
before_filter :find_resource, only: [:show, :subscribe, :unsubscribe]
|
3
|
+
include Api::TaxonomyScope
|
4
|
+
include Foreman::Controller::AutoCompleteSearch
|
5
|
+
|
6
|
+
def index
|
7
|
+
# TODO: Organization...
|
8
|
+
@scc_products = SccProduct.all
|
9
|
+
respond_to do |format|
|
10
|
+
format.json { render json: @scc_products.to_json }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def show
|
15
|
+
respond_to do |format|
|
16
|
+
format.json { render json: @scc_product.to_json }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def subscribe
|
21
|
+
@scc_product.subscribe
|
22
|
+
redirect_to @scc_product.scc_account
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Actions
|
2
|
+
module SccManager
|
3
|
+
class SubscribeProduct < Actions::EntryAction
|
4
|
+
def plan(scc_product)
|
5
|
+
raise _('Product already subscribed!') if scc_product.product
|
6
|
+
::Foreman::Logging.logger('foreman_scc_manager')
|
7
|
+
.info("Initiating subscription for SccProduct '#{scc_product.friendly_name}'.")
|
8
|
+
sequence do
|
9
|
+
product_create_action = plan_action(CreateProduct,
|
10
|
+
product_name: scc_product.uniq_name,
|
11
|
+
product_description: scc_product.description,
|
12
|
+
organization_id: scc_product.organization.id)
|
13
|
+
scc_product.scc_repositories.each do |repo|
|
14
|
+
uniq_name = scc_product.uniq_name + ' ' + repo.description
|
15
|
+
arch = scc_product.arch || 'noarch'
|
16
|
+
plan_action(CreateRepository,
|
17
|
+
:product_id => product_create_action.output[:product_id],
|
18
|
+
:uniq_name => uniq_name,
|
19
|
+
:url => repo.full_url,
|
20
|
+
:arch => arch)
|
21
|
+
end
|
22
|
+
action_subject(scc_product, product_id: product_create_action.output[:product_id])
|
23
|
+
plan_self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def finalize
|
28
|
+
scc_product = SccProduct.find(input[:scc_product][:id])
|
29
|
+
product = ::Katello::Product.find(input[:product_id])
|
30
|
+
scc_product.update!(product: product)
|
31
|
+
end
|
32
|
+
|
33
|
+
def humanized_name
|
34
|
+
_('Subscribe SCC Product')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class CreateProduct < Actions::Base
|
39
|
+
middleware.use ::Actions::Middleware::KeepCurrentUser
|
40
|
+
include ::Dynflow::Action::WithSubPlans
|
41
|
+
|
42
|
+
def create_sub_plans
|
43
|
+
product = ::Katello::Product.new
|
44
|
+
product.name = input[:product_name]
|
45
|
+
product.description = input[:product_description]
|
46
|
+
trigger(::Actions::Katello::Product::Create,
|
47
|
+
product,
|
48
|
+
Organization.find(input[:organization_id])).tap do
|
49
|
+
output[:product_id] = product.id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class CreateRepository < Actions::Base
|
55
|
+
middleware.use ::Actions::Middleware::KeepCurrentUser
|
56
|
+
include ::Dynflow::Action::WithSubPlans
|
57
|
+
|
58
|
+
def create_sub_plans
|
59
|
+
product = ::Katello::Product.find(input[:product_id])
|
60
|
+
uniq_name = input[:uniq_name]
|
61
|
+
label = ::Katello::Util::Model.labelize(uniq_name)
|
62
|
+
unprotected = true
|
63
|
+
gpg_key = product.gpg_key
|
64
|
+
repository = product.add_repo(label, uniq_name, input[:url], 'yum', unprotected, gpg_key)
|
65
|
+
repository.arch = input[:arch]
|
66
|
+
repository.mirror_on_sync = true
|
67
|
+
repository.verify_ssl_on_sync = true
|
68
|
+
trigger(::Actions::Katello::Repository::Create, repository, false, false)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Actions
|
2
|
+
module SccManager
|
3
|
+
class Sync < Actions::EntryAction
|
4
|
+
def plan(scc_account)
|
5
|
+
::Foreman::Logging.logger('foreman_scc_manager')
|
6
|
+
.info("Initiating 'sync' for SccAccount '#{scc_account.name}'.")
|
7
|
+
action_subject(scc_account)
|
8
|
+
sequence do
|
9
|
+
sync_repo_action = plan_action(::Actions::SccManager::SyncRepositories, scc_account)
|
10
|
+
sync_prod_action = plan_action(::Actions::SccManager::SyncProducts, scc_account)
|
11
|
+
plan_self(repo_status: sync_repo_action.output[:status], prod_status: sync_prod_action.output[:status])
|
12
|
+
end
|
13
|
+
scc_account.update! sync_status: 'running'
|
14
|
+
end
|
15
|
+
|
16
|
+
def finalize
|
17
|
+
scc_account = SccAccount.find(input[:scc_account][:id])
|
18
|
+
if input[:repo_status] == 'SUCCESS' and input[:prod_status] == 'SUCCESS'
|
19
|
+
scc_account.sync_status = 'successful'
|
20
|
+
scc_account.synced = DateTime.current
|
21
|
+
else
|
22
|
+
scc_account.sync_status = 'failed'
|
23
|
+
end
|
24
|
+
scc_account.save!
|
25
|
+
end
|
26
|
+
|
27
|
+
def rescue_strategy
|
28
|
+
Dynflow::Action::Rescue::Fail
|
29
|
+
end
|
30
|
+
|
31
|
+
def humanized_name
|
32
|
+
_('Sync SUSE subscriptions')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Actions
|
2
|
+
module SccManager
|
3
|
+
class SyncProducts < Actions::EntryAction
|
4
|
+
def plan(scc_account)
|
5
|
+
action_subject(scc_account)
|
6
|
+
plan_self(id: scc_account.id,
|
7
|
+
base_url: scc_account.base_url,
|
8
|
+
login: scc_account.login,
|
9
|
+
password: scc_account.password)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
output[:status] = 'SUCCESS'
|
14
|
+
begin
|
15
|
+
output[:data] = ::SccManager.get_scc_data(input.fetch(:base_url),
|
16
|
+
'/connect/organizations/products',
|
17
|
+
input.fetch(:login),
|
18
|
+
input.fetch(:password))
|
19
|
+
rescue
|
20
|
+
output[:status] = 'FAILURE'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def finalize
|
25
|
+
SccAccount.find(input.fetch(:id)).update_scc_products(output.fetch(:data)) if output[:status] == 'SUCCESS'
|
26
|
+
end
|
27
|
+
|
28
|
+
def rescue_strategy
|
29
|
+
Dynflow::Action::Rescue::Fail
|
30
|
+
end
|
31
|
+
|
32
|
+
def humanized_name
|
33
|
+
_('Sync SUSE subscriptions (Products)')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Actions
|
2
|
+
module SccManager
|
3
|
+
class SyncRepositories < Actions::EntryAction
|
4
|
+
def plan(scc_account)
|
5
|
+
action_subject(scc_account)
|
6
|
+
plan_self(base_url: scc_account.base_url,
|
7
|
+
login: scc_account.login,
|
8
|
+
password: scc_account.password)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
output[:status] = 'SUCCESS'
|
13
|
+
begin
|
14
|
+
output[:data] = ::SccManager.get_scc_data(input[:base_url],
|
15
|
+
'/connect/organizations/repositories',
|
16
|
+
input[:login],
|
17
|
+
input[:password])
|
18
|
+
rescue
|
19
|
+
output[:status] = 'FAILURE'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def finalize
|
24
|
+
SccAccount.find(input[:scc_account][:id]).update_scc_repositories(output[:data]) if output[:status] == 'SUCCESS'
|
25
|
+
end
|
26
|
+
|
27
|
+
def rescue_strategy
|
28
|
+
Dynflow::Action::Rescue::Fail
|
29
|
+
end
|
30
|
+
|
31
|
+
def humanized_name
|
32
|
+
_('Sync SUSE subscriptions (Repositories)')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|