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

MCO-497: Prep node for RHCOS 9 upgrade #3553

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 13 additions & 1 deletion pkg/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ import (
mcfglistersv1 "github.com/openshift/machine-config-operator/pkg/generated/listers/machineconfiguration.openshift.io/v1"
)

// Make this an interface so that we can easily inject mock versions of this.
type osRelease interface {
IsEL() bool
IsEL9() bool
IsCoreOSVariant() bool
IsFCOS() bool
IsSCOS() bool
IsLikeTraditionalRHEL7() bool
}

var _ osRelease = osrelease.OperatingSystem{}

// Daemon is the dispatch point for the functions of the agent on the
// machine. it keeps track of connections and the current state of the update
// process.
Expand All @@ -50,7 +62,7 @@ type Daemon struct {
name string

// os the operating system the MCD is running on
os osrelease.OperatingSystem
os osRelease

// mock is set if we're running as non-root, probably under unit tests
mock bool
Expand Down
155 changes: 142 additions & 13 deletions pkg/daemon/osrelease/osrelease.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package osrelease

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/ashcrow/osrelease"
)

// Source of the OS release information
type InfoSource string

const (
// From the /etc/os-release / /usr/lib/os-release files.
OSReleaseInfoSource InfoSource = "OS Release"
// From the OS image labels.
ImageLabelInfoSource InfoSource = "OS Image Label"
)

