Skip to content

Commit

Permalink
Fixes #31425 - Render statuses
Browse files Browse the repository at this point in the history
  • Loading branch information
kamils-iRonin committed Feb 15, 2024
1 parent 16fa092 commit b1f012b
Show file tree
Hide file tree
Showing 35 changed files with 655 additions and 1 deletion.
19 changes: 19 additions & 0 deletions app/controllers/api/v2/render_statuses_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Api
module V2
class RenderStatusesController < V2::BaseController
api :GET, "/hosts/:host_id/render_statuses/", N_("List all render statuses for a given host")
api :GET, "/hostgroups/:host_id/render_statuses/", N_("List all render statuses for a given hostgroup")
api :GET, "/provisioning_templates/:provisioning_template_id/render_statuses/", N_("List all render statuses for a given provisioning template")

param :host_id, :identifier, desc: N_("ID of host")
param :hostgroup_id, :identifier, desc: N_("ID of hostgroup")
param :provisioning_template_id, :identifier, desc: N_("ID of provisioning template")
param_group :search_and_pagination, ::Api::V2::BaseController
add_scoped_search_description_for(RenderStatus)

def index
@render_statuses = resource_scope_for_index.includes(:host, :hostgroup, :provisioning_template)
end
end
end
end
2 changes: 2 additions & 0 deletions app/models/host/managed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ class Host::Managed < Host::Base
has_one :build_status_object, :class_name => 'HostStatus::BuildStatus', :foreign_key => 'host_id'
before_destroy :remove_reports

has_many :render_statuses, foreign_key: :host_id, inverse_of: :host, dependent: :destroy, autosave: true

def self.complete_for(query, opts = {})
matcher = /(\s*(?:(?:user\.[a-z]+)|owner)\s*[=~])\s*(\S*)\s*\z/
matches = matcher.match(query)
Expand Down
2 changes: 2 additions & 0 deletions app/models/hostgroup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class Hostgroup < ApplicationRecord
has_many :template_combinations, :dependent => :destroy
has_many :provisioning_templates, :through => :template_combinations

has_many :render_statuses, inverse_of: :hostgroup, dependent: :destroy, autosave: true

belongs_to :domain
belongs_to :subnet
belongs_to :subnet6, :class_name => "Subnet"
Expand Down
7 changes: 7 additions & 0 deletions app/models/provisioning_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def base_class
:reject_if => :reject_template_combination_attributes?
has_and_belongs_to_many :operatingsystems, :join_table => :operatingsystems_provisioning_templates, :association_foreign_key => :operatingsystem_id, :foreign_key => :provisioning_template_id
has_many :os_default_templates

has_many :render_statuses, foreign_key: :template_id, inverse_of: :provisioning_template, dependent: :destroy

before_save :check_for_snippet_assoications

validate :no_os_for_registration
Expand Down Expand Up @@ -233,6 +236,10 @@ def host_init_config_template?
try(:template_kind)&.name == 'host_init_config'
end

def render_statuses_enabled?
!snippet?
end

private

def import_custom_data(options)
Expand Down
24 changes: 24 additions & 0 deletions app/models/render_status.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class RenderStatus < ApplicationRecord
include Authorizable

belongs_to :host, class_name: 'Host::Managed'
belongs_to :hostgroup
belongs_to :provisioning_template, foreign_key: :template_id

validate :host_or_hostgroup_presence
validates :host, uniqueness: { scope: [:provisioning_template, :safemode] }
validates :hostgroup, uniqueness: { scope: [:provisioning_template, :safemode] }
validates :provisioning_template, presence: true
validates :safemode, inclusion: [true, false]
validates :success, inclusion: [true, false]

scoped_search on: :updated_at, default_order: :asc
scoped_search on: :safemode

private

def host_or_hostgroup_presence
errors.add(:base, "can only have host or hostgroup") if host.present? && hostgroup.present?
errors.add(:base, "host or hostgroup is required") if host.blank? && hostgroup.blank?
end
end
4 changes: 4 additions & 0 deletions app/models/template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ def host_init_config_template?
false
end

def render_statuses_enabled?
false
end

private

