foreman_content 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +113 -0
  3. data/Rakefile +111 -0
  4. data/app/assets/javascripts/content/content.js +12 -0
  5. data/app/assets/stylesheets/content/application.css +13 -0
  6. data/app/controllers/content/api/repositories_controller.rb +27 -0
  7. data/app/controllers/content/gpg_keys_controller.rb +42 -0
  8. data/app/controllers/content/products_controller.rb +51 -0
  9. data/app/controllers/content/repositories_controller.rb +57 -0
  10. data/app/helpers/content/application_helper.rb +5 -0
  11. data/app/helpers/content/repositories_helper.rb +56 -0
  12. data/app/models/content/custom_repository_paths.rb +27 -0
  13. data/app/models/content/environment_product.rb +19 -0
  14. data/app/models/content/gpg_key.rb +32 -0
  15. data/app/models/content/host_product.rb +20 -0
  16. data/app/models/content/hostgroup_product.rb +19 -0
  17. data/app/models/content/operatingsystem_repository.rb +19 -0
  18. data/app/models/content/orchestration/pulp.rb +93 -0
  19. data/app/models/content/product.rb +28 -0
  20. data/app/models/content/product_operatingsystem.rb +19 -0
  21. data/app/models/content/remote/pulp/repository.rb +46 -0
  22. data/app/models/content/repository.rb +70 -0
  23. data/app/models/content/validators/content_validator.rb +25 -0
  24. data/app/models/content/validators/description_format.rb +24 -0
  25. data/app/models/content/validators/name_format.rb +40 -0
  26. data/app/models/content/validators/no_trailing_space.rb +27 -0
  27. data/app/models/setting/content.rb +42 -0
  28. data/app/overrides/add_host_conent_tab.rb +9 -0
  29. data/app/overrides/add_hostgroup_conent_tab.rb +9 -0
  30. data/app/overrides/add_os_conent_tab.rb +9 -0
  31. data/app/services/content/pulp_event_handler.rb +25 -0
  32. data/app/views/content/gpg_keys/_form.html.erb +8 -0
  33. data/app/views/content/gpg_keys/edit.html.erb +3 -0
  34. data/app/views/content/gpg_keys/index.html.erb +20 -0
  35. data/app/views/content/gpg_keys/new.html.erb +3 -0
  36. data/app/views/content/products/_form.html.erb +26 -0
  37. data/app/views/content/products/_form_tab.html.erb +3 -0
  38. data/app/views/content/products/_host_tab_pane.html.erb +5 -0
  39. data/app/views/content/products/_hostgroup_tab_pane.html.erb +5 -0
  40. data/app/views/content/products/_operatingsystem_tab_pane.html.erb +5 -0
  41. data/app/views/content/products/edit.html.erb +3 -0
  42. data/app/views/content/products/index.html.erb +27 -0
  43. data/app/views/content/products/new.html.erb +3 -0
  44. data/app/views/content/products/welcome.html.erb +8 -0
  45. data/app/views/content/repositories/_form.html.erb +30 -0
  46. data/app/views/content/repositories/edit.html.erb +3 -0
  47. data/app/views/content/repositories/index.html.erb +32 -0
  48. data/app/views/content/repositories/new.html.erb +3 -0
  49. data/app/views/content/repositories/show.html.erb +98 -0
  50. data/app/views/content/repositories/welcome.html.erb +8 -0
  51. data/config/routes.rb +36 -0
  52. data/db/migrate/20130702140034_create_content_repositories.rb +19 -0
  53. data/db/migrate/20130702162629_create_content_products.rb +10 -0
  54. data/db/migrate/20130709001120_create_content_gpg_keys.rb +10 -0
  55. data/db/migrate/20130717032320_create_content_environments_products.rb +9 -0
  56. data/db/migrate/20130722084911_create_content_operatingsystem_repositories.rb +9 -0
  57. data/db/migrate/20130723084911_create_content_hostgroup_products.rb +9 -0
  58. data/db/migrate/20130723124911_create_content_host_products.rb +9 -0
  59. data/db/migrate/20130729032320_create_content_product_operatingsystems.rb +9 -0
  60. data/lib/content/engine.rb +59 -0
  61. data/lib/content/version.rb +3 -0
  62. data/lib/content_environment.rb +13 -0
  63. data/lib/content_home_helper_patch.rb +23 -0
  64. data/lib/content_host.rb +55 -0
  65. data/lib/content_hostgroup.rb +22 -0
  66. data/lib/content_operatingsystem.rb +17 -0
  67. data/lib/content_redhat.rb +36 -0
  68. data/lib/content_taxonomy.rb +18 -0
  69. data/lib/foreman_content.rb +4 -0
  70. data/lib/pulp_configuration.rb +23 -0
  71. data/lib/tasks/content_tasks.rake +4 -0
  72. data/test/content_test.rb +7 -0
  73. data/test/fixtures/content/products.yml +11 -0
  74. data/test/fixtures/content/repositories.yml +11 -0
  75. data/test/integration/navigation_test.rb +10 -0
  76. data/test/test_helper.rb +23 -0
  77. data/test/unit/content/description_format_validator_test.rb +20 -0
  78. data/test/unit/content/name_format_validator_test.rb +28 -0
  79. data/test/unit/content/product_validation_test.rb +18 -0
  80. data/test/unit/content/repositories_test.rb +7 -0
  81. metadata +194 -0