// OS Release Paths
const (
EtcOSReleasePath string = "/etc/os-release"
Expand All @@ -25,17 +36,57 @@ const (
// OperatingSystem is a wrapper around a subset of the os-release fields
// and also tracks whether ostree is in use.
type OperatingSystem struct {
// id is the ID field from the os-release
// id is the ID field from the os-release or inferred from the OS image
// label.
id string
// variantID is the VARIANT_ID field from the os-release
// variantID is the VARIANT_ID field from the os-release or inferred from the
// OS image label.
variantID string
// version is the VERSION, RHEL_VERSION, or VERSION_ID field from the os-release
// version is the VERSION, RHEL_VERSION, or VERSION_ID field from the
// os-release or image label.
version string
// osrelease is the underlying struct from github.com/ashcrow/osrelease
osrelease osrelease.OSRelease
// values is a map of all the values we uncovered either via the
// /etc/os-release / /usr/lib/os-release files *or* the labels attached
// to an OS image.
values map[string]string
// source identifies whether this came from the OSRelease file or from image labels.
source InfoSource
}

func newOperatingSystemFromImageLabels(imageLabels map[string]string) (OperatingSystem, error) {
if err := hasRequiredLabels(imageLabels); err != nil {
return OperatingSystem{}, err
}

os := OperatingSystem{
values: imageLabels,
source: ImageLabelInfoSource,
// If we've made it this far, we know we have a CoreOS variant.
variantID: coreos,
version: imageLabels["version"],
}

// Only FCOS and SCOS set this label, which is why it's not required.
if osName, osNameOK := imageLabels["io.openshift.build.version-display-names"]; osNameOK {
Copy link
Member Author

Choose a reason for hiding this comment

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

It seems odd that only FCOS and SCOS set this field and that RHCOS simply doesn't. It is possible that I examined the wrong container labels when I was setting up the tests for this code.

Copy link
Member

@cgwalters cgwalters Feb 14, 2023

Choose a reason for hiding this comment

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

Yeah 😢 This is because of the machine-os-content zombie issue...some links for this in openshift/os#1048

Basically oc adm release new barfs and dies if there are two container images in the release which claim to be an operating system. OKD already set things up so that machine-os-content is an alias, so they don't have this problem.

return inferNonRHCOS(os, osName)
}

// Like SCOS, RHCOS has the version number in the middle position of the OCP
// / OKD version ID (e.g., 413.92.202302081904-0, becomes 92; which is 9.2
// though we don't care about the missing decimal here)
os.version = strings.Split(os.version, ".")[1]

// If we've made it this far and the first character is either 8 or 9, we
// most likely have an RHCOS image.
if os.version[0:1] == "8" || os.version[0:1] == "9" {
os.id = rhcos
return os, nil
}

return os, fmt.Errorf("unable to infer OS version from image labels: %v", imageLabels)
}

func newOperatingSystem(etcPath, libPath string) (OperatingSystem, error) {
func newOperatingSystemFromOSRelease(etcPath, libPath string) (OperatingSystem, error) {
ret := OperatingSystem{}

or, err := osrelease.NewWithOverrides(etcPath, libPath)
Expand All @@ -46,14 +97,37 @@ func newOperatingSystem(etcPath, libPath string) (OperatingSystem, error) {
ret.id = or.ID
ret.variantID = or.VARIANT_ID
ret.version = getOSVersion(or)
ret.osrelease = or

// Store all of the values identified by the osrelease library.
ret.values = or.ADDITIONAL_FIELDS
ret.values["NAME"] = or.NAME
ret.values["VERSION"] = or.VERSION
ret.values["ID"] = or.ID
ret.values["ID_LIKE"] = or.ID_LIKE
ret.values["VERSION_ID"] = or.VERSION_ID
ret.values["VERSION_CODENAME"] = or.VERSION_CODENAME
ret.values["PRETTY_NAME"] = or.PRETTY_NAME
ret.values["ANSI_COLOR"] = or.ANSI_COLOR
ret.values["CPE_NAME"] = or.CPE_NAME
ret.values["HOME_URL"] = or.HOME_URL
ret.values["BUG_REPORT_URL"] = or.BUG_REPORT_URL
ret.values["PRIVACY_POLICY_URL"] = or.PRIVACY_POLICY_URL
ret.values["VARIANT"] = or.VARIANT
ret.values["VARIANT_ID"] = or.VARIANT_ID

ret.source = OSReleaseInfoSource

return ret, nil
}

// Returns the underlying OSRelease struct if additional parameters are needed.
func (os OperatingSystem) OSRelease() osrelease.OSRelease {
return os.osrelease
// Returns the source of where this info came from.
func (os OperatingSystem) Source() InfoSource {
return os.source
}

// Returns the values map if cdditional ontext is needed.
func (os OperatingSystem) Values() map[string]string {
return os.values
}

// IsEL is true if the OS is an Enterprise Linux variant,
Expand All @@ -64,7 +138,7 @@ func (os OperatingSystem) IsEL() bool {

// IsEL9 is true if the OS is RHCOS 9 or SCOS 9
func (os OperatingSystem) IsEL9() bool {
return os.IsEL() && strings.HasPrefix(os.version, "9.") || os.version == "9"
return os.IsEL() && strings.HasPrefix(os.version, "9") || os.version == "9"
}

// IsFCOS is true if the OS is Fedora CoreOS
Expand Down Expand Up @@ -102,7 +176,7 @@ func (os OperatingSystem) ToPrometheusLabel() string {

// GetHostRunningOS reads os-release to generate the OperatingSystem data.
func GetHostRunningOS() (OperatingSystem, error) {
return newOperatingSystem(EtcOSReleasePath, LibOSReleasePath)
return newOperatingSystemFromOSRelease(EtcOSReleasePath, LibOSReleasePath)
}

// Generates the OperatingSystem data from strings which contain the desired
Expand All @@ -126,7 +200,62 @@ func LoadOSRelease(etcOSReleaseContent, libOSReleaseContent string) (OperatingSy
return OperatingSystem{}, err
}

return newOperatingSystem(etcOSReleasePath, libOSReleasePath)
return newOperatingSystemFromOSRelease(etcOSReleasePath, libOSReleasePath)
}

// Infers the OS release version given the image labels from a given OS image.
func InferFromOSImageLabels(imageLabels map[string]string) (OperatingSystem, error) {
return newOperatingSystemFromImageLabels(imageLabels)
}

// Determines if an OS image has the labels that are required to infer what OS
// it contains.
func hasRequiredLabels(imageLabels map[string]string) error {
requiredLabels := []string{
"coreos-assembler.image-input-checksum",
"coreos-assembler.image-config-checksum",
"org.opencontainers.image.revision",
"org.opencontainers.image.source",
"version",
}

for _, reqLabel := range requiredLabels {
if _, ok := imageLabels[reqLabel]; !ok {
return fmt.Errorf("labels %v missing required key %q", imageLabels, reqLabel)
}
}

return nil
}

// Infers that a given oeprating system is either FCOS or SCOS.
func inferNonRHCOS(os OperatingSystem, osName string) (OperatingSystem, error) {
osName = strings.ReplaceAll(osName, "machine-os=", "")

switch osName {
case "CentOS Stream CoreOS":
os.id = scos
// Grab the middle value from the version number (e.g.,
// 413.9.202302130811-0 becomes 9)
os.version = strings.Split(os.version, ".")[1]
case "Fedora CoreOS":
// FCOS doesn't have the OCP / OKD version number encoded in it (e.g.,
// 37.20230211.20.0) so we don't need to mutate it or inspect it.
os.id = fedora
default:
// Catch-all if we have an unknown OS name.
return os, fmt.Errorf("unknown OS %q", osName)
}

// Currently, SCOS is at major version 9. This will probably change in the
// distant future. Additionally, this provides a guard in the event that
// the version number schema changes.
if os.id == scos && os.version != "9" {
return os, fmt.Errorf("unknown SCOS version %q", os.version)
}

// We've been able to infer the necessary fields for FCOS and SCOS.
return os, nil
}

// Determines the OS version based upon the contents of the RHEL_VERSION, VERSION or VERSION_ID fields.
Expand Down