Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #37994 - Populate bootc fields from facts and associate hosts with manifest entities #11209

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/models/katello/concerns/host_managed_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ def remote_execution_proxies(provider, *_rest)
has_many :content_views, through: :content_view_environments
has_many :lifecycle_environments, through: :content_view_environments

has_one :docker_manifest, through: :content_facet, source: :manifest_entity, source_type: 'Katello::DockerManifest'
has_one :docker_manifest_list, through: :content_facet, source: :manifest_entity, source_type: 'Katello::DockerManifestList'

has_many :host_installed_packages, :class_name => "::Katello::HostInstalledPackage", :foreign_key => :host_id, :dependent => :delete_all
has_many :installed_packages, :class_name => "::Katello::InstalledPackage", :through => :host_installed_packages

Expand Down
5 changes: 5 additions & 0 deletions app/models/katello/docker_manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ class DockerManifest < Katello::Model
has_many :docker_manifest_list_manifests, :class_name => "Katello::DockerManifestListManifest",
:dependent => :delete_all, :inverse_of => :docker_manifest
has_many :docker_manifest_lists, :through => :docker_manifest_list_manifests, :inverse_of => :docker_manifests
has_many :content_facets, :class_name => "::Katello::Host::ContentFacet", :as => :manifest_entity, :dependent => :nullify
has_many :hosts, :class_name => "::Host::Managed", :through => :content_facets, :inverse_of => :docker_manifest

CONTENT_TYPE = "docker_manifest".freeze

scope :bootable, -> { where(:is_bootable => true) }

scoped_search :relation => :docker_tags, :on => :name, :rename => :tag, :complete_value => true
scoped_search :on => :digest, :rename => :digest, :complete_value => true, :only_explicit => true
scoped_search :on => :schema_version, :rename => :schema_version, :complete_value => true, :only_explicit => true
scoped_search :relation => :docker_manifest_lists, :on => :digest, :rename => :manifest_list_digest, :complete_value => true, :only_explicit => true
scoped_search :on => :is_bootable, :rename => :bootable, :complete_value => { true => true, false => false }, :only_explicit => true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the complete values need to be stringified.

