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.3
to
0.4
+19
app/assets/javascripts/content/repository.js
$(function () {
// Disable repository sync input based on checkbox
$('#content_repository_schedule').click(function () {
$('#sync_schedule_form select').attr('disabled', !$(this).is(':checked'));
});
// try to guess architecture based on the URL
$('#content_repository_feed').focusout(function() {
url = $(this).val();
if (url == undefined || url == '')
return;
$('#content_repository_architecture_id option').each (function ( index, option) {
if (url.match(option.text)) {
$('#content_repository_architecture_id').val(option.value);
return false;
}
})
});
});
module Content::RepositoryCommon
extend ActiveSupport::Concern
REPO_PREFIX = '/pulp/repos/'
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
<%= wizard_header 2, _("Select source type"), _("Select content"), _("Create") %>
<%= form_for @factory, :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 %>
<% title @content_view.to_label %>
<%= title_actions(button_group(
link_to_if_authorized(_("Edit"), hash_for_edit_content_view_path(@content_view)),
display_delete_if_authorized(hash_for_content_view_path(@content_view), :class => 'btn-danger', :confirm => "Delete #{@content_view.name}?")
)) %>
<h3><%= _('Repositories') %></h3>
<table class="table table-bordered table-striped">
<tr>
<th><%= sort :name, :as => s_("Name") %></th>
<th><%= _("State") %></th>
<th><%= _("Last sync status") %></th>
<th><%= _("Content type") %></th>
</tr>
<% @content_view.repository_clones.each do |repository| %>
<tr>
<%= render :partial => 'content/repositories/repository', :locals => { :repository => repository } %>
</tr>
<% end %>
</table>
<%= 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 %>
<%= checkbox_f f, :publish, { :disabled => !f.object.new_record?,
:help_inline => _('should this repository be directly accessible or as a feed for content views?') } %>
<%= select_f f, :gpg_key_id, Content::GpgKey.all, :id, :name, {:include_blank => true}, {:label => _("GPG Key")} %>
<%= f.hidden_field :type if f.object.new_record? %>
<td><%= link_to_if_authorized(h(repository.name), hash_for_repository_path(repository)) %></td>
<td><%= repository.try(:state) %></td>
<td><%= last_time(repository.kind_of?(Content::Repository) ? repository.last_sync : repository.last_published) %></td>
<td><%= repository.content_type %></td>
<%= checkbox_f f, :schedule, :label => _('Schedule Sync'),
:help_inline => _('Sync this repository periodically') %>
<div class='control-group'>
<div class='controls'>
<%= sync_schedule_selector(f) %>
</div>
</div>
class ChangeContentViewIndexName < ActiveRecord::Migration
OLD_NAME = :index_content_content_views_on_originator_id_and_originator_type
def up
rename_index :content_content_views, OLD_NAME, :content_view_id_type_index if index_name_exists? :content_content_views, OLD_NAME, nil
end
def down
rename_index :content_content_views, :content_view_id_type_index, OLD_NAME
end
end
class AddPublishBooleanToContentRepository < ActiveRecord::Migration
def change
add_column :content_repositories, :publish, :boolean
add_index :content_repositories, :publish, :name => :content_repo_publish_index
end
end
class ChangeRepositoryToPolymorphic < ActiveRecord::Migration
def up
add_column :content_repositories, :originator_type, :string
add_column :content_repositories, :originator_id, :integer
Content::Repository.reset_column_information
Content::Repository.all.each do |repo|
if repo.operatingsystem_id
repo.originator_type = 'Operatingsystem'
repo.originator_id = repo.operatingsystem_id
elsif repo.product_id
repo.originator_type = 'Content::Product'
repo.originator_id = repo.product_id
else
raise "invalid repo type, can't continue: #{repo.inspect}"
end
repo.save(:validate => false)
end
remove_column :content_repositories, :operatingsystem_id
remove_column :content_repositories, :product_id
end
def down
add_column :content_repositories, :product_id, :integer
add_column :content_repositories, :operatingsystem_id, :integer
Content::Repository.reset_column_information
Content::Repository.all.each do |repo|
if repo.originator_type && repo.originator_id
case repo.originator_type
when 'Operatingsystem'
repo.operatingsystem_id = repo.originator_id
when 'Content::Product'
repo.product_id = repo.originator_id
end
repo.save(:validate => false)
end
end
remove_column :content_repositories, :originator_type
remove_column :content_repositories, :originator_id
end
end
class AddPolymorphicRepoToContentViewRepositoryClone < ActiveRecord::Migration
def change
add_column :content_content_view_repository_clones, :repository_type, :string
Content::ContentViewRepositoryClone.reset_column_information
Content::ContentViewRepositoryClone.update_all(:repository_type => 'Content::RepositoryClone')
rename_column :content_content_view_repository_clones, :repository_clone_id, :repository_id
end
end
+23
-12

