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
@@ -0,0 +1,37 @@
|
|
1
|
+
module SccManager
|
2
|
+
# adapted from https://github.com/SUSE/connect
|
3
|
+
def self.get_scc_data(base_url, rest_url, login, password)
|
4
|
+
if SETTINGS[:katello][:cdn_proxy] && SETTINGS[:katello][:cdn_proxy][:host]
|
5
|
+
proxy_config = SETTINGS[:katello][:cdn_proxy]
|
6
|
+
uri = URI('')
|
7
|
+
|
8
|
+
uri.scheme = URI.parse(proxy_config[:host]).scheme
|
9
|
+
uri.host = URI.parse(proxy_config[:host]).host
|
10
|
+
uri.port = proxy_config[:port].try(:to_s)
|
11
|
+
uri.user = proxy_config[:user].try(:to_s)
|
12
|
+
uri.password = proxy_config[:password].try(:to_s)
|
13
|
+
|
14
|
+
RestClient.proxy = uri.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
url = base_url + rest_url
|
18
|
+
auth_header = { Authorization: 'Basic ' + Base64.encode64("#{login}:#{password}").chomp,
|
19
|
+
Accept: 'application/vnd.scc.suse.com.v4+json' }
|
20
|
+
results = []
|
21
|
+
loop do
|
22
|
+
response = RestClient.get url, auth_header
|
23
|
+
raise 'Connection to SUSE costomer center failed.' unless response.code == 200
|
24
|
+
links = (response.headers[:link] || '').split(', ').map do |link|
|
25
|
+
href, rel = /<(.*?)>; rel="(\w+)"/.match(link).captures
|
26
|
+
[rel.to_sym, href]
|
27
|
+
end
|
28
|
+
links = Hash[*links.flatten]
|
29
|
+
results += JSON.parse response
|
30
|
+
url = links[:next]
|
31
|
+
break unless url
|
32
|
+
end
|
33
|
+
results
|
34
|
+
ensure
|
35
|
+
RestClient.proxy = ''
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class SccAccount < ActiveRecord::Base
|
2
|
+
include Authorizable
|
3
|
+
include Encryptable
|
4
|
+
include ForemanTasks::Concerns::ActionSubject
|
5
|
+
encrypts :password
|
6
|
+
|
7
|
+
SYNC_STATI = [ nil, 'running', 'successful', 'failed' ]
|
8
|
+
|
9
|
+
self.include_root_in_json = false
|
10
|
+
|
11
|
+
belongs_to :organization
|
12
|
+
has_many :scc_products, dependent: :destroy
|
13
|
+
has_many :scc_repositories, dependent: :destroy
|
14
|
+
|
15
|
+
validates_lengths_from_database
|
16
|
+
validates :name, presence: true
|
17
|
+
validates :organization, presence: true
|
18
|
+
validates :login, presence: true
|
19
|
+
validates :password, presence: true
|
20
|
+
validates :base_url, presence: true
|
21
|
+
validates_inclusion_of :sync_status, in: SYNC_STATI
|
22
|
+
|
23
|
+
default_scope -> { order(:login) }
|
24
|
+
|
25
|
+
scoped_search on: :login, complete_value: true
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
name
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_sync_status
|
32
|
+
if sync_status == nil
|
33
|
+
return _('never synced')
|
34
|
+
elsif sync_status == 'running'
|
35
|
+
return _('sync in progress')
|
36
|
+
elsif sync_status == 'successful'
|
37
|
+
return synced
|
38
|
+
elsif sync_status == 'failed'
|
39
|
+
return _('last sync failed')
|
40
|
+
end
|
41
|
+
''
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_connection
|
45
|
+
begin
|
46
|
+
SccManager::get_scc_data(base_url, '/connect/organizations/subscriptions', login, password)
|
47
|
+
true
|
48
|
+
rescue
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def update_scc_repositories(upstream_repositories)
|
54
|
+
upstream_repo_ids = []
|
55
|
+
SccProduct.transaction do
|
56
|
+
# import repositories
|
57
|
+
upstream_repositories.each do |ur|
|
58
|
+
cached_repository = scc_repositories.find_or_initialize_by(scc_id: ur['id'])
|
59
|
+
cached_repository.name = ur['name']
|
60
|
+
cached_repository.distro_target = ur['distro_target']
|
61
|
+
cached_repository.description = ur['description']
|
62
|
+
cached_repository.url, cached_repository.token = ur['url'].split('?')
|
63
|
+
cached_repository.enabled = ur['enabled']
|
64
|
+
cached_repository.autorefresh = ur['autorefresh']
|
65
|
+
cached_repository.installer_updates = ur['installer_updates']
|
66
|
+
cached_repository.save!
|
67
|
+
upstream_repo_ids << cached_repository.id
|
68
|
+
end
|
69
|
+
# delete repositories beeing removed upstream
|
70
|
+
scc_repositories.where(id: scc_repository_ids - upstream_repo_ids).destroy_all.count
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def update_scc_products(upstream_products)
|
75
|
+
upstream_product_ids = []
|
76
|
+
SccProduct.transaction do
|
77
|
+
# import products
|
78
|
+
upstream_products.each do |up|
|
79
|
+
cached_product = scc_products.find_or_initialize_by(scc_id: up['id'])
|
80
|
+
cached_product.name = up['name']
|
81
|
+
cached_product.version = up['version']
|
82
|
+
cached_product.arch = up['arch']
|
83
|
+
cached_product.description = up['description']
|
84
|
+
cached_product.friendly_name = up['friendly_name']
|
85
|
+
cached_product.product_type = up['product_type']
|
86
|
+
cached_product.scc_repositories =
|
87
|
+
scc_repositories.where(scc_id: up['repositories'].map { |repo| repo['id'] })
|
88
|
+
cached_product.save!
|
89
|
+
upstream_product_ids << cached_product.id
|
90
|
+
end
|
91
|
+
# delete products beeing removed upstream
|
92
|
+
scc_products.where(id: scc_product_ids - upstream_product_ids).destroy_all.count
|
93
|
+
# rewire product to product relationships
|
94
|
+
upstream_products.each do |up|
|
95
|
+
extensions = scc_products.where(scc_id: up['extensions'].map { |ext| ext['id'] })
|
96
|
+
scc_products.find_by!(scc_id: up['id']).update!(scc_extensions: extensions)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class SccProduct < ActiveRecord::Base
|
2
|
+
include Authorizable
|
3
|
+
include ForemanTasks::Concerns::ActionSubject
|
4
|
+
|
5
|
+
self.include_root_in_json = false
|
6
|
+
|
7
|
+
belongs_to :scc_account
|
8
|
+
belongs_to :product, class_name: 'Katello::Product'
|
9
|
+
has_one :organization, through: :scc_account
|
10
|
+
has_and_belongs_to_many :scc_repositories
|
11
|
+
has_many :scc_extendings, dependent: :destroy
|
12
|
+
has_many :scc_extensions, through: :scc_extendings
|
13
|
+
has_many :inverse_scc_extendings,
|
14
|
+
dependent: :destroy,
|
15
|
+
class_name: :SccExtending,
|
16
|
+
foreign_key: :scc_extension_id
|
17
|
+
has_many :inverse_scc_extensions, through: :inverse_scc_extendings, source: :scc_product
|
18
|
+
|
19
|
+
default_scope -> { order(:name) }
|
20
|
+
scoped_search on: :name, complete_value: true
|
21
|
+
|
22
|
+
def uniq_name
|
23
|
+
return "#{scc_id} " + friendly_name;
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscribe
|
27
|
+
raise 'Product already subscribed!' if product
|
28
|
+
new_product = Katello::Product.new
|
29
|
+
new_product.name = uniq_name
|
30
|
+
new_product.description = description
|
31
|
+
ForemanTasks.sync_task(::Actions::Katello::Product::Create, new_product, scc_account.organization)
|
32
|
+
new_product.reload
|
33
|
+
scc_repositories.each do |repo|
|
34
|
+
uniq_repo_name = uniq_name + ' ' + repo.description
|
35
|
+
label = Katello::Util::Model.labelize(uniq_repo_name)
|
36
|
+
unprotected = true
|
37
|
+
gpg_key = new_product.gpg_key
|
38
|
+
new_repo = new_product.add_repo(label, uniq_repo_name, repo.full_url, 'yum', unprotected, gpg_key)
|
39
|
+
new_repo.arch = arch || 'noarch'
|
40
|
+
new_repo.mirror_on_sync = true
|
41
|
+
new_repo.verify_ssl_on_sync = true
|
42
|
+
ForemanTasks.sync_task(::Actions::Katello::Repository::Create, new_repo, false, false)
|
43
|
+
end
|
44
|
+
update!(product: new_product)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class SccRepository < ActiveRecord::Base
|
2
|
+
after_commit :token_changed_callback
|
3
|
+
|
4
|
+
self.include_root_in_json = false
|
5
|
+
|
6
|
+
belongs_to :scc_account
|
7
|
+
has_one :organization, through: :scc_account
|
8
|
+
has_and_belongs_to_many :scc_products
|
9
|
+
|
10
|
+
def full_url
|
11
|
+
token.blank? ? url : url + '?' + token
|
12
|
+
end
|
13
|
+
|
14
|
+
def token_changed_callback
|
15
|
+
User.current = User.anonymous_admin unless User.current
|
16
|
+
scc_products.where.not(product: nil).find_each do |sp|
|
17
|
+
reponame = sp.friendly_name + ' ' + description
|
18
|
+
repository = sp.product.repositories.find_by(name: reponame)
|
19
|
+
unless repository.url == full_url
|
20
|
+
::Foreman::Logging.logger('foreman_scc_manager').info "Update URL-token for repository '#{reponame}'."
|
21
|
+
ForemanTasks.async_task(::Actions::Katello::Repository::Update, repository, url: full_url)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<% javascript 'foreman_scc_manager/scc_accounts' %>
|
2
|
+
|
3
|
+
<%= form_for(@scc_account) do |f| %>
|
4
|
+
<%= base_errors_for @scc_account %>
|
5
|
+
<ul class="nav nav-tabs" data-tabs="tabs">
|
6
|
+
<li class="active"><a href="#primary" data-toggle="tab"><%= _("SUSE Customer Center account") %></a></li>
|
7
|
+
</ul>
|
8
|
+
|
9
|
+
<div class="tab-content">
|
10
|
+
<div class="tab-pane active" id="primary">
|
11
|
+
<div>
|
12
|
+
<%= text_f f, :name %>
|
13
|
+
<%= text_f f, :login %>
|
14
|
+
<%= password_f f, :password %>
|
15
|
+
<%= text_f f, :base_url, label: _('Base URL') %>
|
16
|
+
<div class='clearfix'>
|
17
|
+
<div class='form-group'>
|
18
|
+
<div class='col-md-2'></div>
|
19
|
+
<div class='col-md-4'>
|
20
|
+
<%= spinner_button_f(f, _('Test Connection'), '',
|
21
|
+
id: 'test_scc_connection_btn',
|
22
|
+
spinner_id: 'test_scc_connection_indicator',
|
23
|
+
class: 'btn-default',
|
24
|
+
'data-url': test_connection_scc_accounts_path(scc_account_id: @scc_account)) %>
|
25
|
+
</div>
|
26
|
+
<div class='col-md-2'>
|
27
|
+
<span id='connection_test_result'></span>
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
<%= f.hidden_field :organization_id %>
|
32
|
+
<%= submit_or_cancel f %>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
<% end %>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<% javascript 'foreman_scc_manager/scc_accounts' %>
|
2
|
+
<% title _("SUSE subscriptions") %>
|
3
|
+
<% title_actions new_link(_("Add SCC account")) %>
|
4
|
+
|
5
|
+
<table class="<%= table_css_classes 'table-two-pane table_fixed' %>">
|
6
|
+
<thead>
|
7
|
+
<tr>
|
8
|
+
<th class="col-md-4"><%= sort :name %></th>
|
9
|
+
<th class="col-md-3"><%= _("Products") %></th>
|
10
|
+
<th class="col-md-3"><%= _("Last synced") %></th>
|
11
|
+
<th class="col-md-2"><%= _("Actions") %></th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<% @scc_accounts.each do |scc_account| %>
|
16
|
+
<tr>
|
17
|
+
<td class="display-two-pane ellipsis">
|
18
|
+
<%= link_to_if_authorized(scc_account.name, hash_for_scc_account_path(:id => scc_account).merge(:auth_object => scc_account, :authorizer => authorizer)) %>
|
19
|
+
<%= link_to_if_authorized('', hash_for_edit_scc_account_path(:id => scc_account).merge(:auth_object => scc_account, :authorizer => authorizer), {visible: false, class: 'edit_deferree'}) %>
|
20
|
+
</td>
|
21
|
+
<td><%= scc_account.scc_products.count.to_s %></td>
|
22
|
+
<td><%= scc_account.get_sync_status %></td>
|
23
|
+
<td>
|
24
|
+
<%= action_buttons(
|
25
|
+
display_link_if_authorized(_("Sync"), hash_for_sync_scc_account_path(:id => scc_account).merge(:auth_object => scc_account, :authorizer => authorizer),
|
26
|
+
:method => :put),
|
27
|
+
display_link_if_authorized(_("Edit"), hash_for_edit_scc_account_path(:id => scc_account).merge(:auth_object => scc_account, :authorizer => authorizer),
|
28
|
+
class: 'edit_deferrer'),
|
29
|
+
display_delete_if_authorized(hash_for_scc_account_path(:id => scc_account).merge(:auth_object => scc_account, :authorizer => authorizer),
|
30
|
+
:data => { :confirm => _("Delete %s?") % scc_account.to_s })
|
31
|
+
) %>
|
32
|
+
</td>
|
33
|
+
</tr>
|
34
|
+
<% end %>
|
35
|
+
</tbody>
|
36
|
+
</table>
|
37
|
+
|
38
|
+
<%= will_paginate_with_info @scc_accounts %>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<% javascript 'foreman_scc_manager/scc_accounts' %>
|
2
|
+
<%= form_for([:bulk_subscribe, @scc_account], method: :put) do |f| %>
|
3
|
+
<% def render_list_node(f, scc_product, parent_id = nil) %>
|
4
|
+
<li>
|
5
|
+
<span class="scc_product_checkbox" id="<%= "scc_product_span_#{scc_product.id}" %>" <%= "data-parent=scc_product_span_#{parent_id}" if parent_id %>>
|
6
|
+
<% if scc_product.product %>
|
7
|
+
<%= check_box_tag("scc_account[scc_unsubscribe_product_ids][]", scc_product.id, true, disabled: true) %>
|
8
|
+
<% else %>
|
9
|
+
<%= check_box_tag("scc_account[scc_subscribe_product_ids][]", scc_product.id, false) %>
|
10
|
+
<% end %>
|
11
|
+
</span>
|
12
|
+
<%= scc_product.friendly_name %>
|
13
|
+
<% if scc_product.scc_extensions.any? %>
|
14
|
+
<ul>
|
15
|
+
<% scc_product.scc_extensions.order(:friendly_name).each do |scc_extension| %>
|
16
|
+
<% render_list_node(f, scc_extension, scc_product.id) %>
|
17
|
+
<% end %>
|
18
|
+
</ul>
|
19
|
+
<% end %>
|
20
|
+
</li>
|
21
|
+
<% end %>
|
22
|
+
<%= f.hidden_field :prevent_missing, value: 1 %>
|
23
|
+
<ul class="nav nav-tabs" data-tabs="tabs">
|
24
|
+
<li class="active"><a href="#primary" data-toggle="tab"><%= _("SUSE Customer Center") %></a></li>
|
25
|
+
</ul>
|
26
|
+
|
27
|
+
<div class="tab-content">
|
28
|
+
<div class="tab-pane active pre-scrollable" id="primary">
|
29
|
+
<ul>
|
30
|
+
<% if @scc_account.synced %>
|
31
|
+
<% @scc_account.scc_products.where(product_type: 'base').order(:friendly_name).each do |scc_product| %>
|
32
|
+
<% render_list_node(f, scc_product) %>
|
33
|
+
<% end %>
|
34
|
+
<% else %>
|
35
|
+
<%= _('Please sync your SUSE subscriptions first.') %>
|
36
|
+
<% end %>
|
37
|
+
</ul>
|
38
|
+
</div>
|
39
|
+
<%= submit_or_cancel f %>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
resources :scc_accounts do
|
3
|
+
collection do
|
4
|
+
put 'test_connection'
|
5
|
+
end
|
6
|
+
member do
|
7
|
+
put 'sync'
|
8
|
+
put 'bulk_subscribe'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
resources :scc_products, only: [:index, :show] do
|
12
|
+
member do
|
13
|
+
put 'subscribe'
|
14
|
+
put 'unsubscribe'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateSccAccounts < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scc_accounts do |t|
|
4
|
+
t.string :login, limit: 255
|
5
|
+
t.string :password, limit: 255
|
6
|
+
t.string :base_url, limit: 255, default: 'https://scc.suse.com'
|
7
|
+
|
8
|
+
t.timestamps null: false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreateSccProducts < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scc_products do |t|
|
4
|
+
t.references :scc_account, index: true, foreign_key: true
|
5
|
+
t.integer :scc_id, unique: true
|
6
|
+
t.string :name, limit: 255
|
7
|
+
t.string :version, limit: 63
|
8
|
+
t.string :arch, limit: 31
|
9
|
+
t.string :friendly_name, limit: 255
|
10
|
+
t.string :description
|
11
|
+
t.string :repositories
|
12
|
+
|
13
|
+
t.timestamps null: false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateSccRepositories < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scc_repositories do |t|
|
4
|
+
t.references :scc_account, index: true, foreign_key: true
|
5
|
+
t.integer :scc_id, unique: true
|
6
|
+
t.string :name, limit: 255
|
7
|
+
t.string :distro_target, limit: 255
|
8
|
+
t.string :description, limit: 255
|
9
|
+
t.string :url, limit: 255
|
10
|
+
t.string :token, limit: 255
|
11
|
+
t.boolean :enabled
|
12
|
+
t.boolean :autorefresh
|
13
|
+
t.boolean :installer_updates
|
14
|
+
|
15
|
+
t.timestamps null: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateSccExtendings < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :scc_extendings do |t|
|
4
|
+
t.references :scc_product, index: true, foreign_key: false, null: false
|
5
|
+
t.references :scc_extension, index: true, foreign_key: false, null: false
|
6
|
+
|
7
|
+
t.timestamps null: false
|
8
|
+
end
|
9
|
+
add_foreign_key :scc_extendings, :scc_products, column: :scc_product_id
|
10
|
+
add_foreign_key :scc_extendings, :scc_products, column: :scc_extension_id
|
11
|
+
end
|
12
|
+
end
|