@@ -0,0 +1,5 @@
1
+ module Content
2
+ module ApplicationHelper
3
+ include Rails.application.routes.url_helpers
4
+ end
5
+ end
@@ -0,0 +1,56 @@
1
+ module Content
2
+ module RepositoriesHelper
3
+
4
+ def sync_status repo
5
+ status = repo.sync_status
6
+ status = if status && status.first
7
+ status.first[:state]
8
+ else
9
+ details = repo.retrieve_with_details
10
+ details[:importers][0][:last_sync] if details && details[:importers] && details[:importers][0]
11
+ end
12
+ "#{time_ago_in_words(status)} ago" rescue status
13
+ rescue
14
+ '-'
15
+ end
16
+
17
+ def last_publish details
18
+ return '' unless details[:distributors] && details[:distributors].first && published = details[:distributors].first[:last_publish]
19
+ "#{time_ago_in_words published} ago" rescue ''
20
+ end
21
+
22
+ def last_sync details
23
+ return '' unless details[:importers] && details[:importers].first && synced = details[:importers].first[:last_sync]
24
+ "#{time_ago_in_words synced} ago" rescue ''
25
+ end
26
+
27
+ def sync_history_times history
28
+ return {} unless history && history.first && summary = history.first['summary']
29
+ return {} unless summary[:comps]
30
+ {
31
+ :comps => summary[:comps][:time_total_sec],
32
+ :errata => summary[:errata][:errata_time_total_sec],
33
+ :packages => summary[:packages][:time_total_sec]
34
+ }
35
+ end
36
+
37
+ def sync_history_metrics history
38
+ return {} unless history && last = history.first
39
+ {
40
+ :updated => last[:updated_count],
41
+ :removed => last[:removed_count],
42
+ :added => last[:added_count]
43
+ }
44
+ end
45
+
46
+ def sync_history_status history
47
+ return {} unless history && last = history.first
48
+ {
49
+ 'result' => last[:result],
50
+ 'message' => last[:error_message] || last['summary']['error'],
51
+ 'completed' => ("#{time_ago_in_words(last[:completed])} ago" rescue '')
52
+ }
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ module Content
2
+ module CustomRepositoryPaths
3
+ def repo_path_from_content_path(environment, content_path)
4
+ content_path = content_path.sub(/^\//, "")
5
+ path_prefix = [environment.organization.label, environment.label].join("/")
6
+ "#{path_prefix}/#{content_path}"
7
+ end
8
+
9
+ # repo path for custom product repos (RH repo paths are derived from
10
+ # content url)
11
+ def custom_repo_path(org_label, environment_label, product_label, repo_label)
12
+ prefix = [org_label, environment_label].map { |x| x.gsub(/[^-\w]/, "_") }.join("/")
13
+ prefix + custom_content_path(product_label, repo_label)
14
+ end
15
+
16
+ def custom_content_path(product_label, repo_label)
17
+ parts = []
18
+ # We generate repo path only for custom product content. We add this
19
+ # constant string to avoid collisions with RH content. RH content url
20
+ # begins usually with something like "/content/dist/rhel/...".
21
+ # There we prefix custom content/repo url with "/custom/..."
22
+ parts << "custom"
23
+ parts += [product_label, repo_label]
24
+ "/" + parts.map { |x| x.gsub(/[^-\w]/, "_") }.join("/")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright 2013 Red Hat, Inc.
3
+ #
4
+ # This software is licensed to you under the GNU General Public
5
+ # License as published by the Free Software Foundation; either version
6
+ # 2 of the License (GPLv2) or (at your option) any later version.
7
+ # There is NO WARRANTY for this software, express or implied,
8
+ # including the implied warranties of MERCHANTABILITY,
9
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
10
+ # have received a copy of GPLv2 along with this software; if not, see
11
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
12
+ module Content
13
+ class EnvironmentProduct < ActiveRecord::Base
14
+ belongs_to :product
15
+ belongs_to :environment
16
+ validates_presence_of :product_id, :environment_id
17
+ validates_uniqueness_of :product_id, :scope => :environment_id
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ #
2
+ # Copyright 2013 Red Hat, Inc.
3
+ #
4
+ # This software is licensed to you under the GNU General Public
5
+ # License as published by the Free Software Foundation; either version
6
+ # 2 of the License (GPLv2) or (at your option) any later version.
7
+ # There is NO WARRANTY for this software, express or implied,
8
+ # including the implied warranties of MERCHANTABILITY,
9
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
10
+ # have received a copy of GPLv2 along with this software; if not, see
11
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
12
+
13
+ module Content
14
+ class GpgKey < ActiveRecord::Base
15
+ include ::Taxonomix
16
+ MAX_CONTENT_LENGTH = 100000
17
+
18
+ has_many :repositories
19
+ has_many :products
20
+
21
+ validates :name, :presence => true
22
+ validates_with Validators::NameFormat, :attributes => :name
23
+ validates :content, :presence => true
24
+ validates_with Validators::ContentValidator, :attributes => :content
25
+ validates_length_of :content, :maximum => MAX_CONTENT_LENGTH
26
+
27
+ validates_uniqueness_of :name, :message => N_("Label has already been taken")
28
+
29
+ scoped_search :on => :name
30
+
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ #
2
+ # Copyright 2013 Red Hat, Inc.
3
+ #
4
+ # This software is licensed to you under the GNU General Public
5
+ # License as published by the Free Software Foundation; either version
6
+ # 2 of the License (GPLv2) or (at your option) any later version.
7
+ # There is NO WARRANTY for this software, express or implied,
8
+ # including the implied warranties of MERCHANTABILITY,
9
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
10
+ # have received a copy of GPLv2 along with this software; if not, see
11
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
12
+ module Content
13
+ class HostProduct < ActiveRecord::Base
14
+ belongs_to :product
15
+ belongs_to :host
16
+ validates_presence_of :product_id, :host_id
17
+ validates_uniqueness_of :product_id, :scope => :host_id
18
+
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright 2013 Red Hat, Inc.
3
+ #
4
+ # This software is licensed to you under the GNU General Public
5
+ # License as published by the Free Software Foundation; either version
6
+ # 2 of the License (GPLv2) or (at your option) any later version.
7
+ # There is NO WARRANTY for this software, express or implied,
8
+ # including the implied warranties of MERCHANTABILITY,
9
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
10
+ # have received a copy of GPLv2 along with this software; if not, see
11
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
12
+ module Content
13
+ class HostgroupProduct < ActiveRecord::Base
14
+ belongs_to :product
15
+ belongs_to :hostgroup
16
+ validates_presence_of :product_id, :hostgroup_id
17
+ validates_uniqueness_of :product_id, :scope => :hostgroup_id
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright 2013 Red Hat, Inc.
3
+ #
4
+ # This software is licensed to you under the GNU General Public
5
+ # License as published by the Free Software Foundation; either version
6
+ # 2 of the License (GPLv2) or (at your option) any later version.
7
+ # There is NO WARRANTY for this software, express or implied,
8
+ # including the implied warranties of MERCHANTABILITY,
9
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
10
+ # have received a copy of GPLv2 along with this software; if not, see
11
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
12
+ module Content
13
+ class OperatingsystemRepository < ActiveRecord::Base
14
+ belongs_to :operatingsystem
15
+ belongs_to :repository
16
+ validates_presence_of :operatingsystem_id, :repository_id
17
+ validates_uniqueness_of :operatingsystem_id, :scope => :repository_id
18
+ end
19
+ end
@@ -0,0 +1,93 @@
1
+ module Content::Orchestration::Pulp
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ after_validation :queue_pulp
6
+ before_destroy :queue_pulp_destroy unless Rails.env == "test"
7
+ end
8
+
9
+ def orchestration_errors?
10
+ errors.empty?
11
+ end
12
+
13
+ protected
14
+
15
+ def queue_pulp
16
+ return unless pulp? and errors.empty?
17
+ new_record? ? queue_pulp_create : queue_pulp_update
18
+ end
19
+
20
+ private
21
+
22
+ def queue_pulp_create
23
+ logger.debug "Scheduling new Pulp Repository"
24
+ queue.create(:name => _("Create Pulp Repository for %s") % self, :priority => 10,
25
+ :action => [self, :set_pulp_repo])
26
+ queue.create(:name => _("Sync Pulp Repository %s") % self, :priority => 20,
27
+ :action => [self, :set_sync_pulp_repo])
28
+ end
29
+
30
+ def queue_pulp_update
31
+ end
32
+
33
+ def set_pulp_repo
34
+ Runcible::Extensions::Repository.create_with_importer_and_distributors(pulp_id,
35
+ pulp_importer,
36
+ [pulp_distributor],
37
+ { :display_name => relative_path,
38
+ :description => description })
39
+ rescue RestClient::BadRequest => e
40
+ raise (JSON.parse e.response)['error_message']
41
+ end
42
+
43
+ def del_pulp_repo
44
+ delete
45
+ end
46
+
47
+ def set_sync_pulp_repo
48
+ sync
49
+ end
50
+
51
+ def del_sync_pulp_repo
52
+ status = sync_status
53
+ return if status.blank? || status == ::PulpSyncStatus::Status::NOT_SYNCED
54
+ Runcible::Resources::Task.cancel(status.uuid)
55
+ end
56
+
57
+ def pulp_importer
58
+ options = {
59
+ :feed_url => feed
60
+ }
61
+
62
+ case content_type
63
+ when Content::Repository::YUM_TYPE, Content::Repository::KICKSTART_TYPE
64
+ Runcible::Extensions::YumImporter.new(options)
65
+ when Content::Repository::FILE_TYPE
66
+ Runcible::Extensions::IsoImporter.new(options)
67
+ else
68
+ raise "Unexpected repo type %s" % content_type
69
+ end
70
+ end
71
+
72
+ def pulp_distributor
73
+ case content_type
74
+ when Content::Repository::YUM_TYPE, Content::Repository::KICKSTART_TYPE
75
+ Runcible::Extensions::YumDistributor.new(relative_path, unprotected, true,
76
+ { :protected => true, :id => pulp_id,
77
+ :auto_publish => true })
78
+ when Content::Repository::FILE_TYPE
79
+ dist = Runcible::Extensions::IsoDistributor.new(true, true)
80
+ dist.auto_publish = true
81
+ dist
82
+ else
83
+ raise "Unexpected repo type %s" % content_type
84
+ end
85
+ end
86
+
87
+ def queue_pulp_destroy
88
+ return unless pulp? and errors.empty?
89
+ logger.debug _("Scheduling removal of Pulp Repository %s") % name
90
+ queue.create(:name => _("Delete Pulp repository for %s") % name, :priority => 50,
91
+ :action => [self, :del_pulp_repo])
92
+ end
93
+ end
@@ -0,0 +1,28 @@
1
+ module Content
2
+ class Product < ActiveRecord::Base
3
+ include ::Taxonomix
4
+
5
+ has_many :repositories
6
+ has_many :environment_products, :dependent => :destroy, :uniq=>true
7
+ has_many :environments, :through => :environment_products
8
+
9
+ has_many :product_operatingsystems, :dependent => :destroy, :uniq=>true
10
+ has_many :operatingsystems, :through => :product_operatingsystems
11
+
12
+ has_many :hostgroup_products, :dependent => :destroy, :uniq=>true
13
+ has_many :hostgroups, :through => :hostgroup_products
14
+
15
+ has_many :host_products, :dependent => :destroy, :uniq=>true
16
+ has_many :hosts, :through => :host_products
17
+
18
+ validates_with Validators::DescriptionFormat, :attributes => :description
19
+ validates :name, :presence => true
20
+ validates_with Validators::NameFormat, :attributes => :name
21
+ scoped_search :on => :name
22
+ scoped_search :in => :repositories, :on => :name, :rename => :repository, :complete_value => :true
23
+
24
+ def sync
25
+ self.repositories.map{ |repo| repo.sync }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # Copyright 2013 Red Hat, Inc.
3
+ #
4
+ # This software is licensed to you under the GNU General Public
5
+ # License as published by the Free Software Foundation; either version
6
+ # 2 of the License (GPLv2) or (at your option) any later version.
7
+ # There is NO WARRANTY for this software, express or implied,
8
+ # including the implied warranties of MERCHANTABILITY,
9
+ # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should
10
+ # have received a copy of GPLv2 along with this software; if not, see
11
+ # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
12
+ module Content
13
+ class ProductOperatingsystem < ActiveRecord::Base
14
+ belongs_to :product
15
+ belongs_to :operatingsystem
16
+ validates_presence_of :product_id, :operatingsystem_id
17
+ validates_uniqueness_of :product_id, :scope => :operatingsystem_id
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ module Content
2
+ module Remote
3
+ module Pulp
4
+ module Repository
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ after_initialize :initialize_pulp
9
+ end
10
+
11
+ def pulp?
12
+ @use_pulp ||= Setting.use_pulp and enabled?
13
+ end
14
+
15
+ def sync
16
+ Runcible::Resources::Repository.sync(pulp_id)
17
+ end
18
+
19
+ def retrieve_with_details
20
+ return unless pulp? && pulp_id
21
+ Runcible::Resources::Repository.retrieve(pulp_id, {:details => true})
22
+ end
23
+
24
+ def sync_status
25
+ return unless pulp? && pulp_id
26
+ Runcible::Extensions::Repository.sync_status(pulp_id)
27
+ end
28
+
29
+ def sync_history
30
+ return unless pulp? && pulp_id
31
+ Runcible::Extensions::Repository.sync_history(pulp_id)
32
+ end
33
+
34
+ def delete
35
+ Runcible::Resources::Repository.delete(pulp_id)
36
+ end
37
+
38
+ protected
39
+ def initialize_pulp
40
+ self.pulp_id ||= Foreman.uuid.gsub("-", '')
41
+ self.relative_path ||= custom_repo_path("acme_org", "library", product.name, name) if name
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ require 'content/orchestration/pulp'
2
+ # TODO: split into a custom and red hat repositories:
3
+ # as handling of repo creation/updates is different between them
4
+ module Content
5
+ class Repository < ActiveRecord::Base
6
+ include CustomRepositoryPaths
7
+ include Content::Remote::Pulp::Repository
8
+ include ::Orchestration
9
+ include Content::Orchestration::Pulp
10
+
11
+ YUM_TYPE = 'yum'
12
+ KICKSTART_TYPE = 'kickstart'
13
+ FILE_TYPE = 'iso'
14
+ TYPES = [YUM_TYPE, KICKSTART_TYPE, FILE_TYPE]
15
+
16
+ REPO_PREFIX = '/pulp/repos/'
17
+
18
+ belongs_to :product
19
+ belongs_to :gpg_key
20
+ belongs_to :architecture
21
+ has_many :operatingsystem_repositories, :dependent => :destroy, :uniq => true
22
+ has_many :operatingsystems, :through => :operatingsystem_repositories
23
+
24
+ validates :product, :presence => true
25
+ validates :name, :presence => true
26
+ validates_uniqueness_of :name, :scope => :product_id
27
+ validates_inclusion_of :content_type,
28
+ :in => TYPES,
29
+ :allow_blank => false,
30
+ :message => (_("Please select content type from one of the following: %s") % TYPES.join(', '))
31
+
32
+ scoped_search :on => [:name, :enabled], :complete_value => :true
33
+ scoped_search :in => :architecture, :on => :name, :rename => :architecture, :complete_value => :true
34
+ scoped_search :in => :operatingsystems, :on => :name, :rename => :os, :complete_value => :true
35
+ scoped_search :in => :product, :on => :name, :rename => :product, :complete_value => :true
36
+
37
+ # Repositories filtered by operating system architecture and environment.
38
+ # architecture_id is nil for noarch repositories.
39
+ scope :available_for_host, lambda { |host|
40
+ joins(:operatingsystems => :operatingsystem_repositories , :product => :environments).
41
+ where(:architecture_id => [nil, host.architecture_id],
42
+ :content_operatingsystem_repositories => { :operatingsystem_id => host.operatingsystem_id},
43
+ :content_environment_products => { :environment_id => host.environment_id }).
44
+ uniq}
45
+
46
+ #
47
+ scope :attached_to_host, lambda { |host|
48
+ where(:product_id => host.all_product_ids).available_for_host(host)
49
+ }
50
+
51
+ scope :kickstart, where(:content_type => KICKSTART_TYPE)
52
+ scope :yum, where(:content_type => YUM_TYPE)
53
+
54
+ def update_cache
55
+ nil
56
+ end
57
+
58
+ def full_path
59
+ pulp_url = URI.parse(Setting.pulp_url)
60
+ scheme = (unprotected ? 'http' : 'https')
61
+ port = (pulp_url.port == 443 || pulp_url.port == 80 ? "" : ":#{pulp_url.port}")
62
+ "#{scheme}://#{pulp_url.host}#{port}#{REPO_PREFIX}#{relative_path}"
63
+ end
64
+
65
+ # The label is used as a repository label in a yum repo file.
66
+ def to_label
67
+ "#{product.name}-#{name}".parameterize
68
+ end
69
+ end
70
+ end