@@ -1,12 +0,23 @@

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
// GO AFTER THE REQUIRES BELOW.
//
$(function () {
// Disable repository selection input based on checkbox
$('.repository_selection input:checkbox').click(function () {
var checked = $(this).is(':checked');
$(this).parent().find($(':input')).each (function ( index, input) {
$(input).attr('disabled', !checked);
});
});
// toggle repository ID's to send based on the repo type
$('.cv-repo-selection').change(function () {
enable_selected_repository(this);
});
$('.cv-repo-selection').change();
});
function enable_selected_repository(element) {
var select = $(element);
var id = select.attr('id').replace(/[^\d]/g, '');
var clone = select.val() == 'clone';
$('#clone-' + id).attr('disabled', !clone);
$('#latest-' + id).attr('disabled', clone);
};

@@ -8,12 +8,25 @@ module Content

@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
paginate(:page => params[:page])
@counter = ContentViewRepositoryClone.where(:content_view_id => @content_views.map(&:id)).group(:content_view_id).count
end
def show
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]
options = if params[:product]
{:originator_id => params[:product], :originator_type => 'Content::Product'}
elsif params[:operatingsystem]
{:originator_id => params[:operatingsystem], :originator_type => 'Operatingsystem'}
elsif params[:hostgroup]
@hostgroup = Hostgroup.find_by_id(params[:hostgroup])
{:originator_id => params[:hostgroup], :originator_type => 'Hostgroup'}
elsif params[:content_content_view_factory]
params[:content_content_view_factory]
elsif params[:type].blank?
process_error(:error_msg => _('Must provide a type'))
end
@factory = ContentViewFactory.new(options || {})
@content_view = @factory.try(:content_view) if @factory.originator_id
end

@@ -20,0 +33,0 @@

@@ -9,3 +9,3 @@ module Content

paginate(:page => params[:page])
@counter = Repository::Product.group(:product_id).where(:product_id => @products.map(&:id)).count
@counter = Repository::Product.group(:originator_id).where(:originator_id => @products.map(&:id)).count
end

@@ -12,0 +12,0 @@

module Content
class RepositoriesController < ::ApplicationController
include Foreman::Controller::AutoCompleteSearch
before_filter :find_by_name, :only => %w{show edit update destroy sync}
before_filter :find_repo, :only => %w{show edit update destroy sync}
def index
@repositories = Repository.search_for(params[:search], :order => params[:order]).
paginate(:page => params[:page]).includes(:product, :operatingsystem)
paginate(:page => params[:page])
end

@@ -16,3 +16,3 @@

when "product"
Repository::Product.new(:product_id => params[:product_id])
Repository::Product.new(:originator_id => params[:product_id])
else

@@ -60,3 +60,9 @@ not_found

end
private
def find_repo
@repository = Content::Repository.find(params[:id])
end
end
end
module Content
module ContentViewsHelper
def repositories(view)
if view.new_record?
view.source_repositories + view.repository_clones
def repositories
cv_repos = @content_view.repository_clones + @content_view.repository_sources
if @content_view.new_record? && cv_repos.empty? # new form
@factory.repositories
else
view.repository_clones
cv_repos # edit or new after validation failure
end

@@ -20,3 +22,21 @@ end

def step?
if params[:type]
'step1'
elsif @hostgroup
'composite'
else
'form'
end
end
def options_for_type_selection repo
options = [[_('Clone (snapshot)'), 'clone']]
options << [_('Latest'),'latest'] if repo.kind_of?(Content::Repository) && repo.publish?
selected = 'clone' if @factory # default
selected ||= @content_view.repository_clone_ids.include?(repo.id) ? 'clone' : 'latest' # on validation error
options_for_select(options, selected)
end
end
end