# This method can be overridden in Template children classes to import additional attributes
Expand Down
5 changes: 5 additions & 0 deletions app/registries/foreman/access_permissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,11 @@
:"api/v2/personal_access_tokens" => [:destroy]
end

permission_set.security_block :render_statuses do |map|
map.permission :view_render_statuses,
:"api/v2/render_statuses" => [:index]
end

permission_set.security_block :settings do |map|
map.permission :view_settings, { :settings => [:index, :show, :auto_complete_search],
:'api/v2/settings' => [:index, :show] }
Expand Down
29 changes: 28 additions & 1 deletion app/services/foreman/renderer/base_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def render
end

def self.render(source, scope)
result = new(source, scope).render
renderer_instance = new(source, scope)
result = renderer_instance.render

digest = Digest::SHA256.hexdigest(result)
if !scope.preview? && source.template&.log_render_results?
Foreman::Logging.blob("Unattended render of '#{source.name}' = '#{digest}'", result,
Expand All @@ -24,7 +26,32 @@ def self.render(source, scope)
template_host_name: scope.host.try(:name),
template_host_id: scope.host.try(:id))
end

if !scope.preview? && source.template&.render_statuses_enabled?
Foreman::Renderer::RenderStatusService.success(
host: scope.host,
provisioning_template: source.template,
safemode: safemode
)
end

result
rescue StandardError => e
if !scope.preview? && source.template&.render_statuses_enabled?
Foreman::Renderer::RenderStatusService.error(
host: scope.host,
provisioning_template: source.template,
safemode: safemode
)
end

raise e
end

def self.safemode
self::SAFEMODE
rescue NameError
raise NotImplementedError
end

private
Expand Down
59 changes: 59 additions & 0 deletions app/services/foreman/renderer/render_status_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
module Foreman
module Renderer
class RenderStatusService
def self.success(host:, provisioning_template:, safemode:)
call(host, provisioning_template, safemode, true)
end

def self.error(host:, provisioning_template:, safemode:)
call(host, provisioning_template, safemode, false)
end

def self.call(host, provisioning_template, safemode, success)
return unless host

klass = host.persisted? ? ExistingHostService : NewHostService
klass.new(host, provisioning_template, safemode, success).call
end

class BaseService
def initialize(host, provisioning_template, safemode, success)
@host = host
@provisioning_template = provisioning_template
@safemode = safemode
@success = success
end

private

attr_reader :host, :provisioning_template, :safemode, :success
delegate :render_statuses, to: :host
delegate :id, to: :provisioning_template, prefix: true
end

class ExistingHostService < BaseService
def call
render_statuses.find_or_initialize_by(provisioning_template: provisioning_template, safemode: safemode)
.update(success: success)
end
end

class NewHostService < BaseService
def call
update || create
end

private

def update
render_statuses.find { |status| status.template_id == provisioning_template_id && status.safemode == safemode }
.tap { |status| status&.assign_attributes(success: success) }
end

def create
render_statuses.new(provisioning_template: provisioning_template, safemode: safemode, success: success)
end
end
end
end
end
2 changes: 2 additions & 0 deletions app/services/foreman/renderer/safe_mode_renderer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Foreman
module Renderer
class SafeModeRenderer < BaseRenderer
SAFEMODE = true

def render
box = Safemode::Box.new(scope, allowed_helpers, source_name)
erb = ERB.new(source_content, trim_mode: '-')
Expand Down
2 changes: 2 additions & 0 deletions app/services/foreman/renderer/unsafe_mode_renderer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Foreman
module Renderer
class UnsafeModeRenderer < BaseRenderer
SAFEMODE = false

def render
erb = ERB.new(source_content, trim_mode: '-')
erb.location = source_name, 0
Expand Down
3 changes: 3 additions & 0 deletions app/views/api/v2/render_statuses/base.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object @render_status

attributes :id
3 changes: 3 additions & 0 deletions app/views/api/v2/render_statuses/index.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
collection @render_statuses

extends "api/v2/render_statuses/main"
29 changes: 29 additions & 0 deletions app/views/api/v2/render_statuses/main.json.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
object @render_status

extends "api/v2/render_statuses/base"