[4] pry(main)> ::Katello::DockerManifest.search_for('bootable = true')
ScopedSearch::QueryNotSupported: 'is_bootable' should be one of 'true, false', but the query was 'true'
from /home/vagrant/foreman/.vendor/ruby/3.0.0/gems/scoped_search-4.1.12/lib/scoped_search/query_builder.rb:179:in `translate_value'


def self.default_sort
order(:schema_version)
Expand Down
5 changes: 5 additions & 0 deletions app/models/katello/docker_manifest_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ class DockerManifestList < Katello::Model
has_many :docker_manifest_list_manifests, :class_name => "Katello::DockerManifestListManifest",
:dependent => :delete_all, :inverse_of => :docker_manifest_list
has_many :docker_manifests, :through => :docker_manifest_list_manifests, :inverse_of => :docker_manifest_lists
has_many :content_facets, :class_name => "::Katello::Host::ContentFacet", :as => :manifest_entity, :dependent => :nullify
has_many :hosts, :class_name => "::Host::Managed", :through => :content_facets, :inverse_of => :docker_manifest_list

CONTENT_TYPE = "docker_manifest_list".freeze

scope :bootable, -> { where(:is_bootable => true) }

scoped_search :relation => :docker_tags, :on => :name, :rename => :tag, :complete_value => true
scoped_search :on => :digest, :rename => :digest, :complete_value => true, :only_explicit => true
scoped_search :on => :schema_version, :rename => :schema_version, :complete_value => true, :only_explicit => true
scoped_search :on => :is_bootable, :rename => :bootable, :complete_value => { true => true, false => false }, :only_explicit => true

def self.default_sort
order(:schema_version)
Expand Down
39 changes: 39 additions & 0 deletions app/models/katello/host/content_facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@ class ContentFacet < Katello::Model
ALL_TRACER_PACKAGE_NAMES = [ "python-#{HOST_TOOLS_TRACER_PACKAGE_NAME}",
"python3-#{HOST_TOOLS_TRACER_PACKAGE_NAME}",
HOST_TOOLS_TRACER_PACKAGE_NAME ].freeze
BOOTC_FIELD_FACT_NAMES = [
"bootc.booted.image",
"bootc.booted.digest",
"bootc.staged.image",
"bootc.staged.digest",
"bootc.rollback.image",
"bootc.rollback.digest",
"bootc.available.image",
"bootc.available.digest",
].freeze

belongs_to :kickstart_repository, :class_name => "::Katello::Repository", :inverse_of => :kickstart_content_facets
belongs_to :content_source, :class_name => "::SmartProxy", :inverse_of => :content_facets
belongs_to :manifest_entity, :polymorphic => true, :optional => true, :inverse_of => :content_facets

has_many :content_view_environment_content_facets, :class_name => "Katello::ContentViewEnvironmentContentFacet", :dependent => :destroy, :inverse_of => :content_facet
has_many :content_view_environments, :through => :content_view_environment_content_facets,
Expand Down Expand Up @@ -308,6 +319,34 @@ def self.with_non_installable_errata(errata, hosts = nil)
Katello::Host::ContentFacet.where(id: non_installable_errata)
end

def self.populate_fields_from_facts(host, parser, _type, _source_proxy)
return if host.content_facet.blank?
facet = host.content_facet || host.build_content_facet
attrs_to_add = {}
BOOTC_FIELD_FACT_NAMES.each do |fact_name|
fact_value = parser.facts[fact_name]
field_name = fact_name.tr(".", "_")
attrs_to_add[field_name] = fact_value # overwrite with nil if fact is not present
end
if attrs_to_add['bootc_booted_digest'].present?
manifest_entity = find_manifest_entity(digest: attrs_to_add['bootc_booted_digest'])
if manifest_entity.present?
attrs_to_add['manifest_entity_type'] = manifest_entity.model_name.name
attrs_to_add['manifest_entity_id'] = manifest_entity.id
else
# remove the association if the manifest entity is not found
attrs_to_add['manifest_entity_type'] = nil
attrs_to_add['manifest_entity_id'] = nil
end
end
facet.assign_attributes(attrs_to_add)
facet.save unless facet.new_record?
end

def self.find_manifest_entity(digest:)
::Katello::DockerManifestList.find_by(digest: digest) || ::Katello::DockerManifest.find_by(digest: digest)
end

def self.with_applicable_errata(errata)
self.joins(:applicable_errata).where("#{Katello::Erratum.table_name}.id" => errata)
end
Expand Down
3 changes: 2 additions & 1 deletion app/models/katello/host/subscription_facet.rb
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,11 @@ def self.populate_fields_from_facts(host, parser, _type, _source_proxy)
return unless host.subscription_facet || has_convert2rhel
# Add in custom convert2rhel fact if system was converted using convert2rhel through Katello
# We want the value nil unless the custom fact is present otherwise we get a 0 in the database which if debugging
# might make you think it was converted2rhel but not with satellite, that is why I have the tenary below.
# might make you think it was converted2rhel but not with satellite, that is why I have the ternary below.
facet = host.subscription_facet || host.build_subscription_facet
facet.attributes = {
convert2rhel_through_foreman: has_convert2rhel ? ::Foreman::Cast.to_bool(parser.facts['conversions.env.CONVERT2RHEL_THROUGH_FOREMAN']) : nil,

}.compact
facet.save unless facet.new_record?
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddManifestEntityToContentFacets < ActiveRecord::Migration[6.1]
def change
add_reference :katello_content_facets, :manifest_entity, polymorphic: true, index: true
change_column_null :katello_content_facets, :manifest_entity_type, true
change_column_null :katello_content_facets, :manifest_entity_id, true
end
end
53 changes: 52 additions & 1 deletion webpack/ForemanColumnExtensions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,64 @@ import {
PackageIcon,
} from '@patternfly/react-icons';
import { Link } from 'react-router-dom';
import { Flex, FlexItem, Popover, Badge } from '@patternfly/react-core';
import {
Flex,
FlexItem,
Popover,
Badge,
DescriptionList,
DescriptionListGroup,
DescriptionListDescription as Dd,
DescriptionListTerm as Dt,
} from '@patternfly/react-core';
import { translate as __ } from 'foremanReact/common/I18n';
import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
import { ContentViewEnvironmentDisplay } from '../components/extensions/HostDetails/Cards/ContentViewDetailsCard/ContentViewDetailsCard';
import { truncate } from '../utils/helpers';
import RepoIcon from '../scenes/ContentViews/Details/Repositories/RepoIcon';

const hostsIndexColumnExtensions = [
{
columnName: 'bootc_booted_image',
title: <RepoIcon type="docker" customTooltip={__('Image mode / package mode')} />,
wrapper: (hostDetails) => {
const imageMode = hostDetails?.content_facet_attributes?.bootc_booted_image;
const digest = hostDetails?.content_facet_attributes?.bootc_booted_digest;
return (
<Flex>
{imageMode ?
<Popover
id="image-mode-tooltip"
className="image-mode-tooltip"
maxWidth="74rem"
headerContent={hostDetails.display_name}
bodyContent={
<Flex direction={{ default: 'column' }}>
<DescriptionList isHorizontal>
<DescriptionListGroup>
<Dt>{__('Booted image')}</Dt>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to be consistent with Insights which calls it Running image?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, I can do that.

Should I also add the other info we collect? I can also show available, staged, and rollback so it's just like insights.

cc @ianballou

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also add any of those as options for additional columns you can add. Maybe just "Running image" would make sense. Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these image information will be displayed in the image information card on details tab for each bootc hosts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out what Insights has for the pop up:
image

Instead of showing any data, they have a blurb about what a package mode machine is vs an image mode machine.

I prefer seeing the image information over a blurb about what image mode means. We might want to catch up with Maria on it.

Copy link
Member

@ianballou ianballou Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't realize that the hover over text was different from the window that pops up when you click on the icon. I suppose the blurb could go in the hover over part.

<Dd>{hostDetails.content_facet_attributes.bootc_booted_image}</Dd>
</DescriptionListGroup>
<DescriptionListGroup>
<Dt>{__('Digest')}</Dt>
<Dd>{digest}</Dd>
</DescriptionListGroup>
</DescriptionList>
</Flex>
}
>
<FlexItem>
<RepoIcon type="docker" customTooltip={__('Image mode')} />
</FlexItem>
</Popover>
: <span style={{ color: 'gray' }}><RepoIcon type="yum" customTooltip={__('Package mode')} /></span>
}
</Flex>
);
},
weight: 35, // between power status (0) and name (50)
isSorted: true,
},
{
columnName: 'rhel_lifecycle_status',
title: __('RHEL Lifecycle status'),
Expand Down
6 changes: 4 additions & 2 deletions webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Tooltip } from '@patternfly/react-core';
import { BundleIcon, MiddlewareIcon, BoxIcon, CodeBranchIcon, FanIcon, TenantIcon, AnsibleTowerIcon } from '@patternfly/react-icons';
import PropTypes from 'prop-types';

const RepoIcon = ({ type }) => {
const RepoIcon = ({ type, customTooltip }) => {
const iconMap = {
yum: BundleIcon,
docker: MiddlewareIcon,
Expand All @@ -14,15 +14,17 @@ const RepoIcon = ({ type }) => {
};
const Icon = iconMap[type] || BoxIcon;

return <Tooltip content={<div>{type}</div>}><Icon aria-label={`${type}_type_icon`} /></Tooltip>;
return <Tooltip content={<div>{customTooltip ?? type}</div>}><Icon aria-label={`${type}_type_icon`} /></Tooltip>;
};

RepoIcon.propTypes = {
type: PropTypes.string,
customTooltip: PropTypes.string,
};

RepoIcon.defaultProps = {
type: '', // prevent errors if data isn't loaded yet
customTooltip: null,
};

export default RepoIcon;
Loading