@@ -9,3 +9,31 @@ module Content

def sync_schedule schedules
return _('None') if schedules.empty?
schedules.map{|s| s[:schedule]}.join(', ')
rescue => e
logger.warn _("Failed to fetch sync schedule: ") + e.to_s
_('unknown')
end
def time_selector f
html_options = { :class => 'span1', :disabled => f.object.schedule.blank? }
f.select(:hour, options_for_time(0..23), {}, html_options) +
f.select(:minute, options_for_time(0..59), {}, html_options)
end
def sync_schedule_selector f
content_tag :div, :id => 'sync_schedule_form', :class => 'input-prepend input-append' do
f.select(:interval, [%w(Daily D), %w(Weekly W)], {}, { :disabled => f.object.schedule.blank? , :class => 'span1' }) +
content_tag(:span, :class => 'add-on') { '@' } +
time_selector(f)
end
end
private
def options_for_time(options)
options.map { |i| [sprintf("%02d", i), i] }
end
end
end

@@ -41,3 +41,3 @@ module Content::HostExtensions

where(:content_available_content_views => {:environment_id => environment_id}).
where(:originator_type => 'Hostgroup', :originator_id => hostgroup_id).pluck(:id)
where(:originator_type => 'Hostgroup', :originator_id => hostgroup_id).pluck('content_content_views.id')
end

@@ -44,0 +44,0 @@

@@ -5,3 +5,3 @@ module Content::OperatingsystemExtensions

included do
has_many :repositories, :class_name => 'Content::Repository'
has_many :repositories, :class_name => 'Content::Repository', :as => :originator

@@ -8,0 +8,0 @@ has_many :available_content_views, :dependent => :destroy, :class_name => 'Content::AvailableContentView'

@@ -6,3 +6,3 @@ module Content::Orchestration::Pulp

included do
delegate :sync_status, :sync, :counters, :sync_history, :state, :to => :repo
delegate :sync_status, :sync, :sync_schedule, :counters, :sync_history, :state, :to => :repo
end

@@ -9,0 +9,0 @@

@@ -8,2 +8,5 @@ module Content::Orchestration::Pulp::Sync

before_destroy :queue_pulp_destroy unless Rails.env.test?
attr_accessor :interval, :hour, :minute, :schedule
validates :interval, :inclusion => { :in => %w(D W) }, :if => :schedule_sync?
validates :hour, :minute, :numericality => true, :if => :schedule_sync?
end

@@ -26,4 +29,10 @@

:action => [self, :set_pulp_repo])
logger.debug "Scheduling new Pulp Repository Sync"
queue.create(:name => _("Sync Pulp Repository %s") % self, :priority => 20,
:action => [self, :set_sync_pulp_repo])
if schedule_sync?
logger.debug "Scheduling Pulp Repository sync scheduler"
queue.create(:name => _("Sync Schedule Pulp Repository %s") % self, :priority => 30,
:action => [self, :set_sync_schedule_pulp_repo])
end
end

@@ -43,3 +52,3 @@

def set_pulp_repo
repo.create
publish ? repo.create_with_distributor : repo.create
end

@@ -59,2 +68,10 @@

def set_sync_schedule_pulp_repo
repo.sync_schedule = sync_schedule_time
end
def del_sync_schedule_pulp_repo
repo.sync_schedule = nil
end
def repo_options

@@ -68,2 +85,4 @@ {

:protected => unprotected,
:relative_path => relative_path,
:auto_publish => publish
}

@@ -75,2 +94,18 @@ end

end
end
def relative_path
"#{entity_name}/#{name}"
end
def schedule_sync?
schedule.present? && schedule != '0'
end
def sync_schedule_time
return if schedule.blank?
time = Time.parse("00:#{hour}:#{minute}").iso8601
repetition = "R1" # no limit of hour many syncs
"#{repetition}/#{time}/P1#{interval}"
end
end

@@ -9,3 +9,3 @@ module Content::RedhatExtensions