attributes :safemode, :success, :created_at, :updated_at

child :host do
attributes :id, :name
node :path do |host|
path = host_details_page_path(host)
User.current.allowed_to?(path) ? path : nil
end
end

child :hostgroup do
attributes :id, :name
node :path do |hostgroup|
path = edit_hostgroup_path(hostgroup)
User.current.allowed_to?(path) ? path : nil
end
end

child :provisioning_template do
attributes :id, :name
node :path do |provisioning_template|
path = edit_provisioning_template_path(provisioning_template)
User.current.allowed_to?(path) ? path : nil
end
end
3 changes: 3 additions & 0 deletions app/views/provisioning_templates/_custom_tab_headers.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
<li><a href="#template_type" data-toggle="tab"><%= _("Type") %></a></li>
<li><a href="#template_associations" data-toggle="tab"><%= _("Association") %></a></li>
<% if @template.render_statuses_enabled? %>
<li><a href="#render_statuses" data-toggle="tab"><%= _("Render Statuses") %></a></li>
<% end %>
6 changes: 6 additions & 0 deletions app/views/provisioning_templates/_custom_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@
<%= render "provisioning_templates/combinations", :f => f %>
</div>
</div>

<% if @template.render_statuses_enabled? %>
<div class="tab-pane" id="render_statuses">
<%= render "provisioning_templates/render_statuses" %>
</div>
<% end %>
1 change: 1 addition & 0 deletions app/views/provisioning_templates/_render_statuses.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= react_component('RenderStatuses', { path: api_provisioning_template_render_statuses_path(@template) }.to_json) %>
3 changes: 3 additions & 0 deletions config/routes/api/v2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
resources :template_combinations, :only => [:index, :create, :update, :show]
resources :operatingsystems, :except => [:new, :edit]
resources :os_default_templates, :except => [:new, :edit]
resources :render_statuses, :only => :index
end

resources :dashboard, :only => [:index]
Expand All @@ -74,6 +75,7 @@
end
resources :hosts, :except => [:new, :edit]
resources :template_combinations, :only => [:show, :index, :create, :update]
resources :render_statuses, :only => :index
end

resources :media, :except => [:new, :edit] do
Expand Down Expand Up @@ -316,6 +318,7 @@
post :facts, :on => :collection
resources :audits, :only => :index
resources :facts, :only => :index, :controller => :fact_values
resources :render_statuses, :only => :index
resources :interfaces, :except => [:new, :edit]
resources :parameters, :except => [:new, :edit] do
collection do
Expand Down
33 changes: 33 additions & 0 deletions db/migrate/20211116220221_create_render_statuses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class CreateRenderStatuses < ActiveRecord::Migration[6.0]
def change
create_table :render_statuses do |t|
t.references :host, type: :integer, foreign_key: true, index: true
t.references :hostgroup, type: :integer, foreign_key: true, index: true
t.references :template, type: :integer, foreign_key: true, index: true, null: false
t.boolean :safemode, null: false
t.boolean :success, null: false
t.timestamps
end

add_index :render_statuses, [:host_id, :template_id, :safemode], unique: true, name: 'index_render_statuses_on_host_and_template_and_safemode'
add_index :render_statuses, [:hostgroup_id, :template_id, :safemode], unique: true, name: 'index_render_statuses_on_hostgroup_and_template_and_safemode'

reversible do |dir|
dir.up do
execute <<-SQL
ALTER TABLE render_statuses ADD CONSTRAINT host_or_hostgroup_check CHECK (
(
(host_id is not null)::integer +
(hostgroup_id is not null)::integer
) = 1
);
SQL
end
dir.down do
execute <<-SQL
ALTER TABLE render_statuses DROP CONSTRAINT host_or_hostgroup_check;
SQL
end
end
end
end
1 change: 1 addition & 0 deletions db/seeds.d/020-permissions_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def permissions
['User', 'create_users'],
['User', 'edit_users'],
['User', 'destroy_users'],
['RenderStatus', 'view_render_statuses'],
[nil, 'view_statuses'],
]
end
Expand Down
Loading

0 comments on commit b1f012b

Please sign in to comment.