foreman_content
Advanced tools
| module Content | ||
| class ContentViewsController < ::ApplicationController | ||
| include Foreman::Controller::AutoCompleteSearch | ||
| before_filter :find_by_name, :only => %w{show edit update destroy} | ||
| def index | ||
| @content_views = ContentView.search_for(params[:search], :order => params[:order]). | ||
| paginate(:page => params[:page]) | ||
| @counter = RepositoryClone.joins(:content_view_repository_clones).group(:content_view_id).count | ||
| end | ||
| def new | ||
| @hostgroup = Hostgroup.find_by_id(params[:hostgroup]) if params[:hostgroup] | ||
| @content_view = ContentViewFactory.create_product_content_view(params[:product]) if params[:product] | ||
| @content_view ||= ContentViewFactory.create_os_content_view(params[:operatingsystem]) if params[:operatingsystem] | ||
| @content_view ||= ContentViewFactory.create_composite_content_view(params[:content_content_view_factory]) if params[:content_content_view_factory] | ||
| end | ||
| def create | ||
| @content_view = ContentView.new(params[:content_content_view]) | ||
| if @content_view.save | ||
| process_success | ||
| else | ||
| process_error | ||
| end | ||
| end | ||
| def edit | ||
| end | ||
| def update | ||
| if @content_view.update_attributes(params[:content_content_view]) | ||
| process_success | ||
| else | ||
| process_error | ||
| end | ||
| end | ||
| def destroy | ||
| if @content_view.destroy | ||
| process_success | ||
| else | ||
| process_error | ||
| end | ||
| end | ||
| end | ||
| end |
| module Content | ||
| module ContentViewsHelper | ||
| def repositories(view) | ||
| if view.new_record? | ||
| view.source_repositories + view.repository_clones | ||
| else | ||
| view.repository_clones | ||
| end | ||
| end | ||
| def next_or_cancel path, args={} | ||
| args[:cancel_path] ||= send("#{controller_name}_path") | ||
| content_tag(:div, :class => "form-actions") do | ||
| link_to(_("Cancel"), args[:cancel_path], :class => "btn") + " " + | ||
| submit_tag( _("Next"), :class => "btn btn-primary") | ||
| end | ||
| end | ||
| end | ||
| end |
| module Content | ||
| module ProductsHelper | ||
| def visible? product_id | ||
| @counter[product_id].to_i == 0 | ||
| end | ||
| end | ||
| end |
| module Content::Orchestration::Pulp | ||
| extend ActiveSupport::Concern | ||
| include ::Orchestration | ||
| included do | ||
| delegate :sync_status, :sync, :counters, :sync_history, :state, :to => :repo | ||
| end | ||
| def orchestration_errors? | ||
| errors.empty? | ||
| end | ||
| def pulp? | ||
| @use_pulp ||= Setting.use_pulp | ||
| end | ||
| def update_cache; nil; end | ||
| def progress_report_id | ||
| @progress_report_id ||= Foreman.uuid | ||
| end | ||
| def pulp_repo_id | ||
| self.pulp_id ||= Foreman.uuid.gsub("-", '') | ||
| end | ||
| end |
| module Content::Orchestration::Pulp::Clone | ||
| extend ActiveSupport::Concern | ||
| include Content::Orchestration::Pulp | ||
| included do | ||
| after_validation :repository_clone_save unless Rails.env.test? | ||
| before_destroy :repository_clone_destroy unless Rails.env.test? | ||
| end | ||
| def last_published | ||
| read_attribute(:last_published) || repo.last_publish | ||
| end | ||
| private | ||
| def repository_clone_save | ||
| return unless (pulp? and errors.empty? and new_record?) | ||
| queue_pulp_create | ||
| end | ||
| def queue_pulp_create | ||
| logger.debug "Scheduling new Pulp Repository" | ||
| queue.create(:name => _("Create Pulp Repository for %s") % self, :priority => 10, | ||
| :action => [self, :set_pulp_repo]) | ||
| queue.create(:name => _("Copy Pulp Repository %s") % self, :priority => 20, | ||
| :action => [self, :set_copy_pulp_repo]) | ||
| queue.create(:name => _("Create Event Notifier") % self, :priority => 30, | ||
| :action => [self, :set_create_event_notifier]) | ||
| queue.create(:name => _("Publish Pulp Repository %s") % self, :priority => 40, | ||
| :action => [self, :set_publish_pulp_repo]) | ||
| end | ||
| def repository_clone_destroy | ||
| return unless pulp? and errors.empty? | ||
| logger.debug _("Scheduling removal of Pulp Repository %s") % name | ||
| queue.create(:name => _("Delete Pulp repository for %s") % name, :priority => 50, | ||
| :action => [self, :del_pulp_repo]) | ||
| end | ||
| def set_pulp_repo | ||
| repo.create_with_distributor | ||
| end | ||
| def set_copy_pulp_repo | ||
| repo.copy_from(repository.pulp_id) | ||
| end | ||
| def set_publish_pulp_repo | ||
| repo.publish | ||
| end | ||
| def set_create_event_notifier | ||
| repo.create_event_notifier | ||
| end | ||
| def del_create_event_notifier; end | ||
| def del_copy_pulp_repo; end | ||
| def del_pulp_repo | ||
| repo.delete | ||
| end | ||
| def repo_options | ||
| { | ||
| :pulp_id => pulp_repo_id, | ||
| :name => "#{to_label}_clone", | ||
| :relative_path => relative_path, | ||
| :content_type => content_type, | ||
| :protected => false, | ||
| } | ||
| end | ||
| def repo | ||
| @repo ||= Content::Pulp::RepositoryClone.new(repo_options) | ||
| end | ||
| end |
| module Content::Orchestration::Pulp::Sync | ||
| extend ActiveSupport::Concern | ||
| include Content::Orchestration::Pulp | ||
| included do | ||
| after_validation :queue_pulp | ||
| before_destroy :queue_pulp_destroy unless Rails.env.test? | ||
| end | ||
| def last_sync | ||
| read_attribute(:last_sync) || repo.last_sync | ||
| end | ||
| private | ||
| def queue_pulp | ||
| return unless pulp? and errors.empty? | ||
| new_record? ? queue_pulp_create : queue_pulp_update | ||
| end | ||
| def queue_pulp_create | ||
| logger.debug "Scheduling new Pulp Repository" | ||
| queue.create(:name => _("Create Pulp Repository for %s") % self, :priority => 10, | ||
| :action => [self, :set_pulp_repo]) | ||
| queue.create(:name => _("Sync Pulp Repository %s") % self, :priority => 20, | ||
| :action => [self, :set_sync_pulp_repo]) | ||
| end | ||
| def queue_pulp_update | ||
| # TODO: handle repo updates. | ||
| end | ||
| def queue_pulp_destroy | ||
| return unless pulp? and errors.empty? | ||
| logger.debug _("Scheduling removal of Pulp Repository %s") % name | ||
| queue.create(:name => _("Delete Pulp repository for %s") % name, :priority => 50, | ||
| :action => [self, :del_pulp_repo]) | ||
| end | ||
| def set_pulp_repo | ||
| repo.create | ||
| end | ||
| def del_pulp_repo | ||
| repo.delete | ||
| end | ||
| def set_sync_pulp_repo | ||
| repo.sync | ||
| end | ||
| def del_sync_pulp_repo | ||
| repo.cancel_running_sync! | ||
| end | ||
| def repo_options | ||
| { | ||
| :pulp_id => pulp_repo_id, | ||
| :name => to_label, | ||
| :description => description, | ||
| :feed => feed, | ||
| :content_type => content_type, | ||
| :protected => unprotected, | ||
| } | ||
| end | ||
| def repo | ||
| @repo ||= Content::Pulp::Repository.new(repo_options) | ||
| end | ||
| end |
| module Content | ||
| class AvailableContentView < ActiveRecord::Base | ||
| belongs_to :environment | ||
| belongs_to :operatingsystem | ||
| belongs_to :content_view | ||
| validates_presence_of :content_view_id, :environment_id | ||
| #todo needs to validate that default can't be archived and vice-versa | ||
| end | ||
| end |
| module Content | ||
| class ContentViewHost < ActiveRecord::Base | ||
| belongs_to :host | ||
| belongs_to :content_view | ||
| validates_presence_of :content_view_id, :host_id | ||
| validates_uniqueness_of :content_view_id, :scope => :host_id | ||
| end | ||
| end |
| class Content::ContentViewRepositoryClone < ActiveRecord::Base | ||
| belongs_to :content_view | ||
| belongs_to :repository_clone | ||
| validate :content_view_id, :repository_clone_id, :presence => true | ||
| end |
| module Content | ||
| class ContentView < ActiveRecord::Base | ||
| has_ancestry :orphan_strategy => :rootify | ||
| belongs_to :originator, :polymorphic => true | ||
| has_many :available_content_views, :dependent => :destroy | ||
| has_many :environments, :through => :available_content_views | ||
| delegate :operatingsystems, :to => :available_content_views, :allow_nil => true | ||
| has_many :content_view_hosts, :dependent => :destroy, :uniq => true, :foreign_key => :content_view_id, :class_name => 'Content::ContentViewHost' | ||
| has_many :hosts, :through => :content_view_hosts | ||
| has_many :content_view_repository_clones, :dependent => :destroy | ||
| has_many :repository_clones, :through => :content_view_repository_clones, :class_name => 'Content::RepositoryClone' | ||
| has_many :repositories, :through => :repository_clones | ||
| scope :hostgroups, where(:originator_type => 'Hostgroup') | ||
| scope :products, where(:originator_type => 'Content::Product') | ||
| scope :operatingsystem, where(:originator_type => 'Operatingsystem') | ||
| after_save :clone_repos | ||
| before_destroy :clean_unused_clone_repos | ||
| validates_presence_of :name | ||
| # special relationships needed for search with polymorphic associations | ||
| belongs_to :search_hostgroups, :class_name => 'Hostgroup', :foreign_key => :originator_id, | ||
| :conditions => '"content_content_views"."originator_type" = "Hostgroup"' | ||
| belongs_to :search_operatingsystems, :class_name => 'Operatingsystem', :foreign_key => :originator_id, | ||
| :conditions => '"content_content_views"."originator_type" = "Operatingsystem"' | ||
| belongs_to :search_products, :class_name => 'Content::Product', :foreign_key => :originator_id, | ||
| :conditions => '"content_content_views"."originator_type" = "Content::Product"' | ||
| scoped_search :on => [:name, :created_at], :complete_value => :true | ||
| scoped_search :in => :search_products, :on => :name, :rename => :product, | ||
| :complete_value => :true, :only_explicit => true | ||
| scoped_search :in => :search_operatingsystems, :on => :name, :rename => :operatingsystem, | ||
| :complete_value => :true, :only_explicit => true | ||
| scoped_search :in => :search_hostgroups, :on => :label, :complete_value => true, | ||
| :rename => :hostgroup, :only_explicit => true | ||
| attr_accessor :source_repositories | ||
| def to_label | ||
| name || "#{originator.to_label} - #{DateTime.now.strftime("%m/%d/%Y")}".parameterize | ||
| end | ||
| def clone_repos | ||
| return unless @source_repositories | ||
| Repository.where(:id => @source_repositories).each do |repository| | ||
| repository.publish self | ||
| end | ||
| end | ||
| private | ||
| def clean_unused_clone_repos | ||
| current_repos = Content::ContentViewRepositoryClone.where(:content_view_id => id).pluck(:repository_clone_id) | ||
| used_repos = Content::ContentViewRepositoryClone. | ||
| where(:repository_clone_id => current_repos). | ||
| where(['content_view_id IS NOT ?', id]).pluck(:repository_clone_id) | ||
| repos_to_delete = current_repos - used_repos | ||
| Content::RepositoryClone.destroy(repos_to_delete) if repos_to_delete.any? | ||
| end | ||
| end | ||
| end |
| module Content | ||
| class RepositoryClone < ActiveRecord::Base | ||
| include Content::Orchestration::Pulp::Clone | ||
| REPO_PREFIX = '/pulp/repos/' | ||
| belongs_to :repository | ||
| has_many :content_view_repository_clones, :dependent => :destroy | ||
| has_many :content_views, :through => :content_view_repository_clones | ||
| before_destroy EnsureNotUsedBy.new(:content_views) | ||
| attr_accessible :description, :last_published, :name, :pulp_id, :relative_path, :status, :content_views | ||
| validate :relative_path, :repository_id, :presence => true | ||
| delegate :content_type, :architecture, :unprotected, :gpg_key, :product, :to => :repository | ||
| scope :for_content_views, lambda { |ids| | ||
| joins(:content_view_repository_clones). | ||
| where('content_content_view_repository_clones' => {:content_view_id => ids}) | ||
| } | ||
| def full_path | ||
| pulp_url = URI.parse(Setting.pulp_url) | ||
| scheme = (unprotected ? 'http' : 'https') | ||
| port = (pulp_url.port == 443 || pulp_url.port == 80 ? "" : ":#{pulp_url.port}") | ||
| "#{scheme}://#{pulp_url.host}#{port}#{REPO_PREFIX}#{relative_path}" | ||
| end | ||
| end | ||
| end |
| module Content | ||
| class Repository::OperatingSystem < Repository | ||
| validates_presence_of :operatingsystem | ||
| def self.model_name | ||
| Repository.model_name | ||
| end | ||
| def entity_name | ||
| operatingsystem.to_label | ||
| end | ||
| end | ||
| end |
| module Content | ||
| class Repository::Product < Repository | ||
| has_many :operatingsystem_repositories, :foreign_key => :repository_id, :dependent => :destroy, :uniq => true | ||
| has_many :operatingsystems, :through => :operatingsystem_repositories | ||
| validates_presence_of :product | ||
| def self.model_name | ||
| Repository.model_name | ||
| end | ||
| def content_types | ||
| TYPES - [KICKSTART_TYPE] | ||
| end | ||
| def entity_name | ||
| product.name | ||
| end | ||
| end | ||
| end |
| Deface::Override.new(:virtual_path => "hosts/_form", | ||
| :name => "add_content_tab", | ||
| :insert_after => 'ul.nav > li a#params-tab', | ||
| :partial => 'content/content_views/form_tab') | ||
| Deface::Override.new(:virtual_path => "hosts/_form", | ||
| :name => "add_content_tab_pane", | ||
| :insert_after => 'div.tab-pane#params', | ||
| :partial => 'content/content_views/host_tab_pane') |
| Deface::Override.new(:virtual_path => "hostgroups/_form", | ||
| :name => "add_content_tab", | ||
| :insert_after => 'ul.nav > li a#params-tab', | ||
| :partial => 'content/products/form_tab') | ||
| Deface::Override.new(:virtual_path => "hostgroups/_form", | ||
| :name => "add_content_tab_pane", | ||
| :insert_after => 'div.tab-pane#params', | ||
| :partial => 'content/products/hostgroup_tab_pane') |
| module Content | ||
| class ContentViewFactory | ||
| include ActiveModel::Conversion | ||
| extend ActiveModel::Naming | ||
| attr_accessor :type, :originator_id, :parent_cv, :product_cv, :os_cv | ||
| # create a content view of a single product | ||
| def self.create_product_content_view(product_id) | ||
| product = Product.find_by_id(product_id) | ||
| ContentView.new(:source_repositories => product.repositories, :originator => product) | ||
| end | ||
| # create a content view of an operating system | ||
| def self.create_os_content_view(operatingsystem_id) | ||
| os = Operatingsystem.find_by_id(operatingsystem_id) | ||
| ContentView.new(:source_repositories => os.repositories, :originator => os) | ||
| end | ||
| # create a composite content view of a hostgroup (the hostgroup may have a list of products) and a parent | ||
| # content view. The parent content view is one of the hostgroup parent content views. | ||
| # A hostgroup may have number of content views representing different versions of that hostgroup content. | ||
| def self.create_composite_content_view(options = {}) | ||
| factory = new(options) | ||
| hostgroup = Hostgroup.find_by_id(factory.originator_id) | ||
| clone_ids = Content::RepositoryClone.for_content_views([factory.product_cv, factory.os_cv]). | ||
| pluck(:id) | ||
| ContentView.new(:source_repositories=> [], :originator => hostgroup, | ||
| :repository_clone_ids => clone_ids, :parent_id => factory.parent_cv) | ||
| end | ||
| def initialize(attributes = {}) | ||
| attributes.each do |name, value| | ||
| instance_variable_set("@#{name}", value) if respond_to?("#{name}".to_sym) | ||
| end | ||
| end | ||
| def persisted? | ||
| false | ||
| end | ||
| end | ||
| end |
| require 'runcible' | ||
| require 'uri' | ||
| class Content::Pulp::Configuration | ||
| def initialize options = {} | ||
| Runcible::Base.config = runcible_config.merge(options) | ||
| end | ||
| def runcible_config | ||
| pulp_url = URI(Setting.pulp_url) | ||
| { | ||
| :url => "#{pulp_url.scheme}://#{pulp_url.host}:#{pulp_url.port}", | ||
| :api_path => pulp_url.path, | ||
| :user => "admin", | ||
| :timeout => 60, | ||
| :open_timeout => 60, | ||
| :oauth => { :oauth_secret => Setting['pulp_oauth_secret'], | ||
| :oauth_key => Setting['pulp_oauth_key'] }, | ||
| :logging => { :logger => Rails.logger, | ||
| :exception => true, | ||
| :debug => true } | ||
| } | ||
| end | ||
| end |
| class Content::Pulp::EventHandler | ||
| attr_reader :type, :status | ||
| delegate :name, :to => :repo | ||
| delegate :logger, :to => :Rails | ||
| def initialize(pulp_id, params) | ||
| @pulp_id = pulp_id | ||
| @params = params | ||
| @type, @status = parse_type | ||
| return nil unless repo | ||
| log | ||
| update_state | ||
| end | ||
| private | ||
| attr_reader :pulp_id, :params | ||
| def repo | ||
| @repo ||= case type | ||
| when 'sync' | ||
| Content::Repository.where(:pulp_id => pulp_id).first | ||
| when 'promote' | ||
| Content::RepositoryClone.where(:pulp_id => pulp_id).first | ||
| end | ||
| end | ||
| def log | ||
| logger.info "Repository #{name} #{type} #{status}" | ||
| end | ||
| def parse_type | ||
| # converts repo.sync.start to ['sync','start'] | ||
| params['event_type'].gsub(/^repo\./, '').split('.') | ||
| end | ||
| def update_state | ||
| #nothing to do if we didnt finish a task | ||
| return unless status == 'finish' | ||
| case type | ||
| when 'sync' | ||
| repo.update_attribute(:last_sync, finished_at) if success? | ||
| when 'promote' | ||
| repo.update_attribute(:last_published, finished_at) if success? | ||
| end | ||
| end | ||
| def success? | ||
| result == 'success' | ||
| end | ||
| def result | ||
| params['payload']['result'] | ||
| end | ||
| def finished_at | ||
| Time.parse(params['payload']['completed']) | ||
| end | ||
| end |
| class Content::Pulp::RepositoryClone < Content::Pulp::Repository | ||
| PULP_SELECT_FIELDS = %w(name epoch version release arch checksumtype checksum) | ||
| def copy_from(src_repo_id) | ||
| # In order to reduce the memory usage of pulp during the copy process, | ||
| # include the fields that will uniquely identify the rpm. If no fields | ||
| # are listed, pulp will retrieve every field it knows about for the rpm | ||
| # (e.g. changelog, filelist...etc). | ||
| Runcible::Extensions::Rpm.copy(src_repo_id, pulp_id, { :fields => PULP_SELECT_FIELDS }) | ||
| Runcible::Extensions::Distribution.copy(src_repo_id, pulp_id) | ||
| # Since the rpms will be copied above, during the copy of errata and package groups, | ||
| # include the copy_children flag to request that pulp skip copying them again. | ||
| Runcible::Extensions::Errata.copy(src_repo_id, pulp_id, { :copy_children => false }) | ||
| Runcible::Extensions::PackageGroup.copy(src_repo_id, pulp_id, { :copy_children => false }) | ||
| Runcible::Extensions::YumRepoMetadataFile.copy(src_repo_id, pulp_id) | ||
| end | ||
| def defaults; end | ||
| def pulp_importer | ||
| Runcible::Extensions::YumImporter.new | ||
| end | ||
| end |
| class Content::Pulp::RepositorySyncHistory | ||
| attr_reader :exception, :removed_count, :started, :completed, :summary, | ||
| :error_message, :added_count, :updated_count, :details, :result | ||
| def initialize(attrs) | ||
| attrs.each do |k, v| | ||
| instance_variable_set("@#{k}", v) if respond_to?("#{k}".to_sym) | ||
| end | ||
| end | ||
| def times | ||
| { | ||
| :comps => summary[:time_total_sec], | ||
| :errata => summary[:errata_time_total_sec], | ||
| :packages => summary[:packages][:time_total_sec] | ||
| } | ||
| end | ||
| def metrics | ||
| { | ||
| :updated => updated_count, | ||
| :removed => removed_count, | ||
| :added => added_count, | ||
| } | ||
| end | ||
| def status | ||
| { | ||
| :result => result, | ||
| :message => error_message || summary['error'], | ||
| :completed => completed | ||
| } | ||
| end | ||
| end |
| class Content::Pulp::RepositorySyncStatus | ||
| attr_reader :state, :progress, :finish_time, :start_time, :sync_times, :sync_metrics, :message, :task_id | ||
| HISTORY_ERROR = 'failed' | ||
| HISTORY_SUCCESS = 'success' | ||
| FINISHED = 'finished' | ||
| ERROR = 'error' | ||
| RUNNING = 'running' | ||
| WAITING = 'waiting' | ||
| CANCELED = 'canceled' | ||
| NOT_SYNCED = 'not synchronized' | ||
| def initialize(attrs) | ||
| @state = NOT_SYNCED | ||
| @sync_times = @sync_metrics = {} | ||
| attrs.each do |k, v| | ||
| instance_variable_set("@#{k}", v) if respond_to?("#{k}".to_sym) | ||
| end | ||
| end | ||
| def not_synced? | ||
| state == NOT_SYNCED | ||
| end | ||
| def running? | ||
| state == RUNNING | ||
| end | ||
| end |
| class Content::Pulp::Repository | ||
| PULP_SELECT_FIELDS = ['name', 'epoch', 'version', 'release', 'arch', 'checksumtype', 'checksum'] | ||
| attr_reader :pulp_id, :content_type, :relative_path, :name, :description | ||
| delegate :logger, :to => :Rails | ||
| class << self | ||
| def create(options = {}) | ||
| new(options).create | ||
| end | ||
| def delete(pulp_id) | ||
| new(:pulp_id => pulp_id).delete | ||
| end | ||
| def sync(pulp_id) | ||
| new(:pulp_id => pulp_id).sync | ||
| end | ||
| end | ||
| def initialize options = {} | ||
| options.each do |k, v| | ||
| instance_variable_set("@#{k}", v) if respond_to?("#{k}".to_sym) | ||
| end | ||
| raise('must define pulp id') unless pulp_id | ||
| # initiate pulp connection | ||
| Content::Pulp::Configuration.new | ||
| end | ||
| def create_with_distributor | ||
| create_repository [pulp_distributor] | ||
| end | ||
| def create | ||
| create_repository | ||
| end | ||
| def display_name | ||
| details['display_name'] | ||
| end | ||
| def checksum_type | ||
| details['checksum_type'] | ||
| end | ||
| def description | ||
| @description || details['description'] | ||
| end | ||
| def counters | ||
| details['content_unit_counts'] | ||
| end | ||
| def feed | ||
| @feed || importer['config']['feed_url'] | ||
| end | ||
| def last_sync | ||
| importer['last_sync'] | ||
| end | ||
| def last_publish | ||
| distributor['last_publish'] | ||
| end | ||
| def auto_publish | ||
| @auto_publish.nil? ? distributor['auto_publish'] : @auto_publish | ||
| end | ||
| def protected | ||
| @protected.nil? ? distributor['protected'] : @protected | ||
| end | ||
| def reload! | ||
| @details = nil | ||
| end | ||
| def sync_status | ||
| status = Runcible::Extensions::Repository.sync_status(pulp_id) | ||
| Content::Pulp::RepositorySyncStatus.new(status.first) unless status.empty? | ||
| end | ||
| def sync_history | ||
| history = Runcible::Extensions::Repository.sync_history(pulp_id) | ||
| (history || []).map do |sync| | ||
| Content::Pulp::RepositorySyncHistory.new(sync) | ||
| end | ||
| end | ||
| def delete | ||
| Runcible::Resources::Repository.delete(pulp_id) | ||
| rescue RestClient::ResourceNotFound => e | ||
| true #not found is not a failure for delete | ||
| end | ||
| def sync | ||
| Runcible::Resources::Repository.sync(pulp_id) | ||
| end | ||
| def publish | ||
| Runcible::Extensions::Repository.publish_all(pulp_id) | ||
| end | ||
| def cancel_running_sync! | ||
| return if sync_status.blank? || sync_status.not_synced? | ||
| Runcible::Resources::Task.cancel(sync_status.task_id) | ||
| rescue RestClient::ResourceNotFound | ||
| # task already done, nothing to do here | ||
| end | ||
| # once we keep a list pulp servers, this should be done in create/destroy | ||
| def create_event_notifier | ||
| url = Rails.application.routes.url_helpers. | ||
| events_api_repositories_url(:only_path => false, :host => Setting[:foreman_url]) | ||
| type = '*' | ||
| resource = Runcible::Resources::EventNotifier | ||
| notifs = resource.list | ||
| #only create a notifier if one doesn't exist with the correct url | ||
| exists = notifs.select { |n| n['event_types'] == [type] && n['notifier_config']['url'] == url } | ||
| resource.create(resource::NotifierTypes::REST_API, { :url => url }, [type]) if exists.empty? | ||
| true | ||
| end | ||
| private | ||
| def create_repository pulp_distributors = [] | ||
| defaults | ||
| Runcible::Extensions::Repository.create_with_importer_and_distributors(pulp_id, | ||
| pulp_importer, | ||
| pulp_distributors, | ||
| { :display_name => relative_path || name, | ||
| :description => description }) | ||
| rescue RestClient::BadRequest => e | ||
| raise parse_error(e) | ||
| end | ||
| def pulp_importer | ||
| options = { | ||
| :feed_url => feed | ||
| } | ||
| case content_type | ||
| when Content::Repository::YUM_TYPE, Content::Repository::KICKSTART_TYPE | ||
| Runcible::Extensions::YumImporter.new(options) | ||
| when Content::Repository::FILE_TYPE | ||
| Runcible::Extensions::IsoImporter.new(options) | ||
| else | ||
| raise "Unexpected repo type %s" % content_type | ||
| end | ||
| end | ||
| def pulp_distributor | ||
| case content_type | ||
| when Content::Repository::YUM_TYPE, Content::Repository::KICKSTART_TYPE | ||
| Runcible::Extensions::YumDistributor.new(relative_path, protected, true, | ||
| { :protected => protected, :id => pulp_id, | ||
| :auto_publish => auto_publish }) | ||
| when Content::Repository::FILE_TYPE | ||
| dist = Runcible::Extensions::IsoDistributor.new(true, true) | ||
| dist.auto_publish = true | ||
| dist | ||
| else | ||
| raise "Unexpected repo type %s" % content_type | ||
| end | ||
| end | ||
| def defaults | ||
| @protected = true if @protected.nil? | ||
| end | ||
| def importer | ||
| details['importers'].empty? ? {} : details['importers'].first | ||
| end | ||
| def distributor | ||
| details['distributors'].empty? ? {} : details['distributors'].first | ||
| end | ||
| def details | ||
| @details ||= Runcible::Resources::Repository.retrieve(pulp_id, { :details => true }) | ||
| rescue RestClient::ResourceNotFound => e | ||
| logger.warn "Repo not found: #{parse_error(e)}" | ||
| {:state => 'not found'} | ||
| end | ||
| def state | ||
| return 'published' if last_publish.present? | ||
| return sync_status.state if sync_status.present? | ||
| return details[:state] if details[:state] | ||
| end | ||
| def parse_error exception | ||
| JSON.parse(exception.response)['error_message'] | ||
| end | ||
| end |
| <li id="content_views_tab"> | ||
| <a href="#content_views" data-toggle="tab">Content Views</a> | ||
| </li> |
| <%= form_for @content_view, :url => (@content_view.new_record? ? content_views_path : content_view_path(@content_view)) do |f| %> | ||
| <%= base_errors_for @content_view %> | ||
| <ul class="nav nav-tabs" data-tabs="tabs"> | ||
| <li class="active"><a href="#primary" data-toggle="tab"><%= _("Content view") %></a></li> | ||
| <li><a href="#availability" data-toggle="tab"><%= _("Availability") %></a></li> | ||
| </ul> | ||
| <div class="tab-content"> | ||
| <div class="tab-pane active" id="primary"> | ||
| <%= text_f f, :name, :value => @content_view.to_label %> | ||
| <% if @content_view.new_record? %> | ||
| <% @content_view.source_repositories.each do |repo| %> | ||
| <%= f.hidden_field :source_repositories, :multiple => true, :value => repo.id %> | ||
| <% end if @content_view.source_repositories %> | ||
| <% @content_view.repository_clones.each do |repo| %> | ||
| <%= f.hidden_field :repository_clone_ids, :multiple => true, :value => repo.id %> | ||
| <% end if @content_view.repository_clones %> | ||
| <%= f.hidden_field :originator_id, :value => @content_view.originator_id %> | ||
| <%= f.hidden_field :originator_type, :value => @content_view.originator_type %> | ||
| <% end %> | ||
| <table class="table table-bordered"> | ||
| <tr> | ||
| <th><%= _("Name") %></th> | ||
| <th><%= _("State") %></th> | ||
| <th><%= _("Last publish") %></th> | ||
| <th><%= _("Content type") %></th> | ||
| </tr> | ||
| <% repositories(@content_view).each do |repository| %> | ||
| <tr> | ||
| <td><%= repository.name %></td> | ||
| <td><%= repository.try(:state) %></td> | ||
| <td><%= last_time(repository.last_published) %></td> | ||
| <td><%= repository.content_type %></td> | ||
| </tr> | ||
| <% end %> | ||
| </table> | ||
| </div> | ||
| <div class="tab-pane" id="availability"> | ||
| <%= multiple_selects(f, :environments, Environment, | ||
| Content::AvailableContentView.where(:content_view_id => @content_view.id).pluck(:environment_id), | ||
| { :label => _('Available in environments') }) %> | ||
| </div> | ||
| </div> | ||
| <%= submit_or_cancel f %> | ||
| <% end %> |
| <div class="tab-pane" id="content_views"> | ||
| <%= fields_for @host do |f| %> | ||
| <%= multiple_selects f, :content_views, Content::ContentView, @host.all_content_view_ids , {:disabled => @host.inherited_content_view_ids}, {} %> | ||
| <% end -%> | ||
| </div> |
| <%= wizard_header 1, _('Select source type'), _('Select content'), _('Create') %> | ||
| <%= form_tag hash_for_new_content_view_path, :class => 'form-horizontal well', :method => :get do %> | ||
| <% case params[:type] %> | ||
| <% when 'product' %> | ||
| <%= field(nil, _('Product')) do | ||
| select_tag('product', options_from_collection_for_select(Content::Product.has_repos, 'id', 'name')) | ||
| end %> | ||
| <% when 'operatingsystem' %> | ||
| <%= field(nil, _('Operating system')) do | ||
| select_tag('operatingsystem', options_from_collection_for_select(Redhat.has_repos, 'id', 'to_label')) | ||
| end %> | ||
| <% when 'hostgroup' %> | ||
| <%= field(nil, _('Host group')) do | ||
| select_tag('hostgroup', options_from_collection_for_select(accessible_hostgroups, 'id', 'to_label')) | ||
| end %> | ||
| <% end %> | ||
| <%= next_or_cancel hash_for_new_content_view_path() %> | ||
| <% end %> |
| <%= wizard_header 2, _("Select source type"), _("Select content"), _("Create") %> | ||
| <%= form_for Content::ContentViewFactory.new, :url => hash_for_new_content_view_path, :class => 'form-horizontal well', :method => :get do |f| %> | ||
| <%= f.hidden_field :originator_id, :value => @hostgroup.id %> | ||
| <%= f.hidden_field :originator_type, :value => @hostgroup.class.name %> | ||
| <%= select_f f, :parent_cv, @hostgroup.parent.try(:content_views) || [], "id", "name", | ||
| :label => _("Parent Content view") %> | ||
| <h6>Products</h6> | ||
| <% @hostgroup.products.each do |product| %> | ||
| <%= select_f f, :product_cv, product.content_views, "id", "name", {}, :label => product.name %> | ||
| <% end %> | ||
| <h6>Operating System</h6> | ||
| <%= select_f f, :os_cv, @hostgroup.os.content_views, "id", "name" %> | ||
| <%= next_or_cancel hash_for_new_content_view_path() %> | ||
| <% end %> |
| <%= javascript 'content/content.js' %> | ||
| <% title _("Edit Content View") %> | ||
| <%= render :partial => 'form' %> |
| <% title _("Content Views") %> | ||
| <% title_actions select_action_button( _('New content view'), | ||
| display_link_if_authorized(_("Product view"), hash_for_new_content_view_path(:type=>'product')), | ||
| display_link_if_authorized(_("Operating system view"), hash_for_new_content_view_path(:type=>'operatingsystem')), | ||
| display_link_if_authorized(_("Hostgroup view"), hash_for_new_content_view_path(:type=>'hostgroup'))) %> | ||
| <table class="table table-bordered table-striped"> | ||
| <tr> | ||
| <th><%= sort :name, :as => s_("Name") %></th> | ||
| <th><%= _("Repositories") %></th> | ||
| <th><%= _("Created at") %></th> | ||
| <th></th> | ||
| </tr> | ||
| <% @content_views.each do |content_view| %> | ||
| <tr> | ||
| <td><%= link_to_if_authorized(h(content_view.name), hash_for_edit_content_view_path(content_view)) %></td> | ||
| <td><%= @counter[content_view.id] || '0' %></td> | ||
| <td><%= _("%s ago") % time_ago_in_words(content_view.created_at) %></td> | ||
| <td><%= action_buttons( | ||
| display_delete_if_authorized(hash_for_content_view_path(content_view), :confirm => "Delete #{content_view}?") | ||
| )%></td> | ||
| </tr> | ||
| <% end %> | ||
| </table> | ||
| <%= page_entries_info @content_views %> | ||
| <%= will_paginate @content_views %> |
| <%= javascript 'content/content.js' %> | ||
| <% title _("New Content View") %> | ||
| <% if @content_view %> | ||
| <%= render :partial => 'form' %> | ||
| <% elsif @hostgroup %> | ||
| <%= render :partial => 'step2' %> | ||
| <% else %> | ||
| <%= render :partial => 'step1' %> | ||
| <% end %> |
| <%= javascript 'content/content.js' %> | ||
| <%= form_for @repository, :url => (@repository.new_record? ? repositories_path : repository_path(:id => @repository.id)) do |f| %> | ||
| <%= base_errors_for @repository %> | ||
| <ul class="nav nav-tabs" data-tabs="tabs"> | ||
| <li class="active"><a href="#primary" data-toggle="tab"><%= _("Repository") %></a></li> | ||
| </ul> | ||
| <div class="tab-content"> | ||
| <div class="tab-pane active" id="primary"> | ||
| <%= f.hidden_field :type %> | ||
| <%= select_f f, :operatingsystem_id, ::Redhat.all, :id, :to_label, {}, {:label => _("Operating System")} %> | ||
| <%= text_f f, :name %> | ||
| <%= text_f f, :feed, :class => 'span6' %> | ||
| <%= selectable_f f, :content_type, @repository.content_types %> | ||
| <%= select_f f, :architecture_id, ::Architecture.all, :id, :name, { :include_blank => N_("noarch")} %> | ||
| <%= checkbox_f f, :enabled %> | ||
| <%= select_f f, :gpg_key_id, Content::GpgKey.all, :id, :name, {:include_blank => true}, {:label => _("GPG Key")} %> | ||
| </div> | ||
| </div> | ||
| <%= submit_or_cancel f %> | ||
| <% end %> |
| <%= javascript 'content/content.js' %> | ||
| <%= form_for @repository, :url => (@repository.new_record? ? repositories_path : repository_path(:id => @repository.id)) do |f| %> | ||
| <%= base_errors_for @repository %> | ||
| <ul class="nav nav-tabs" data-tabs="tabs"> | ||
| <li class="active"><a href="#primary" data-toggle="tab"><%= _("Repository") %></a></li> | ||
| <li><a href="#operatingsystems" data-toggle="tab"><%= _("Operating systems") %></a></li> | ||
| </ul> | ||
| <div class="tab-content"> | ||
| <div class="tab-pane active" id="primary"> | ||
| <%= f.hidden_field :type %> | ||
| <%= select_f f, :product_id, Content::Product.all, :id, :name, {}, {:label => _("Product")} %> | ||
| <%= text_f f, :name %> | ||
| <%= text_f f, :feed, :class => 'span6' %> | ||
| <%= selectable_f f, :content_type, @repository.content_types %> | ||
| <%= select_f f, :architecture_id, ::Architecture.all, :id, :name, { :include_blank => N_("noarch")} %> | ||
| <%= checkbox_f f, :enabled %> | ||
| <%= select_f f, :gpg_key_id, Content::GpgKey.all, :id, :name, {:include_blank => true}, {:label => _("GPG Key")} %> | ||
| </div> | ||
| <div class="tab-pane" id="operatingsystems"> | ||
| <%= alert :class => 'controls alert-success', :header => 'Repository for operating systems', | ||
| :text => _('This Repository will be restricted only to hosts that runs the selected operating system') %> | ||
| <%= multiple_selects(f, :operatingsystems, ::Redhat, @repository.operatingsystem_ids) %> | ||
| </div> | ||
| </div> | ||
| <%= submit_or_cancel f %> | ||
| <% end %> |
| class CreateContentContentViews < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_content_views do |t| | ||
| t.string :name | ||
| t.string :ancestry | ||
| t.integer :originator_id | ||
| t.string :originator_type | ||
| t.timestamps | ||
| end | ||
| add_index(:content_content_views, :ancestry,:name=>'content_view_ancestry_index') | ||
| add_index :content_content_views, [:originator_id, :originator_type] | ||
| add_index :content_content_views, :originator_type | ||
| end | ||
| end |
| class CreateContentAvailableContentViews < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_available_content_views do |t| | ||
| t.references :environment, :null =>false | ||
| t.references :content_view, :null =>false | ||
| t.references :operatingsystem | ||
| t.boolean :archived | ||
| t.boolean :default | ||
| end | ||
| end | ||
| end |
| class CreateContentContentViewHosts < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_content_view_hosts do |t| | ||
| t.references :host, :null =>false | ||
| t.references :content_view, :null =>false | ||
| end | ||
| add_index(:content_content_view_hosts, [:content_view_id, :host_id],:name=>'content_content_view_hosts_index', :unique=>true) | ||
| end | ||
| end |
| class CreateContentRepositoryClones < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_repository_clones do |t| | ||
| t.string :name | ||
| t.string :description | ||
| t.references :repository | ||
| t.string :relative_path | ||
| t.string :pulp_id | ||
| t.string :status | ||
| t.datetime :last_published | ||
| t.timestamps | ||
| end | ||
| add_index :content_repository_clones, :repository_id | ||
| add_index :content_repository_clones, :pulp_id, :unique => true | ||
| end | ||
| end |
| class CreateContentContentViewRepositoryClones < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_content_view_repository_clones do |t| | ||
| t.references :content_view | ||
| t.references :repository_clone | ||
| t.timestamps | ||
| end | ||
| add_index :content_content_view_repository_clones, :content_view_id, :name => 'cv_repo_clone_cv_id' | ||
| add_index :content_content_view_repository_clones, :repository_clone_id, :name => 'cv_repo_clone_clone_id' | ||
| end | ||
| end |
| # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html | ||
| one: | ||
| name: MyString | ||
| description: MyString | ||
| repository: | ||
| relative_path: MyString | ||
| content_view: | ||
| pulp_id: MyString | ||
| status: MyString | ||
| last_published: 2013-08-13 14:04:55 | ||
| two: | ||
| name: MyString | ||
| description: MyString | ||
| repository: | ||
| relative_path: MyString | ||
| content_view: | ||
| pulp_id: MyString | ||
| status: MyString | ||
| last_published: 2013-08-13 14:04:55 |
| require 'test_helper' | ||
| class Content::RepositoryCloneTest < ActiveSupport::TestCase | ||
| # test "the truth" do | ||
| # assert true | ||
| # end | ||
| end |
@@ -10,7 +10,5 @@ module Content | ||
| def events | ||
| if (repo_id = params['payload'] && params['payload']['repo_id']) | ||
| repo = Content::Repository.where(:pulp_id => repo_id).first | ||
| end | ||
| render_error 'not_found', :status => :not_found and return false if repo.nil? | ||
| PulpEventHandler.new(repo_id, params) | ||
| repo_id = params['payload']['repo_id'] if params['payload'] | ||
| render_error 'not_found', :status => :not_found and return false if repo_id.blank? | ||
| Content::Pulp::EventHandler.new(repo_id, params) | ||
| head :status => 202 | ||
@@ -17,0 +15,0 @@ end |
@@ -9,3 +9,3 @@ module Content | ||
| paginate(:page => params[:page]) | ||
| @counter = Repository.group(:product_id).where(:product_id => @products.map(&:id)).count | ||
| @counter = Repository::Product.group(:product_id).where(:product_id => @products.map(&:id)).count | ||
| end | ||
@@ -12,0 +12,0 @@ |
@@ -8,7 +8,14 @@ module Content | ||
| @repositories = Repository.search_for(params[:search], :order => params[:order]). | ||
| paginate(:page => params[:page]).includes(:product, :operatingsystems) | ||
| paginate(:page => params[:page]).includes(:product, :operatingsystem) | ||
| end | ||
| def new | ||
| @repository = Repository.new(:unprotected => true) | ||
| @repository = case params[:type] | ||
| when "operatingsystem" | ||
| Repository::OperatingSystem.new(:unprotected => true) | ||
| when "product" | ||
| Repository::Product.new(:product_id => params[:product_id]) | ||
| else | ||
| not_found | ||
| end | ||
| end | ||
@@ -37,6 +44,2 @@ | ||
| def show | ||
| @details = @repository.retrieve_with_details | ||
| @sync_history = @repository.sync_history | ||
| rescue | ||
| redirect_back_or_to(edit_repository_path) | ||
| end | ||
@@ -43,0 +46,0 @@ |
| module Content | ||
| module RepositoriesHelper | ||
| def sync_status repo | ||
| status = repo.sync_status | ||
| status = if status && status.first | ||
| status.first[:state] | ||
| else | ||
| details = repo.retrieve_with_details | ||
| details[:importers][0][:last_sync] if details && details[:importers] && details[:importers][0] | ||
| end | ||
| "#{time_ago_in_words(status)} ago" rescue status | ||
| rescue | ||
| '-' | ||
| def last_time(time) | ||
| return if time.blank? | ||
| time_ago_in_words(time) + ' ago' | ||
| end | ||
| def last_publish details | ||
| return '' unless details[:distributors] && details[:distributors].first && published = details[:distributors].first[:last_publish] | ||
| "#{time_ago_in_words published} ago" rescue '' | ||
| end | ||
| def last_sync details | ||
| return '' unless details[:importers] && details[:importers].first && synced = details[:importers].first[:last_sync] | ||
| "#{time_ago_in_words synced} ago" rescue '' | ||
| end | ||
| def sync_history_times history | ||
| return {} unless history && history.first && summary = history.first['summary'] | ||
| return {} unless summary[:comps] | ||
| { | ||
| :comps => summary[:comps][:time_total_sec], | ||
| :errata => summary[:errata][:errata_time_total_sec], | ||
| :packages => summary[:packages][:time_total_sec] | ||
| } | ||
| end | ||
| def sync_history_metrics history | ||
| return {} unless history && last = history.first | ||
| { | ||
| :updated => last[:updated_count], | ||
| :removed => last[:removed_count], | ||
| :added => last[:added_count] | ||
| } | ||
| end | ||
| def sync_history_status history | ||
| return {} unless history && last = history.first | ||
| { | ||
| 'result' => last[:result], | ||
| 'message' => last[:error_message] || last['summary']['error'], | ||
| 'completed' => ("#{time_ago_in_words(last[:completed])} ago" rescue '') | ||
| } | ||
| end | ||
| end | ||
| end |
@@ -5,6 +5,6 @@ module Content::EnvironmentExtensions | ||
| included do | ||
| has_many :environment_products, :dependent => :destroy, :uniq => true, :class_name => 'Content::EnvironmentProduct' | ||
| has_many :products, :through => :environment_products, :class_name => 'Content::Product' | ||
| has_many :available_content_views, :dependent => :destroy, :class_name => 'Content::AvailableContentView' | ||
| has_many :content_views, :through => :available_content_views, :class_name => 'Content::ContentView' | ||
| end | ||
| end |
@@ -14,2 +14,3 @@ module Content::HomeHelper | ||
| [_('Repositories'), :"content/repositories"], | ||
| [_('Content Views'), :"content/content_views"], | ||
| [_('Gpg keys'), :"content/gpg_keys"] | ||
@@ -16,0 +17,0 @@ ] |
@@ -8,2 +8,5 @@ module Content::HostExtensions | ||
| has_many :content_view_hosts, :dependent => :destroy, :uniq => true, :foreign_key => :host_id, :class_name => 'Content::ContentViewHost' | ||
| has_many :content_views, :through => :content_view_hosts, :class_name => 'Content::ContentView' | ||
| scoped_search :in => :products, :on => :name, :complete_value => true, :rename => :product | ||
@@ -34,4 +37,17 @@ | ||
| def inherited_content_view_ids | ||
| return [] if hostgroup_id.nil? or environment_id.nil? | ||
| Content::ContentView.joins(:available_content_views). | ||
| where(:content_available_content_views => {:environment_id => environment_id}). | ||
| where(:originator_type => 'Hostgroup', :originator_id => hostgroup_id).pluck(:id) | ||
| end | ||
| def all_content_view_ids | ||
| (inherited_content_view_ids + content_view_ids).uniq | ||
| end | ||
| def attached_repositories | ||
| Content::Repository.attached_to_host(self) | ||
| return [] if all_content_view_ids.empty? | ||
| Content::RepositoryClone.for_content_views(all_content_view_ids) | ||
| end | ||
@@ -38,0 +54,0 @@ |
@@ -7,7 +7,12 @@ module Content::HostgroupExtensions | ||
| has_many :products, :through => :hostgroup_products, :class_name => 'Content::Product' | ||
| has_many :content_views, :as => :originator, :class_name => 'Content::ContentView' | ||
| scope :has_content_views, joins(:content_views) | ||
| scoped_search :in => :products, :on => :name, :complete_value => true, :rename => :product | ||
| scoped_search :in => :content_views, :on => :name, :complete_value => true, :rename => :content_view | ||
| end | ||
| def inherited_product_ids | ||
| return [] if hostgroup.ancestor_ids.empty? | ||
| Content::HostgroupProduct.where(:hostgroup_id => hostgroup.ancestor_ids).pluck(:product_id) | ||
@@ -19,2 +24,11 @@ end | ||
| end | ||
| def inherited_content_view_ids | ||
| return [] if hostgroup.ancestor_ids.empty? | ||
| Content::ContentView.where(:originator_id => hostgroup.ancestor_ids, :originator_type => 'Hostgroup').pluck(:id) | ||
| end | ||
| def all_content_views_ids | ||
| (inherited_content_view_ids + content_view_ids).uniq | ||
| end | ||
| end |
@@ -5,9 +5,10 @@ module Content::OperatingsystemExtensions | ||
| included do | ||
| has_many :operatingsystem_repositories, :dependent => :destroy, :uniq => true, :class_name => 'Content::OperatingsystemRepository' | ||
| has_many :repositories, :through => :operatingsystem_repositories, :class_name => 'Content::Repository' | ||
| has_many :product_operatingsystems, :dependent => :destroy, :uniq => true, :class_name => 'Content::ProductOperatingsystem' | ||
| has_many :products, :through => :product_operatingsystems, :class_name => 'Content::Product' | ||
| has_many :default_repositories, :through => :products, :source => :repositories, :class_name => 'Content::Repository' | ||
| has_many :repositories, :class_name => 'Content::Repository' | ||
| has_many :available_content_views, :dependent => :destroy, :class_name => 'Content::AvailableContentView' | ||
| has_many :content_views, :as => :originator, :class_name => 'Content::ContentView' | ||
| scope :has_repos, joins(:repositories).uniq | ||
| end | ||
| end |
@@ -1,12 +0,1 @@ | ||
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
@@ -13,0 +2,0 @@ class HostProduct < ActiveRecord::Base |
@@ -1,12 +0,1 @@ | ||
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
@@ -13,0 +2,0 @@ class HostgroupProduct < ActiveRecord::Base |
@@ -1,12 +0,1 @@ | ||
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
@@ -13,0 +2,0 @@ class OperatingsystemRepository < ActiveRecord::Base |
@@ -6,8 +6,5 @@ module Content | ||
| has_many :repositories | ||
| has_many :environment_products, :dependent => :destroy, :uniq=>true | ||
| has_many :environments, :through => :environment_products | ||
| has_many :repository_clones, :through => :repositories | ||
| has_many :content_views, :as => :originator | ||
| has_many :product_operatingsystems, :dependent => :destroy, :uniq=>true | ||
| has_many :operatingsystems, :through => :product_operatingsystems | ||
| has_many :hostgroup_products, :dependent => :destroy, :uniq=>true | ||
@@ -19,2 +16,6 @@ has_many :hostgroups, :through => :hostgroup_products | ||
| before_destroy EnsureNotUsedBy.new(:hostgroups, :hosts, :repositories) | ||
| scope :has_repos, joins(:repositories).uniq | ||
| validates_with Validators::DescriptionFormat, :attributes => :description | ||
@@ -21,0 +22,0 @@ validates :name, :presence => true |
@@ -1,10 +0,5 @@ | ||
| require 'content/orchestration/pulp' | ||
| # TODO: split into a custom and red hat repositories: | ||
| # as handling of repo creation/updates is different between them | ||
| module Content | ||
| class Repository < ActiveRecord::Base | ||
| include CustomRepositoryPaths | ||
| include Content::Remote::Pulp::Repository | ||
| include ::Orchestration | ||
| include Content::Orchestration::Pulp | ||
| include Content::Orchestration::Pulp::Sync | ||
| include Foreman::STI | ||
@@ -16,13 +11,14 @@ YUM_TYPE = 'yum' | ||
| REPO_PREFIX = '/pulp/repos/' | ||
| belongs_to :product | ||
| belongs_to :product, :class_name => 'Content::Product' | ||
| belongs_to :operatingsystem | ||
| belongs_to :gpg_key | ||
| belongs_to :architecture | ||
| has_many :operatingsystem_repositories, :dependent => :destroy, :uniq => true | ||
| has_many :operatingsystems, :through => :operatingsystem_repositories | ||
| has_many :repository_clones | ||
| validates :product, :presence => true | ||
| before_destroy EnsureNotUsedBy.new(:repository_clones) | ||
| validates_presence_of :type # can't create this object, only child | ||
| validates :name, :presence => true | ||
| validates_uniqueness_of :name, :scope => :product_id | ||
| validates_uniqueness_of :name, :scope => [:operatingsystem_id, :product_id] | ||
| validates_inclusion_of :content_type, | ||
@@ -35,38 +31,31 @@ :in => TYPES, | ||
| scoped_search :in => :architecture, :on => :name, :rename => :architecture, :complete_value => :true | ||
| scoped_search :in => :operatingsystems, :on => :name, :rename => :os, :complete_value => :true | ||
| scoped_search :in => :operatingsystem, :on => :name, :rename => :os, :complete_value => :true | ||
| scoped_search :in => :product, :on => :name, :rename => :product, :complete_value => :true | ||
| # Repositories filtered by operating system architecture and environment. | ||
| # architecture_id is nil for noarch repositories. | ||
| scope :available_for_host, lambda { |host| | ||
| joins(:operatingsystems => :operatingsystem_repositories , :product => :environments). | ||
| where(:architecture_id => [nil, host.architecture_id], | ||
| :content_operatingsystem_repositories => { :operatingsystem_id => host.operatingsystem_id}, | ||
| :content_environment_products => { :environment_id => host.environment_id }). | ||
| uniq} | ||
| # | ||
| scope :attached_to_host, lambda { |host| | ||
| where(:product_id => host.all_product_ids).available_for_host(host) | ||
| } | ||
| scope :kickstart, where(:content_type => KICKSTART_TYPE) | ||
| scope :yum, where(:content_type => YUM_TYPE) | ||
| def update_cache | ||
| nil | ||
| def content_types | ||
| TYPES | ||
| end | ||
| def full_path | ||
| pulp_url = URI.parse(Setting.pulp_url) | ||
| scheme = (unprotected ? 'http' : 'https') | ||
| port = (pulp_url.port == 443 || pulp_url.port == 80 ? "" : ":#{pulp_url.port}") | ||
| "#{scheme}://#{pulp_url.host}#{port}#{REPO_PREFIX}#{relative_path}" | ||
| end | ||
| # The label is used as a repository label in a yum repo file. | ||
| def to_label | ||
| "#{product.name}-#{name}".parameterize | ||
| "#{entity_name}-#{name}".parameterize | ||
| end | ||
| #inhariters are expected to override this method | ||
| def entity_name | ||
| '' | ||
| end | ||
| def publish content_view | ||
| repository_clones.create!( | ||
| :content_views => [content_view], | ||
| :name => self.name + "_clone", | ||
| :relative_path => "content_views/#{to_label}/#{Foreman.uuid}" | ||
| ) | ||
| end | ||
| end | ||
| end |
@@ -1,13 +0,1 @@ | ||
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
@@ -14,0 +2,0 @@ module Validators |
@@ -1,13 +0,1 @@ | ||
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
@@ -14,0 +2,0 @@ module Validators |
@@ -1,13 +0,1 @@ | ||
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
@@ -14,0 +2,0 @@ module Validators |
@@ -24,20 +24,2 @@ class Setting::Content< ::Setting | ||
| def self.ensure_sync_notification | ||
| include Rails.application.routes.url_helpers | ||
| resource = Runcible::Resources::EventNotifier | ||
| url = events_repositories_url(:only_path => false, :host => Setting[:foreman_url]) | ||
| type = '*' #resource::EventTypes::REPO_SYNC_COMPLETE | ||
| notifs = resource.list | ||
| #delete any similar tasks with the wrong url (in case it changed) | ||
| notifs.select { |n| n['event_types'] == [type] && n['notifier_config']['url'] != url }.each do |e| | ||
| resource.delete(e['id']) | ||
| end | ||
| #only create a notifier if one doesn't exist with the correct url | ||
| exists = notifs.select { |n| n['event_types'] == [type] && n['notifier_config']['url'] == url } | ||
| resource.create(resource::NotifierTypes::REST_API, { :url => url }, [type]) if exists.empty? | ||
| end | ||
| end |
@@ -1,3 +0,3 @@ | ||
| <li id="products_tab"> | ||
| <a href="#products" data-toggle="tab">Products</a> | ||
| </li> | ||
| <li id="content_tab"> | ||
| <a href="#product" data-toggle="tab">Content</a> | ||
| </li> |
@@ -6,4 +6,2 @@ <%= javascript 'content/content.js' %> | ||
| <li class="active"><a href="#primary" data-toggle="tab"><%= _("Product") %></a></li> | ||
| <li><a href="#environments" data-toggle="tab"><%= _("Environments") %></a></li> | ||
| <li><a href="#operatingsystems" data-toggle="tab"><%= _("Operating systems") %></a></li> | ||
| </ul> | ||
@@ -13,16 +11,7 @@ <div class="tab-content"> | ||
| <%= text_f f, :name %> | ||
| <%= text_f f, :description %> | ||
| <%= textarea_f f, :description, :class => "input-xlarge" , :rows=> '3' %> | ||
| </div> | ||
| <div class="tab-pane" id="environments"> | ||
| <%= alert :class => 'controls alert-success', :header => 'Product environments list', | ||
| :text => _('Repositories of this product will be restricted only for hosts in the selected environments.') %> | ||
| <%= multiple_selects(f, :environments, Environment, f.object.environment_ids) %> | ||
| </div> | ||
| <div class="tab-pane" id="operatingsystems"> | ||
| <%= alert :class => 'controls alert-success', :header => 'Default Products for operating system', | ||
| :text => _('Repositories of this product will be added to every host that runs the selected operating system, pending environment restriction') %> | ||
| <%= multiple_selects(f, :operatingsystems, Redhat, f.object.operatingsystem_ids,{:label=>"Operating system default products"}) %> | ||
| </div> | ||
| </div> | ||
| <%= submit_or_cancel f %> | ||
| <% end %> |
@@ -1,5 +0,5 @@ | ||
| <div class="tab-pane" id="products"> | ||
| <div class="tab-pane" id="product"> | ||
| <%= fields_for @hostgroup do |f| %> | ||
| <%= multiple_selects f, :products, Content::Product, @hostgroup.all_product_ids , {:disabled => @hostgroup.inherited_product_ids}, {} %> | ||
| <% end -%> | ||
| </div> | ||
| </div> |
@@ -19,3 +19,8 @@ | ||
| <%= action_buttons( | ||
| display_link_if_authorized(_("Synchronize"), hash_for_sync_product_path(product), :method => :put), | ||
| display_link_if_authorized(_("Create Content View"), hash_for_new_content_view_path(:product=>product), | ||
| :disabled => visible?(product.id)), | ||
| display_link_if_authorized(_("Synchronize"), hash_for_sync_product_path(product), :method => :put, | ||
| :disabled => visible?(product.id)), | ||
| display_link_if_authorized(_("Add Repository"), | ||
| hash_for_new_repository_path(:type => "product",:product_id => product.id)), | ||
| display_delete_if_authorized(hash_for_product_path(product), :confirm => "Delete #{product.name}?") | ||
@@ -22,0 +27,0 @@ )%> |
| <% title _("Edit Repository") %> | ||
| <%= render :partial => 'form' %> | ||
| <% if @repository.is_a? Content::Repository::OperatingSystem %> | ||
| <%= render :partial => 'os_form' %> | ||
| <% else %> | ||
| <%= render :partial => 'product_form' %> | ||
| <% end %> |
| <% title _("Repositories") %> | ||
| <% title_actions display_link_if_authorized(_("New Repository"), hash_for_new_repository_path), help_path %> | ||
| <% title_actions select_action_button(_('New repository'), | ||
| display_link_if_authorized(_("New Product Repository"), hash_for_new_repository_path(:type => 'product')), | ||
| display_link_if_authorized(_("New Operating system Repository"), hash_for_new_repository_path(:type => 'operatingsystem'))) %> | ||
| <table class="table table-bordered table-striped"> | ||
@@ -8,3 +11,3 @@ <tr> | ||
| <th><%= _("Product") %></th> | ||
| <th><%= _("Operating system") %></th> | ||
| <th><%= _("State") %></th> | ||
| <th><%= _("Last sync status") %></th> | ||
@@ -17,12 +20,12 @@ <th><%= _("Content type") %></th> | ||
| <td><%= link_to_if_authorized(h(repository.name), hash_for_repository_path(repository)) %></td> | ||
| <td><%= repository.product %></td> | ||
| <td><%= repository.operatingsystems.map(&:to_label).to_sentence %></td> | ||
| <td><%= sync_status repository %></td> | ||
| <td><%= repository.entity_name %></td> | ||
| <td><%= repository.try(:state) %></td> | ||
| <td><%= last_time(repository.last_sync) %></td> | ||
| <td><%= repository.content_type %></td> | ||
| <td align="right"> | ||
| <%= action_buttons( | ||
| display_link_if_authorized(_("Edit"), hash_for_edit_repository_path(repository)), | ||
| display_link_if_authorized(_("Synchronize"), hash_for_sync_repository_path(repository), :method => :put), | ||
| display_delete_if_authorized(hash_for_repository_path(repository), :confirm => "Delete #{repository.name}?") | ||
| )%> | ||
| display_link_if_authorized(_("Edit"), hash_for_edit_repository_path(repository)), | ||
| display_link_if_authorized(_("Synchronize"), hash_for_sync_repository_path(repository), :method => :put), | ||
| display_delete_if_authorized(hash_for_repository_path(repository), :confirm => "Delete #{repository.name}?") | ||
| ) %> | ||
@@ -29,0 +32,0 @@ </td> |
| <% title _("New Repository") %> | ||
| <%= render :partial => 'form' %> | ||
| <% if @repository.is_a? Content::Repository::OperatingSystem %> | ||
| <%= render :partial => "os_form" %> | ||
| <% else %> | ||
| <%= render :partial => "product_form" %> | ||
| <% end %> |
@@ -6,3 +6,4 @@ <% javascript 'charts' %> | ||
| link_to_if_authorized(_("Edit"), hash_for_edit_repository_path(@repository)), | ||
| link_to_if_authorized(_("Synchronize"), hash_for_sync_repository_path(@repository), :method => :put) | ||
| link_to_if_authorized(_("Synchronize"), hash_for_sync_repository_path(@repository), :method => :put), | ||
| display_delete_if_authorized(hash_for_repository_path(@repository), :class => 'btn-danger', :confirm => "Delete #{@repository.name}?") | ||
| )) %> | ||
@@ -44,6 +45,2 @@ <div class="row"> | ||
| </tr> | ||
| <tr> | ||
| <td> <%= _("Full path") %> </td> | ||
| <td> <%= @repository.full_path %> </td> | ||
| </tr> | ||
| </table> | ||
@@ -56,3 +53,3 @@ </div> | ||
| <div style="margin-top:50px;padding-bottom: 40px;"> | ||
| <%= flot_pie_chart("metrics" ,_("Last Update Metrics"), sync_history_times(@sync_history), :class => "statistics-pie small")%> | ||
| <%= flot_pie_chart("metrics" ,_("Last Update Metrics"), @repository.sync_history.last.try(:times), :class => "statistics-pie small")%> | ||
| </div> | ||
@@ -62,3 +59,3 @@ </div> | ||
| <h4 class="ca" ><%= _('Update Summary') -%></h4> | ||
| <%= flot_bar_chart("status" ,"", _("Number of packages"), sync_history_metrics(@sync_history), :class => "statistics-bar")%> | ||
| <%= flot_bar_chart("status" ,"", _("Number of packages"), @repository.sync_history.last.try(:metrics), :class => "statistics-bar")%> | ||
| </div> | ||
@@ -72,5 +69,5 @@ | ||
| </tr> | ||
| <% @details[:content_unit_counts].each do |name, value| -%> | ||
| <% @repository.counters.each do |name, value| -%> | ||
| <tr> | ||
| <td> <%= name.humanize %> </td> | ||
| <td> <%= name.to_s.humanize %> </td> | ||
| <td> <%= value %> </td> | ||
@@ -80,8 +77,4 @@ </tr> | ||
| <tr> | ||
| <td> <%= _("Last published") %> </td> | ||
| <td> <%= last_publish @details %> </td> | ||
| </tr> | ||
| <tr> | ||
| <td> <%= _("Last synchronized") %> </td> | ||
| <td> <%= last_sync @details %> </td> | ||
| <td> <%= last_time @repository.last_sync %> </td> | ||
| </tr> | ||
@@ -96,5 +89,5 @@ </table> | ||
| </tr> | ||
| <% sync_history_status(@sync_history).each do |name, value| -%> | ||
| <% @repository.sync_history.last.status.each do |name, value| -%> | ||
| <tr> | ||
| <td> <%= name.humanize %> </td> | ||
| <td> <%= name.to_s.humanize %> </td> | ||
| <td> <%= value %> </td> | ||
@@ -101,0 +94,0 @@ </tr> |
+6
-0
@@ -9,2 +9,8 @@ Rails.application.routes.draw do | ||
| resources :content_views do | ||
| collection do | ||
| get 'auto_complete_search' | ||
| end | ||
| end | ||
| resources :products do | ||
@@ -11,0 +17,0 @@ collection do |
| class CreateContentRepositories < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_repositories do |t| | ||
| t.string :type | ||
| t.string :pulp_id, :null => false | ||
| t.string :name, :null => false | ||
@@ -8,9 +10,11 @@ t.string :description | ||
| t.boolean :enabled, :default => true | ||
| t.string :relative_path | ||
| t.string :feed | ||
| t.boolean :unprotected, :default => false | ||
| t.references :product, :null => false | ||
| t.string :pulp_id | ||
| t.references :gpg_key | ||
| t.references :architecture | ||
| t.string :status | ||
| t.datetime :last_sync | ||
| t.references :product | ||
| t.references :operatingsystem | ||
| t.timestamps | ||
@@ -17,0 +21,0 @@ end |
@@ -9,2 +9,2 @@ class CreateContentOperatingsystemRepositories < ActiveRecord::Migration | ||
| end | ||
| end | ||
| end |
@@ -0,1 +1,3 @@ | ||
| require 'deface' | ||
| module Content | ||
@@ -2,0 +4,0 @@ ENGINE_NAME = "content" |
| module Content | ||
| VERSION = "0.2" | ||
| VERSION = "0.3" | ||
| end |
+40
-1
@@ -71,4 +71,25 @@ # foreman\_content | ||
| Once created, the repo would automatically be synced and published. | ||
| Once created, the repo would automatically be synced but not yet visible for | ||
| end users, for that you would need to create a Content View. | ||
| ## Creating Content Views | ||
| Content Views, are a collection of immutibal repositories, these repositories | ||
| are cloned from the synced repository and allow you to have multiple | ||
| repositories definitions per Operation Systems, Custom yum repos, and | ||
| Hostgroups. | ||
| You would first need to create a content view per Product / Operation system. | ||
| ## Configuring Hostgroups (and hosts) to consume content views | ||
| In your hostgroup definiton, you can select the operation system and the custom | ||
| products you are interested in. | ||
| next under the content view, you can create a new hostgroup content view, and | ||
| select the releavnt content view you would like to consume within your hostgroup. | ||
| You could also define the puppet environment(s) in which the content view is | ||
| used. | ||
| ## Consuming Repos within your Kickstart | ||
@@ -115,1 +136,19 @@ | ||
| filter engine spike | ||
| # License | ||
| foreman_content plugin | ||
| Copyright (c) 2013 Red Hat Inc. | ||
| This program is free software: you can redistribute it and/or modify | ||
| it under the terms of the GNU General Public License as published by | ||
| the Free Software Foundation, either version 3 of the License, or | ||
| (at your option) any later version. | ||
| This program is distributed in the hope that it will be useful, | ||
| but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| GNU General Public License for more details. | ||
| You should have received a copy of the GNU General Public License | ||
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| module Content | ||
| module CustomRepositoryPaths | ||
| extend ActiveSupport::Concern | ||
| def repo_path_from_content_path(environment, content_path) | ||
| content_path = content_path.sub(/^\//, "") | ||
| path_prefix = [environment.organization.label, environment.label].join("/") | ||
| "#{path_prefix}/#{content_path}" | ||
| end | ||
| # repo path for custom product repos (RH repo paths are derived from | ||
| # content url) | ||
| def custom_repo_path(org_label, environment_label, product_label, repo_label) | ||
| prefix = [org_label, environment_label].map { |x| x.gsub(/[^-\w]/, "_") }.join("/") | ||
| prefix + custom_content_path(product_label, repo_label) | ||
| end | ||
| def custom_content_path(product_label, repo_label) | ||
| parts = [] | ||
| # We generate repo path only for custom product content. We add this | ||
| # constant string to avoid collisions with RH content. RH content url | ||
| # begins usually with something like "/content/dist/rhel/...". | ||
| # There we prefix custom content/repo url with "/custom/..." | ||
| parts << "custom" | ||
| parts += [product_label, repo_label] | ||
| "/" + parts.map { |x| x.gsub(/[^-\w]/, "_") }.join("/") | ||
| end | ||
| end | ||
| end |
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
| class EnvironmentProduct < ActiveRecord::Base | ||
| belongs_to :product | ||
| belongs_to :environment | ||
| validates_presence_of :product_id, :environment_id | ||
| validates_uniqueness_of :product_id, :scope => :environment_id | ||
| end | ||
| end |
| module Content::Orchestration::Pulp | ||
| extend ActiveSupport::Concern | ||
| included do | ||
| after_validation :queue_pulp | ||
| before_destroy :queue_pulp_destroy unless Rails.env == "test" | ||
| end | ||
| def orchestration_errors? | ||
| errors.empty? | ||
| end | ||
| protected | ||
| def queue_pulp | ||
| return unless pulp? and errors.empty? | ||
| new_record? ? queue_pulp_create : queue_pulp_update | ||
| end | ||
| private | ||
| def queue_pulp_create | ||
| logger.debug "Scheduling new Pulp Repository" | ||
| queue.create(:name => _("Create Pulp Repository for %s") % self, :priority => 10, | ||
| :action => [self, :set_pulp_repo]) | ||
| queue.create(:name => _("Sync Pulp Repository %s") % self, :priority => 20, | ||
| :action => [self, :set_sync_pulp_repo]) | ||
| end | ||
| def queue_pulp_update | ||
| end | ||
| def set_pulp_repo | ||
| Runcible::Extensions::Repository.create_with_importer_and_distributors(pulp_id, | ||
| pulp_importer, | ||
| [pulp_distributor], | ||
| { :display_name => relative_path, | ||
| :description => description }) | ||
| rescue RestClient::BadRequest => e | ||
| raise (JSON.parse e.response)['error_message'] | ||
| end | ||
| def del_pulp_repo | ||
| delete | ||
| end | ||
| def set_sync_pulp_repo | ||
| sync | ||
| end | ||
| def del_sync_pulp_repo | ||
| status = sync_status | ||
| return if status.blank? || status == ::PulpSyncStatus::Status::NOT_SYNCED | ||
| Runcible::Resources::Task.cancel(status.uuid) | ||
| end | ||
| def pulp_importer | ||
| options = { | ||
| :feed_url => feed | ||
| } | ||
| case content_type | ||
| when Content::Repository::YUM_TYPE, Content::Repository::KICKSTART_TYPE | ||
| Runcible::Extensions::YumImporter.new(options) | ||
| when Content::Repository::FILE_TYPE | ||
| Runcible::Extensions::IsoImporter.new(options) | ||
| else | ||
| raise "Unexpected repo type %s" % content_type | ||
| end | ||
| end | ||
| def pulp_distributor | ||
| case content_type | ||
| when Content::Repository::YUM_TYPE, Content::Repository::KICKSTART_TYPE | ||
| Runcible::Extensions::YumDistributor.new(relative_path, unprotected, true, | ||
| { :protected => true, :id => pulp_id, | ||
| :auto_publish => true }) | ||
| when Content::Repository::FILE_TYPE | ||
| dist = Runcible::Extensions::IsoDistributor.new(true, true) | ||
| dist.auto_publish = true | ||
| dist | ||
| else | ||
| raise "Unexpected repo type %s" % content_type | ||
| end | ||
| end | ||
| def queue_pulp_destroy | ||
| return unless pulp? and errors.empty? | ||
| logger.debug _("Scheduling removal of Pulp Repository %s") % name | ||
| queue.create(:name => _("Delete Pulp repository for %s") % name, :priority => 50, | ||
| :action => [self, :del_pulp_repo]) | ||
| end | ||
| end |
| # | ||
| # Copyright 2013 Red Hat, Inc. | ||
| # | ||
| # This software is licensed to you under the GNU General Public | ||
| # License as published by the Free Software Foundation; either version | ||
| # 2 of the License (GPLv2) or (at your option) any later version. | ||
| # There is NO WARRANTY for this software, express or implied, | ||
| # including the implied warranties of MERCHANTABILITY, | ||
| # NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should | ||
| # have received a copy of GPLv2 along with this software; if not, see | ||
| # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. | ||
| module Content | ||
| class ProductOperatingsystem < ActiveRecord::Base | ||
| belongs_to :product | ||
| belongs_to :operatingsystem | ||
| validates_presence_of :product_id, :operatingsystem_id | ||
| validates_uniqueness_of :product_id, :scope => :operatingsystem_id | ||
| end | ||
| end |
| module Content | ||
| module Remote | ||
| module Pulp | ||
| module Repository | ||
| extend ActiveSupport::Concern | ||
| included do | ||
| after_initialize :initialize_pulp | ||
| end | ||
| def pulp? | ||
| @use_pulp ||= Setting.use_pulp and enabled? | ||
| end | ||
| def sync | ||
| Runcible::Resources::Repository.sync(pulp_id) | ||
| end | ||
| def retrieve_with_details | ||
| return unless pulp? && pulp_id | ||
| Runcible::Resources::Repository.retrieve(pulp_id, {:details => true}) | ||
| end | ||
| def sync_status | ||
| return unless pulp? && pulp_id | ||
| Runcible::Extensions::Repository.sync_status(pulp_id) | ||
| end | ||
| def sync_history | ||
| return unless pulp? && pulp_id | ||
| Runcible::Extensions::Repository.sync_history(pulp_id) | ||
| end | ||
| def delete | ||
| Runcible::Resources::Repository.delete(pulp_id) | ||
| end | ||
| protected | ||
| def initialize_pulp | ||
| # initiate pulp connection | ||
| Content::PulpConfiguration.new | ||
| self.pulp_id ||= Foreman.uuid.gsub("-", '') | ||
| self.relative_path ||= custom_repo_path("acme_org", "library", product.name, name) if name | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
| Deface::Override.new(:virtual_path => "hosts/_form", | ||
| :name => "add_content_tab", | ||
| :insert_after => 'ul.nav > li a#params-tab', | ||
| :partial => 'content/products/form_tab') | ||
| Deface::Override.new(:virtual_path => "hosts/_form", | ||
| :name => "add_content_tab_pane", | ||
| :insert_after => 'div.tab-pane#params', | ||
| :partial => 'content/products/host_tab_pane') |
| Deface::Override.new(:virtual_path => "hostgroups/_form", | ||
| :name => "add_content_tab", | ||
| :insert_after => 'ul.nav > li a#params-tab', | ||
| :partial => 'content/products/form_tab') | ||
| Deface::Override.new(:virtual_path => "hostgroups/_form", | ||
| :name => "add_content_tab_pane", | ||
| :insert_after => 'div.tab-pane#params', | ||
| :partial => 'content/products/hostgroup_tab_pane') |
| Deface::Override.new(:virtual_path => "operatingsystems/_form", | ||
| :name => "add_content_tab", | ||
| :insert_after => 'ul.nav > li a#params-tab', | ||
| :partial => 'content/products/form_tab') | ||
| Deface::Override.new(:virtual_path => "operatingsystems/_form", | ||
| :name => "add_content_tab_pane", | ||
| :insert_after => 'div.tab-pane#params', | ||
| :partial => 'content/products/operatingsystem_tab_pane') |
| require 'runcible' | ||
| require 'uri' | ||
| class Content::PulpConfiguration | ||
| def initialize options = {} | ||
| Runcible::Base.config = runcible_config.merge(options) | ||
| end | ||
| def runcible_config | ||
| pulp_url = URI(Setting.pulp_url) | ||
| { | ||
| :url => "#{pulp_url.scheme}://#{pulp_url.host}:#{pulp_url.port}", | ||
| :api_path => pulp_url.path, | ||
| :user => "admin", | ||
| :timeout => 60, | ||
| :open_timeout => 60, | ||
| :oauth => { :oauth_secret => Setting['pulp_oauth_secret'], | ||
| :oauth_key => Setting['pulp_oauth_key'] }, | ||
| :logging => { :logger => Rails.logger, | ||
| :exception => true, | ||
| :debug => true } | ||
| } | ||
| end | ||
| end |
| class Content::PulpEventHandler | ||
| attr_reader :type, :status | ||
| delegate :name, :to => :repo | ||
| delegate :logger, :to => :Rails | ||
| def initialize(pulp_id, params) | ||
| @repo = Content::Repository.where(:pulp_id => pulp_id).first | ||
| @params = params | ||
| @type, @status = parse_type | ||
| log | ||
| end | ||
| private | ||
| attr_reader :repo, :params | ||
| def log | ||
| logger.info "Repository #{name} #{type} #{status}" | ||
| end | ||
| def parse_type | ||
| # converts repo.sync.start to ['sync','start'] | ||
| params['event_type'].gsub(/^repo\./, '').split('.') | ||
| end | ||
| end |
| <div class="tab-pane" id="products"> | ||
| <%= fields_for @host do |f| %> | ||
| <%= multiple_selects f, :products, Content::Product, @host.all_product_ids , {:disabled => @host.inherited_product_ids}, {} %> | ||
| <% end -%> | ||
| </div> |
| <div class="tab-pane" id="products"> | ||
| <%= fields_for @operatingsystem do |f| %> | ||
| <%= multiple_selects f, :products, Content::Product, @operatingsystem.product_ids , {}, {} %> | ||
| <% end -%> | ||
| </div> |
| <%= javascript 'content/content.js' %> | ||
| <%= form_for @repository, :url => (@repository.new_record? ? repositories_path : repository_path(:id => @repository.id)) do |f| %> | ||
| <%= base_errors_for @repository %> | ||
| <ul class="nav nav-tabs" data-tabs="tabs"> | ||
| <li class="active"><a href="#primary" data-toggle="tab"><%= _("Repository") %></a></li> | ||
| <li><a href="#operatingsystems" data-toggle="tab"><%= _("Operating systems") %></a></li> | ||
| </ul> | ||
| <div class="tab-content"> | ||
| <div class="tab-pane active" id="primary"> | ||
| <%= select_f f, :product_id, Content::Product.all, :id, :name, {}, {:label => _("Product")} %> | ||
| <%= text_f f, :name %> | ||
| <%= text_f f, :feed, :class => 'span6' %> | ||
| <%= selectable_f f, :content_type, Content::Repository::TYPES %> | ||
| <%= select_f f, :architecture_id, ::Architecture.all, :id, :name, { :include_blank => N_("noarch")} %> | ||
| <%= checkbox_f f, :enabled %> | ||
| <%= select_f f, :gpg_key_id, Content::GpgKey.all, :id, :name, {:include_blank => true}, {:label => _("GPG Key")} %> | ||
| <% unless @repository.new_record? %> | ||
| <%= text_f f, :full_path, :disabled => true, :class => 'span6' %> | ||
| <% end %> | ||
| </div> | ||
| <div class="tab-pane" id="operatingsystems"> | ||
| <%= alert :class => 'controls alert-success', :header => 'Repository for operating systems', | ||
| :text => _('This Repository will be restricted only to hosts that runs the selected operating system') %> | ||
| <%= multiple_selects(f, :operatingsystems, ::Redhat, f.object.operatingsystem_ids) %> | ||
| </div> | ||
| </div> | ||
| <%= submit_or_cancel f %> | ||
| <% end %> |
| class CreateContentEnvironmentsProducts < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_environment_products do |t| | ||
| t.references :environment, :null =>false | ||
| t.references :product, :null =>false | ||
| end | ||
| add_index(:content_environment_products, [:environment_id, :product_id],:name=>'environment_products_index', :unique=>true) | ||
| end | ||
| end |
| class CreateContentProductOperatingsystems < ActiveRecord::Migration | ||
| def change | ||
| create_table :content_product_operatingsystems do |t| | ||
| t.references :operatingsystem, :null =>false | ||
| t.references :product, :null =>false | ||
| end | ||
| add_index(:content_product_operatingsystems, [:operatingsystem_id, :product_id],:name=>'product_operatingsystems_index', :unique=>true) | ||
| end | ||
| end |