def medium_uri_with_content_uri host, url = nil
if url.nil? && (full_path = Content::Repository.available_for_host(host).kickstart.first.try(:full_path))
if url.nil? && (full_path = kickstart_repo(host).try(:full_path))
URI.parse(full_path)

@@ -20,5 +20,12 @@ else

def repos host
host.attached_repositories.yum.map { |repo| format_repo(repo) }
yum_repos(host).map { |repo| format_repo(repo) }
end
def boot_files_uri(medium, architecture)
Redhat::PXEFILES.values.collect do |img|
medium_vars_to_uri("#{medium.path}/#{pxedir}/#{img}", architecture.name, self)
URI.parse(interpolate_medium_vars(url, arch, os)).normalize
end
end
private

@@ -30,3 +37,3 @@ # convert a repository to a format that kickstart script can consume

:name => repo.to_label,
:description => repo.product.description,
:description => repo.description,
:enabled => repo.enabled,

@@ -36,2 +43,11 @@ :gpgcheck => !!repo.gpg_key

end
def kickstart_repo host
host.attached_repositories.detect{|r| r.content_type == Content::Repository::KICKSTART_TYPE}
end
def yum_repos host
host.attached_repositories.select{|r| r.content_type == Content::Repository::YUM_TYPE}
end
end
class Content::ContentViewRepositoryClone < ActiveRecord::Base
belongs_to :content_view
belongs_to :repository_clone
validate :content_view_id, :repository_clone_id, :presence => true
belongs_to :repository, :polymorphic => true
validate :content_view_id, :repository_id, :repository_type, :presence => true
end
module Content
class ContentView < ActiveRecord::Base
has_ancestry :orphan_strategy => :rootify
attr_accessor :repository_ids_to_clone

@@ -13,6 +14,11 @@ belongs_to :originator, :polymorphic => true

before_destroy :clean_unused_clone_repos
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
has_many :repository_clones, :through => :content_view_repository_clones,
:source => :repository, :source_type => 'Content::RepositoryClone'
has_many :repository_sources, :through => :content_view_repository_clones,
:source => :repository, :source_type => 'Content::Repository'
scope :hostgroups, where(:originator_type => 'Hostgroup')

@@ -22,6 +28,6 @@ scope :products, where(:originator_type => 'Content::Product')

after_save :clone_repos
before_destroy :clean_unused_clone_repos
after_create :clone_repos
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:originator_id, :originator_type]

@@ -43,4 +49,2 @@ # special relationships needed for search with polymorphic associations

attr_accessor :source_repositories
def to_label

@@ -50,18 +54,20 @@ name || "#{originator.to_label} - #{DateTime.now.strftime("%m/%d/%Y")}".parameterize

private
def clone_repos
return unless @source_repositories
Repository.where(:id => @source_repositories).each do |repository|
repository.publish self
return unless repository_ids_to_clone
Repository.where(:id => repository_ids_to_clone).each do |repository|
repository.clone 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)
current_repos = Content::ContentViewRepositoryClone.where(:content_view_id => id, :repository_type => 'Content::RepositoryClone').pluck(:repository_id)
used_repos = Content::ContentViewRepositoryClone.
where(:repository_id => current_repos).
where(['content_view_id IS NOT ?', id]).pluck(:repository_id)
repos_to_delete = current_repos - used_repos
logger.debug('All Cloned repositories are used elsewhere, nothing to do') if repos_to_delete.empty?
logger.debug("Clone Repos IDS: #{repos_to_delete.join(', ')} are unused - deleting...") if repos_to_delete.any?
Content::RepositoryClone.destroy(repos_to_delete) if repos_to_delete.any?

@@ -68,0 +74,0 @@ end

@@ -5,3 +5,3 @@ module Content

has_many :repositories
has_many :repositories, :as => :originator
has_many :repository_clones, :through => :repositories

@@ -8,0 +8,0 @@ has_many :content_views, :as => :originator

module Content
class RepositoryClone < ActiveRecord::Base
include Content::Orchestration::Pulp::Clone
include Content::RepositoryCommon
REPO_PREFIX = '/pulp/repos/'
belongs_to :repository
has_many :content_view_repository_clones, :dependent => :destroy
has_many :content_view_repository_clones, :dependent => :destroy, :as => :repository
has_many :content_views, :through => :content_view_repository_clones

