foreman_scc_manager 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +619 -0
  3. data/README.md +38 -0
  4. data/Rakefile +47 -0
  5. data/app/assets/javascripts/foreman_scc_manager/scc_accounts.js.coffee +46 -0
  6. data/app/controllers/scc_accounts_controller.rb +118 -0
  7. data/app/controllers/scc_products_controller.rb +24 -0
  8. data/app/helpers/scc_accounts_helper.rb +2 -0
  9. data/app/helpers/scc_product_helper.rb +2 -0
  10. data/app/lib/actions/scc_manager/subscribe_product.rb +72 -0
  11. data/app/lib/actions/scc_manager/sync.rb +36 -0
  12. data/app/lib/actions/scc_manager/sync_products.rb +37 -0
  13. data/app/lib/actions/scc_manager/sync_repositories.rb +36 -0
  14. data/app/lib/scc_manager.rb +37 -0
  15. data/app/models/scc_account.rb +100 -0
  16. data/app/models/scc_extending.rb +4 -0
  17. data/app/models/scc_product.rb +46 -0
  18. data/app/models/scc_repository.rb +25 -0
  19. data/app/views/scc_accounts/_form.html.erb +36 -0
  20. data/app/views/scc_accounts/edit.html.erb +3 -0
  21. data/app/views/scc_accounts/index.html.erb +38 -0
  22. data/app/views/scc_accounts/new.html.erb +3 -0
  23. data/app/views/scc_accounts/show.html.erb +41 -0
  24. data/config/routes.rb +17 -0
  25. data/db/migrate/20170221100619_create_scc_accounts.rb +11 -0
  26. data/db/migrate/20170227103408_create_scc_products.rb +16 -0
  27. data/db/migrate/20170301131641_create_scc_repositories.rb +18 -0
  28. data/db/migrate/20170301141330_create_scc_products_scc_repositories_join_table.rb +8 -0
  29. data/db/migrate/20170301163451_add_product_type_to_scc_product.rb +5 -0
  30. data/db/migrate/20170302082912_remove_repositories_from_scc_products.rb +5 -0
  31. data/db/migrate/20170302121542_create_scc_extendings.rb +12 -0
  32. data/db/migrate/20170303085304_add_organization_to_scc_account.rb +22 -0
  33. data/db/migrate/20170303131704_add_product_id_to_scc_product.rb +6 -0
  34. data/db/migrate/20170307092057_add_synced_to_scc_account.rb +5 -0
  35. data/db/migrate/20170418132648_add_name_to_scc_account.rb +5 -0
  36. data/db/migrate/20170505063726_add_sync_status_to_scc_account.rb +5 -0
  37. data/lib/foreman_scc_manager.rb +4 -0
  38. data/lib/foreman_scc_manager/engine.rb +80 -0
  39. data/lib/foreman_scc_manager/version.rb +3 -0
  40. data/lib/tasks/foreman_scc_manager_tasks.rake +47 -0
  41. data/locale/Makefile +60 -0
  42. data/locale/action_names.rb +61 -0
  43. data/locale/de/LC_MESSAGES/foreman_scc_manager.mo +0 -0
  44. data/locale/de/foreman_scc_manager.po +265 -0
  45. data/locale/en/LC_MESSAGES/foreman_scc_manager.mo +0 -0
  46. data/locale/en/foreman_scc_manager.po +265 -0
  47. data/locale/foreman_scc_manager.pot +345 -0
  48. data/locale/gemspec.rb +2 -0
  49. data/test/factories/foreman_scc_manager_factories.rb +5 -0
  50. data/test/test_plugin_helper.rb +6 -0
  51. data/test/unit/foreman_scc_manager_test.rb +11 -0
  52. 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,4 @@
1
+ class SccExtending < ActiveRecord::Base
2
+ belongs_to :scc_product
3
+ belongs_to :scc_extension, class_name: :SccProduct
4
+ 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,3 @@
1
+ <% title(_("Edit %s") % @scc_account) %>
2
+
3
+ <%= render partial: 'form' %>
@@ -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,3 @@
1
+ <% title _("Add SUSE Customer Center Account") %>
2
+
3
+ <%= render partial: 'form' %>
@@ -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 %>
@@ -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,8 @@
1
+ class CreateSccProductsSccRepositoriesJoinTable < ActiveRecord::Migration
2
+ def change
3
+ create_join_table :scc_products, :scc_repositories do |t|
4
+ # t.index [:scc_product_id, :scc_repository_id]
5
+ # t.index [:scc_repository_id, :scc_product_id]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddProductTypeToSccProduct < ActiveRecord::Migration
2
+ def change
3
+ add_column :scc_products, :product_type, :string, limit: 63
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class RemoveRepositoriesFromSccProducts < ActiveRecord::Migration
2
+ def change
3
+ remove_column :scc_products, :repositories, :string
4
+ end
5
+ 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