foreman_scc_manager 1.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 (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