@@ -15,3 +14,3 @@ before_destroy EnsureNotUsedBy.new(:content_views)

delegate :content_type, :architecture, :unprotected, :gpg_key, :product, :to => :repository
delegate :content_type, :architecture, :unprotected, :gpg_key, :product, :operatingsystem, :enabled, :to => :repository

@@ -23,9 +22,5 @@ scope :for_content_views, lambda { |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
default_scope { includes(:repository) }
end
end

@@ -5,2 +5,3 @@ module Content

include Foreman::STI
include Content::RepositoryCommon

@@ -12,8 +13,9 @@ YUM_TYPE = 'yum'

belongs_to :product, :class_name => 'Content::Product'
belongs_to :operatingsystem
belongs_to :originator, :polymorphic => true
belongs_to :gpg_key
belongs_to :architecture
has_many :repository_clones
has_many :content_view_repository_clones, :dependent => :destroy, :as => :repository
before_validation :set_originator_type
before_destroy EnsureNotUsedBy.new(:repository_clones)

@@ -23,4 +25,7 @@

validates_presence_of :originator_id, :originator_type
validates :name, :presence => true
validates_uniqueness_of :name, :scope => [:operatingsystem_id, :product_id]
validates_uniqueness_of :name, :scope => [:originator_type, :originator_id]
validates :feed, :presence => true, :uniqueness => true
validates_inclusion_of :content_type,

@@ -33,4 +38,10 @@ :in => TYPES,

scoped_search :in => :architecture, :on => :name, :rename => :architecture, :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
belongs_to :search_operatingsystems, :class_name => 'Operatingsystem', :foreign_key => :originator_id,
:conditions => '"content_repositories"."originator_type" = "Operatingsystem"'
belongs_to :search_products, :class_name => 'Content::Product', :foreign_key => :originator_id,
:conditions => '"content_repositories"."originator_type" = "Content::Product"'
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

@@ -40,2 +51,7 @@ scope :kickstart, where(:content_type => KICKSTART_TYPE)

scope :for_content_views, lambda { |ids|
joins(:content_view_repository_clones).
where('content_content_view_repository_clones' => {:content_view_id => ids})
}
def content_types

@@ -47,3 +63,3 @@ TYPES

def to_label
"#{entity_name}-#{name}".parameterize
"#{entity_name}/#{name}"
end

@@ -56,7 +72,7 @@

def publish content_view
def clone content_view
repository_clones.create!(
:content_views => [content_view],
:name => self.name + "_clone",
:relative_path => "content_views/#{to_label}/#{Foreman.uuid}"
:relative_path => "content_views/#{entity_name}/#{name}/#{Foreman.uuid}"
)

@@ -63,0 +79,0 @@ end

module Content
class Repository::OperatingSystem < Repository
alias_method :operatingsystem, :originator
validates_presence_of :operatingsystem
def self.model_name

@@ -13,3 +12,13 @@ Repository.model_name

end
def description
entity_name
end
private
def set_originator_type
self.originator_type ||= 'Operatingsystem'
end
end
end
module Content
class Repository::Product < Repository
alias_method :product, :originator
has_many :operatingsystem_repositories, :foreign_key => :repository_id, :dependent => :destroy, :uniq => true
has_many :operatingsystems, :through => :operatingsystem_repositories
delegate :description, :to => :product
validates_presence_of :product
def self.model_name

@@ -20,3 +19,9 @@ Repository.model_name

end
private
def set_originator_type
self.originator_type = 'Content::Product'
end
end
end
module Content
class ContentViewFactory
include ActiveModel::Conversion
extend ActiveModel::Naming
include ActiveModel::Validations
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)
attr_accessor :originator_type, :originator_id, :parent_cv, :product_cv, :os_cv, :source_repositories
validates_presence_of :originator_type, :originator_id
def initialize(attributes = {})
attributes.each do |name, value|
instance_variable_set("@#{name}", value) if respond_to?("#{name}".to_sym)
end
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)
def persisted?
false
end
def content_view
case originator_type
when 'Hostgroup'
create_composite_content_view
else
ContentView.new(:repository_ids_to_clone => originator.repositories, :originator => originator)
end
end
def repositories
originator.repositories
end
private
def originator
originator ||= case originator_type
when 'Content::Product'
Product.find(originator_id)
when 'Operatingsystem'
Operatingsystem.find(originator_id)
when 'Hostgroup'
Hostgroup.find(originator_id)
else
raise _('Unknown type')
end
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)
def create_composite_content_view
clone_ids = Content::RepositoryClone.for_content_views([product_cv, os_cv]).pluck(:id)
source_ids = Content::Repository.for_content_views([product_cv, os_cv]).pluck(:id)
ContentView.new(:originator => originator,
:repository_clone_ids => clone_ids,
:repository_source_ids => source_ids,
:parent_id => 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
class Content::Pulp::RepositorySyncHistory
attr_reader :exception, :removed_count, :started, :completed, :summary,
attr_reader :exception, :removed_count, :started, :completed,
:error_message, :added_count, :updated_count, :details, :result

@@ -15,3 +15,3 @@

:errata => summary[:errata_time_total_sec],
:packages => summary[:packages][:time_total_sec]
:packages => (summary[:packages][:time_total_sec] rescue 0)
}

