Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

foreman_content

Package Overview
Dependencies
Maintainers
3
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

foreman_content - rubygems Package Compare versions

Comparing version
0.2
to
0.3
+48
app/controllers/content/content_views_controller.rb
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
+3
-5

@@ -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>

@@ -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

@@ -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