- Make downstream release automated.
- Have the same code base for all upstream/downstream projects.
- Create one place to insert all the changes from upstream to downstream (1 project changes vs all projects).
- Usage of downstream translations for upstream use.
The most common task is to brand string messages shown to the user:
We want Welcome to foreman
to become Welcome to Satellite
.
This is done for both .erb
and .js
files by enabling translation:
Ruby code - erb and helpers:
<%= _('Welcome to Foreman') %>
# => "Welcome to Satellite"
React:
const SettingName = ({ setting }) => (
<React.Fragment>
{__('Welcome to Foreman')}
</React.Fragment>
);
// =>
// <React.Fragment>
// Welcome to Satellite
// </React.Fragment>
In case there is a need to add more terms to be branded or bypass branding,
it can be done in
branded_words.rb
.
This is a dictionary where the key is a
Regular Expression and the value is a replacement
string.
FOREMAN_BRAND = {
# string to replace:
/\bForeman\b(?!-)/ => 'Satellite',
/\bproxy\b(?!-)/ => 'Capsule',
# string to bypass
/\bHTTP\(S\) proxy\b(?!-)/ => 'HTTP(S) proxy',
}
branded_string = _('Use proxy for this')
# => "Use Capsule for this"
bypassed_string = _('HTTP(S) proxy will not be branded')
# => "HTTP(S) proxy will not be branded"
In order to brand translated strings too, the system assumes that the names will appear in the translated string exactly as they appear in the original string.
For example in Russian we will use
"Добро пожаловать в Foreman"
that will be branded automatically to
"Добро пожаловать в Satellite"
Many times we need to use a downstream link instead of an upstream one. The most common example for this is pointing to a documentation link.
All links to documentation in Foreman are local and redirected to
links_controller
. This controller is prepended with
documentation_controller_branding
concern that uses dictionary stored in
lib/foreman_theme_satellite/documentation.rb
to redirect documentation to
downstream URLs.
In foreman:
<p><%= link_to _('Learn more about LDAP authentication in the documentation.'), documentation_url("4.1.1LDAPAuthentication")%></p>
will redirect to https://theforeman.org/manuals/2.2/index.html#4.1.1LDAPAuthentication
to change that, we need to add a downstream documentation link to documentation.rb
:
USER_GUIDE_DICTIONARY = {
# ...
'LDAPAuthentication' => "#{ForemanThemeSatellite.documentation_root}/administering_red_hat_satellite/chap-red_hat_satellite-administering_red_hat_satellite-configuring_external_authentication",
# ...
}
notice the use of
ForemanThemeSatellite.documentation_root
this constant is maintained by the theme and always point to the correct documentation version.
Most of the time a setting's default value should be changed to a downstream
version. This is done by adding more entries to setting changing code in
settings_branding.rb
@branded_settings ||= {
'my_new_setting' => 'satellite value'
'email_reply_address' => "satellite-noreply@#{domain}",
'email_subject_prefix' => '[satellite]',
'rss_url' => 'https://www.redhat.com/en/rss/blog/channel/red-hat-satellite',
'foreman_tasks_troubleshooting_url' => 'https://access.redhat.com/solutions/satellite6-tasks#%{label}'
}.freeze
When a feature is deprecated upstream, a deprecation notification might need branding.
An example to this is in
deprecation_notification.rb
Standard ruby override technique is used to override notification behavior
module DeprecationNotification
module StringParser
def initialize(template, options = {})
options[:version] = '6.8' if options[:version] == '2.0'
super(template, options)
end
end
module Notification
def create!(opts)
if opts[:notification_blueprint] == NotificationBlueprint.find_by_name('feature_deprecation') &&
opts.dig(:actions, :links, 0, :href) == 'https://community.theforeman.org/t/dropping-smart-variables/16176'
opts[:actions][:links][0][:href] = "#{ForemanThemeSatellite.documentation_root}/release_notes/index"
end
super(opts)
end
end
end
and later on in engine.rb
UINotifications::StringParser.send :prepend, DeprecationNotification::StringParser
Notification.singleton_class.send :prepend, DeprecationNotification::Notification
There are two common tasks that need to be done:
- layout changes
- color scheme adjustments
Layout constants may need adjusting since the logos e.t.c. look differently in
themed pages. This is done by adding a new SCSS file under
satellite
folder in app/assets/stylesheets
.
Make sure the CSS rules are the most specific, so that the CSS engine would use theme version instead of the default foreman ones.
.wizard li {
padding: 8px 12px 8px;
&:first-child {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
&:last-child {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
}
When a color scheme need adjustment, most of the time it should be done across
all the values. It will require generating the final CSS file (not the scss
sources) and running a transformation that will result in a set of color
changing rules that should be placed under the
satellite
folder.
- For files in
app/assets/...
runrails assets:precompile
. The source path will bepublic/assets/application-ETag.css
- For files in
webpack/...
runrake webpack:compile
. The source path will bepublic/webpack/bundle-ETag.css
- Run the following command in the foreman directory:
rails generate foreman_theme_satellite:color_diff --source_css <PATH_TO_SOURCE_FILE> --destination_file <PATH_TO_DESTINATION_FILE>
List of color conversion rules can be found in:
color_changer.rb
in case a new rule should be added.
COLOR_REPLACEMENT = {
'#000000' => '#FFFFFF', # $primary_color
}
For react components there is a need to create an extension point in the base
code by using a proper Slot
.
Later we will be able to register a Fill
component in our global
index.js
file.
Example slot foreman code:
<div className="host-details-tab-item">
<Slot
response={response}
id="host-details-page-tabs"
fillID={tab}
/>
</div>
Later we can add a proper fill in the theme:
import React from 'react';
import { addGlobalFill } from '../../common/Fill/GlobalFill';
import SatelliteDetailsTab from './Details';
export const registerCoreTabs = () => {
addGlobalFill('host-details-page-tabs', 'Details', <SatelliteDetailsTab />, 1000);
};
Sometimes a component is added from an .erb
file. Those components often pass
server data as props to the component.
It is advised to design such extension points with helper functions, to make the process of replacing props easier.
For example in foreman
we use
def login_props
{
token: form_authenticity_token,
version: SETTINGS[:version].version,
caption: Setting[:login_text],
alerts: flash_inline,
logoSrc: image_path("foreman_theme_satellite/login_logo.png"),
}
end
def mount_login
react_component('LoginPage', login_props.to_json)
end
to mount react login component. Once the extension point is in place, we can override it from theme helper:
module ThemeLoginHelper
def login_props
super.merge(
version: ForemanThemeSatellite::SATELLITE_VERSION,
logoSrc: image_path('foreman_theme_satellite/login_logo.svg')
)
end
end
Use deface in order to change views,
all the deface changes are located under
app/overrides
.
For example, changing about page content
Deface::Override.new(:virtual_path => "about/index",
:name => "change about page content",
# div#support
:replace => "div.col-md-5 div.stats-well:nth-child(1)",
:text => " <div class=\"stats-well\"><h4><%= _(\"Support\") %></h4> <p>Visit the <%= link_to _('Customer Portal'), \"https://access.redhat.com/\",
:rel => \"external\" %> to get support, find solutions to common questions, and more.</p><h6><%= _(\"Documentation\") %></h6>
<ul>
<li><%= link_to _('Complete Product Documentation for Red Hat Satellite'),\"https://access.redhat.com/documentation/en/red_hat_satellite/#{ForemanThemeSatellite::SATELLITE_SHORT_VERSION}\", :rel => \"external\" %></li>
<li><%= link_to _('API Resources'), apipie_apipie_path, :title => _('Automate Satellite via a simple and powerful API') %></li>
</ul>
<h6><%= _(\"Blog\") %></h6>
<ul>
<li><%= link_to _('Red Hat Satellite Blog'), \"https://access.redhat.com/blogs/1169563\", :rel => \"external\" %></li>
</ul>
<h6><%= _(\"IRC\") %></h6>
<p><%= (_(\"You can find us on %{freenode} (irc.freenode.net) on #satellite6.\") % {:freenode => link_to(\"freenode\", \"http://www.freenode.net\", :rel => \"external\")}).html_safe %></p>
</div>")
When there is need to change HTML components rendered by a helper, they could be overridden from theme's helpers:
module ThemeApplicationHelper
def association_text()
content_tag(:p, _('When editing a Template, you must assign a list of Operating Systems with which this Template can be used. Optionally, you can restrict a template to a list of Hostgroups or Environments.')) +
content_tag(:p, _('When a Host requests a template (e.g. during provisioning), Foreman selects the optimal match from the available templates of that type, in the following order:')) +
(content_tag :ul do
content_tag(:li, _('Host group and Environment'))
content_tag(:li, _('Host group only'))
content_tag(:li, _('Environment only'))
content_tag(:li, _('Operating system default'))
end)
(_('The final entry, Operating System default, can be set by editing the %s page.') % (link_to _('Operating System'), operatingsystems_path)).html_safe
end
end
When there is a need to have a list of features/components e.t.c. it's recommended to add a method or constant in upstream foreman and override it in the theme by using concerns or redefining consts.
class ComputeResource < ApplicationRecord
# ...
def self.supported_providers
{
'Libvirt' => 'Foreman::Model::Libvirt',
'Ovirt' => 'Foreman::Model::Ovirt',
'EC2' => 'Foreman::Model::EC2',
'Vmware' => 'Foreman::Model::Vmware',
'Openstack' => 'Foreman::Model::Openstack',
}
end
# ...
module ComputeResourceBranding
module ClassMethods
def supported_providers
super.except('Rackspace')
end
def providers_requiring_url
_('Libvirt, oVirt and OpenStack')
end
end
end
This approach is also applicable to other plugins too, here is an example from Katello.
module Katello
class Ping
PACKAGES = %w(katello candlepin pulp qpid foreman tfm hammer).freeze
end
end
module SatellitePackages
extend ActiveSupport::Concern
included do
old_packages = remove_const(:PACKAGES)
const_set(:PACKAGES, (old_packages + ['satellite']).freeze)
end
end
When there is a new template (supported by downstream) added in the upstream repo, please don't forget to update this constant. You simply need to add the name of the new template to the array.
Currently the theme maintains a list of constants and metadata that is relevant
only to the downstream version. These constants can be used throughout the theme
and can be overridden from either ENV
variable or by setting the constant in
a specialized yaml file.
Setting up ENV variables:
SATELLITE_VERSION='6.8.000'
SATELLITE_DOCUMENTATION_SERVER=http://access.redhat.com
SATELLITE_DOCUMENTATION_VERSION=6.6
Setting up the metadata file:
version: '6.8.000'
documentation_server: "http://access.redhat.com"
documentation_version: "6.6"
the metadata file should be called /usr/share/satellite/metadata.yml
for
production style deployments, or used from
source
directory for development and test environments.
The mechanism resides in theme's
engine.rb
Since the life cycle of our documentation differs from Satellite's, there is need to test the links against different documentation version or instance.
These parameters are cofigurable by either setting ENV
variables:
SATELLITE_DOCUMENTATION_SERVER=http://access.redhat.com
SATELLITE_DOCUMENTATION_VERSION=6.6
Another option is to add or change keys in /usr/share/satellite/metadata.yml
:
documentation_server: "http://access.redhat.com"
documentation_version: "6.6"
There is also an automatic test that tests all documentation links on each PR,
for the test to work
metadata.yml
should be updated accordingly.
Due to all the changes made by the plugin, it is possible that some of the core tests will fail, we solve that by skipping tests and replacing them with our own.
Skipping:
initializer 'foreman_theme_satellite.register_plugin', :after=> :finisher_hook do |app|
Foreman::Plugin.register :foreman_theme_satellite do
requires_foreman '>= 1.10'
tests_to_skip ({
"ComputeResourceTest" => ["friendly provider name"]
})
end
end
Replacing:
class ModelsTest < ActiveSupport::TestCase
test "check openstack friendly name" do
assert_equal Foreman::Model::Openstack::provider_friendly_name, "RHEL OpenStack Platform", "Friendly name override was unsuccessful"
end
end