@@ -36,2 +36,8 @@ end

private
def summary
@summary ||= Hash.new(0)
end
end

@@ -129,2 +129,15 @@ class Content::Pulp::Repository

def sync_schedule=(schedule)
type = Runcible::Extensions::YumImporter::ID
if schedule.present?
Runcible::Resources::RepositorySchedule.create(pulp_id, type, schedule)
else
Runcible::Extensions::Repository.remove_schedules(pulp_id, type)
end
end
def sync_schedule
Runcible::Resources::RepositorySchedule.list(pulp_id, Runcible::Extensions::YumImporter::ID)
end
private

@@ -131,0 +144,0 @@

@@ -0,1 +1,3 @@

<%= wizard_header 2, _('Select source type'), _('Select content'), _('Create') %>
<%= form_for @content_view, :url => (@content_view.new_record? ? content_views_path : content_view_path(@content_view)) do |f| %>

@@ -14,9 +16,2 @@ <%= base_errors_for @content_view %>

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

@@ -28,13 +23,21 @@ <%= f.hidden_field :originator_type, :value => @content_view.originator_type %>

<tr>
<th></th>
<th><%= _("Name") %></th>
<th><%= _("State") %></th>
<th><%= _("Last publish") %></th>
<th><%= f.object.kind_of?(Content::Repository) ? _('Last Sync') : _('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>
<% repositories.each do |repository| %>
<tr class='repository_selection'>
<td>
<% if @content_view.new_record? %>
<%= check_box_tag nil, repository.id, true %>
<%= f.hidden_field :repository_ids_to_clone, :value => repository.id, :multiple => true, :id => "clone-#{repository.id}",
:disabled => true %>
<%= f.hidden_field :repository_source_ids, :value => repository.id, :multiple => true, :id => "latest-#{repository.id}",
:disabled => true %>
<%= select_tag(nil, options_for_type_selection(repository), {:class => 'cv-repo-selection span2', :id => "select-#{repository.id}"}) %>
<% end %>
</td>
<%= render :partial => 'content/repositories/repository', :locals => {:repository => repository} %>
</tr>

@@ -41,0 +44,0 @@ <% end %>

@@ -5,3 +5,3 @@ <% title _("Content Views") %>

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'))) %>
display_link_if_authorized(_("Composite hostgroup view"), hash_for_new_content_view_path(:type=>'hostgroup'))) %>

@@ -18,7 +18,8 @@

<tr>
<td><%= link_to_if_authorized(h(content_view.name), hash_for_edit_content_view_path(content_view)) %></td>
<td><%= link_to_if_authorized(h(content_view.name), hash_for_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}?")
display_link_if_authorized(_("Edit"), hash_for_edit_content_view_path(content_view)),
display_delete_if_authorized(hash_for_content_view_path(content_view), :confirm => "Delete #{content_view}?")
)%></td>

@@ -25,0 +26,0 @@ </tr>

@@ -5,8 +5,2 @@ <%= javascript 'content/content.js' %>

<% if @content_view %>
<%= render :partial => 'form' %>
<% elsif @hostgroup %>
<%= render :partial => 'step2' %>
<% else %>
<%= render :partial => 'step1' %>
<% end %>
<%= render :partial => step? %>
<%= javascript 'content/content.js' %>
<%= form_for @product, :url => (@product.new_record? ? products_path : product_path(@product)) do |f| %>
<%= base_errors_for @product %>
<ul class="nav nav-tabs" data-tabs="tabs">
<li class="active"><a href="#primary" data-toggle="tab"><%= _("Product") %></a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="primary">
<%= text_f f, :name %>
<%= textarea_f f, :description, :class => "input-xlarge" , :rows=> '3' %>
</div>
</div>
<%= text_f f, :name %>
<%= textarea_f f, :description, :class => "input-xlarge", :rows => '3' %>
<%= submit_or_cancel f %>
<% end %>

@@ -1,2 +0,1 @@

<%= javascript 'content/content.js' %>
<%= form_for @repository, :url => (@repository.new_record? ? repositories_path : repository_path(:id => @repository.id)) do |f| %>

@@ -6,18 +5,14 @@ <%= base_errors_for @repository %>

<li class="active"><a href="#primary" data-toggle="tab"><%= _("Repository") %></a></li>
<li><a href="#sync_schedule" data-toggle="tab"><%= _("Sync Schedule") %></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")} %>
<%= select_f f, :originator_id, ::Redhat.all, :id, :to_label, {}, {:label => _("Operating System")} %>
<%= render :partial => 'fields', :locals => {:f => f} %>
</div>
<div class="tab-pane" id="sync_schedule">
<%= render :partial => 'sync_schedule', :locals => { :f => f } %>
</div>
</div>
<%= submit_or_cancel f %>
<% end %>

@@ -1,2 +0,1 @@

<%= javascript 'content/content.js' %>
<%= form_for @repository, :url => (@repository.new_record? ? repositories_path : repository_path(:id => @repository.id)) do |f| %>

@@ -7,15 +6,8 @@ <%= base_errors_for @repository %>

<li><a href="#operatingsystems" data-toggle="tab"><%= _("Operating systems") %></a></li>
<li><a href="#sync_schedule" data-toggle="tab"><%= _("Sync Schedule") %></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")} %>
<%= select_f f, :originator_id, Content::Product.all, :id, :name, { :prompt => _('Please Select') }, { :label => _("Product") } %>
<%= render :partial => 'fields', :locals => { :f => f } %>
</div>

@@ -27,4 +19,8 @@ <div class="tab-pane" id="operatingsystems">

</div>
<div class="tab-pane" id="sync_schedule">
<%= render :partial => 'sync_schedule', :locals => { :f => f } %>
</div>
</div>
<%= submit_or_cancel f %>
<% end %>

@@ -0,1 +1,3 @@

<%= javascript 'content/repository.js' %>
<% title _("Edit Repository") %>

@@ -2,0 +4,0 @@ <% if @repository.is_a? Content::Repository::OperatingSystem %>

@@ -10,3 +10,2 @@ <% title _("Repositories") %>

<th><%= sort :name, :as => s_("Name") %></th>
<th><%= _("Product") %></th>
<th><%= _("State") %></th>

@@ -17,18 +16,17 @@ <th><%= _("Last sync status") %></th>

</tr>
<% @repositories.each do |repository| %>
<tr>
<td><%= link_to_if_authorized(h(repository.name), hash_for_repository_path(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}?")
) %>
<% @repositories.group_by(&:originator).each do |originator, repositories| %>
<tr><td colspan = '5'><%= originator %></td></tr>
<% repositories.each do |repository| %>
<tr>
<%= render :partial => 'repository', :locals => { :repository => repository } %>
<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}?")
) %>
</td>
</tr>
</td>
</tr>
<% end %>
<% end %>

@@ -35,0 +33,0 @@ </table>

@@ -0,1 +1,3 @@

<%= javascript 'content/repository.js' %>
<% title _("New Repository") %>

@@ -2,0 +4,0 @@ <% if @repository.is_a? Content::Repository::OperatingSystem %>

@@ -44,2 +44,13 @@ <% javascript 'charts' %>

</tr>
<% if @repository.publish %>
<tr>
<td> <%= _("URL") %> </td>
<td> <%= @repository.full_path %> </td>
</tr>
<% end %>
<tr>
<td> <%= _('Sync Schedule') %> </td>
<td> <%= sync_schedule(@repository.sync_schedule) %> </td>
</tr>
</table>

@@ -49,45 +60,48 @@ </div>

<div class="row-fluid">
<div class="stats-well span4">
<h4 class="ca" ><%= _('Last Update Metrics') -%></h4>
<div style="margin-top:50px;padding-bottom: 40px;">
<%= flot_pie_chart("metrics" ,_("Last Update Metrics"), @repository.sync_history.last.try(:times), :class => "statistics-pie small")%>
<%# running sync %>
<% if @repository.sync_history.any? %>
<div class="stats-well span4">
<h4 class="ca" ><%= _('Last Update Metrics') -%></h4>
<div style="margin-top:50px;padding-bottom: 40px;">
<%= flot_pie_chart("metrics" ,_("Last Update Metrics"), @repository.sync_history.last.try(:times), :class => "statistics-pie small") %>
</div>
</div>
</div>
<div class="stats-well span4">
<h4 class="ca" ><%= _('Update Summary') -%></h4>
<%= flot_bar_chart("status" ,"", _("Number of packages"), @repository.sync_history.last.try(:metrics), :class => "statistics-bar")%>
</div>
<div class="stats-well span4">
<h4 class="ca" ><%= _('Update Summary') -%></h4>
<%= flot_bar_chart("status" ,"", _("Number of packages"), @repository.sync_history.last.try(:metrics), :class => "statistics-bar")%>
</div>
<div class="span4">
<table class="table table-bordered table-striped">
<tr>
<th><%= _('Repository Counters') %></th>
<th></th>
</tr>
<% @repository.counters.each do |name, value| -%>
<div class="span4">
<table class="table table-bordered table-striped">
<tr>
<td> <%= name.to_s.humanize %> </td>
<td> <%= value %> </td>
<th><%= _('Repository Counters') %></th>
<th></th>
</tr>
<% end -%>
<tr>
<td> <%= _("Last synchronized") %> </td>
<td> <%= last_time @repository.last_sync %> </td>
</tr>
</table>
</div>
<div class="span4">
<table class="table table-bordered table-striped">
<tr>
<th><%= _('Last Sync') %></th>
<th></th>
</tr>
<% @repository.sync_history.last.status.each do |name, value| -%>
<% @repository.counters.each do |name, value| -%>
<tr>
<td> <%= name.to_s.humanize %> </td>
<td> <%= value %> </td>
</tr>
<% end -%>
<tr>
<td> <%= name.to_s.humanize %> </td>
<td> <%= value %> </td>
<td> <%= _("Last synchronized") %> </td>
<td> <%= last_time @repository.last_sync %> </td>
</tr>
<% end -%>
</table>
</div>
</table>
</div>
<div class="span4">
<table class="table table-bordered table-striped">
<tr>
<th><%= _('Last Sync') %></th>
<th></th>
</tr>
<% @repository.sync_history.last.status.each do |name, value| -%>
<tr>
<td> <%= name.to_s.humanize %> </td>
<td> <%= value %> </td>
</tr>
<% end -%>
</table>
</div>
<% end %>
</div>

@@ -12,5 +12,5 @@ class CreateContentContentViews < ActiveRecord::Migration

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_id, :originator_type], :name => 'content_view_id_type_index'
add_index :content_content_views, :originator_type
end
end
module Content
VERSION = "0.3"
VERSION = "0.4"
end

@@ -32,2 +32,3 @@ # foreman\_content

* enable oauth authentication
* make sure you have enough disk space! lots of GB to /var/lib/pulp and /var/lib/mongo

@@ -54,3 +55,8 @@ ## UI config

example, CentOS 6.4 + CentOS updates.
* Content View - a collection of cloned repositories
## Create a Product
If you are planning to sync non OS packages (e.g. 3rd party yum repo), you should first create a product, e.g. Foreman 1.2-stable, afterwards, you can add repositories to it.
## Syncing Repositories

@@ -68,4 +74,2 @@

* Enabled - true / false
* Unprotected - weather pulp should expose this repo via http and https or just
over https.
* Product - the product from above.

@@ -72,0 +76,0 @@ * GPG key - not implemented

<%= 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 %>