Skip to content
Draft
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
14 changes: 6 additions & 8 deletions cmd/machine-config-operator/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,12 @@ func runBootstrapCmd(_ *cobra.Command, _ []string) {

imgs := ctrlcommon.Images{
RenderConfigImages: ctrlcommon.RenderConfigImages{
MachineConfigOperator: bootstrapOpts.mcoImage,
KeepalivedBootstrap: bootstrapOpts.keepalivedImage,
CorednsBootstrap: bootstrapOpts.corednsImage,
BaremetalRuntimeCfgBootstrap: bootstrapOpts.baremetalRuntimeCfgImage,
OauthProxy: bootstrapOpts.oauthProxyImage,
KubeRbacProxy: bootstrapOpts.kubeRbacProxyImage,
BaseOSContainerImage: bootstrapOpts.baseOSContainerImage,
BaseOSExtensionsContainerImage: bootstrapOpts.baseOSExtensionsContainerImage,
MachineConfigOperator: bootstrapOpts.mcoImage,
KeepalivedBootstrap: bootstrapOpts.keepalivedImage,
CorednsBootstrap: bootstrapOpts.corednsImage,
BaremetalRuntimeCfgBootstrap: bootstrapOpts.baremetalRuntimeCfgImage,
OauthProxy: bootstrapOpts.oauthProxyImage,
KubeRbacProxy: bootstrapOpts.kubeRbacProxyImage,
},
ControllerConfigImages: ctrlcommon.ControllerConfigImages{
InfraImage: bootstrapOpts.infraImage,
Expand Down
15 changes: 10 additions & 5 deletions install/0000_80_machine-config_05_osimageurl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ metadata:
include.release.openshift.io/single-node-developer: "true"
data:
releaseVersion: 0.0.1-snapshot
# This (will eventually) replace the below when https://github.com/openshift/enhancements/pull/1032
# progresses towards the default.
baseOSContainerImage: "placeholder.url.oc.will.replace.this.org/placeholdernamespace:rhel-coreos"
baseOSExtensionsContainerImage: "placeholder.url.oc.will.replace.this.org/placeholdernamespace:rhel-coreos-extensions"
# The OS payload used for 4.10 and below; more information in
# https://github.com/openshift/machine-config-operator/blob/master/docs/OSUpgrades.md
# (The original issue was https://github.com/openshift/machine-config-operator/issues/183 )
osImageURL: ""
streams.json: >
{
"default": "rhel-coreos",
"streams": {
"rhel-coreos": {
"baseOSContainerImage": "placeholder.url.oc.will.replace.this.org/placeholdernamespace:rhel-coreos",
"baseOSExtensionsContainerImage": "placeholder.url.oc.will.replace.this.org/placeholdernamespace:rhel-coreos-extensions"
}
}
}
7 changes: 4 additions & 3 deletions pkg/controller/build/buildrequest/buildrequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ func (br buildRequestImpl) renderContainerfile() (string, error) {
// lowercase fields. Additionally, since there are a few fields where we
// default to a value from a different location, it makes more sense for us
// to implement that logic in Go as opposed to the Go template language.
osImageStreamImages := br.opts.OSImageURLConfig.StreamsConfig.GetOSImageURLsForDefaultStream()
items := struct {
MachineOSBuild *mcfgv1.MachineOSBuild
MachineOSConfig *mcfgv1.MachineOSConfig
Expand All @@ -335,8 +336,8 @@ func (br buildRequestImpl) renderContainerfile() (string, error) {
MachineOSBuild: br.opts.MachineOSBuild,
MachineOSConfig: br.opts.MachineOSConfig,
UserContainerfile: br.userContainerfile,
BaseOSImage: br.opts.OSImageURLConfig.BaseOSContainerImage,
ExtensionsImage: br.opts.OSImageURLConfig.BaseOSExtensionsContainerImage,
BaseOSImage: osImageStreamImages.BaseOSContainerImage,
ExtensionsImage: osImageStreamImages.BaseOSExtensionsContainerImage,
ExtensionsPackages: extPkgs,
KernelType: kernelType,
KernelPackages: kernelPackages,
Expand Down Expand Up @@ -671,7 +672,7 @@ func (br buildRequestImpl) toBuildahPod() *corev1.Pod {
// us to avoid parsing log files.
Name: "create-digest-configmap",
Command: append(command, digestCMScript),
Image: br.opts.OSImageURLConfig.BaseOSContainerImage,
Image: br.opts.OSImageURLConfig.StreamsConfig.GetOSImageURLsForDefaultStream().BaseOSContainerImage,
Env: env,
ImagePullPolicy: corev1.PullAlways,
SecurityContext: securityContext,
Expand Down
145 changes: 107 additions & 38 deletions pkg/controller/common/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@ package common

import (
"context"
"fmt"

"encoding/json"
"fmt"
"maps"
"slices"
"strings"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

clientset "k8s.io/client-go/kubernetes"
)

const (
imagesConfigMapImagesField = "images.json"
osImageUrlConfigMapStreamsField = "streams.json"
osImageUrlConfigMapStreamsDefaultImagesTag = "rhel-coreos"
)

// Images contain data derived from what github.com/openshift/installer's
// bootkube.sh provides. If you want to add a new image, you need
// to "ratchet" the change as follows:
Expand All @@ -28,10 +36,6 @@ type Images struct {
// RenderConfigImages are image names used to render templates under ./manifests/
type RenderConfigImages struct {
MachineConfigOperator string `json:"machineConfigOperator"`
// The new format image
BaseOSContainerImage string `json:"baseOSContainerImage"`
// The matching extensions container for the new format image
BaseOSExtensionsContainerImage string `json:"baseOSExtensionsContainerImage"`
// These have to be named differently from the ones in ControllerConfigImages
// or we get errors about ambiguous selectors because both structs are
// combined in the Images struct.
Expand All @@ -51,56 +55,115 @@ type ControllerConfigImages struct {
BaremetalRuntimeCfg string `json:"baremetalRuntimeCfgImage"`
}

// Parses the JSON blob containing the images information into an Images struct.
func ParseImagesFromBytes(in []byte) (*Images, error) {
img := &Images{}
type OSImageURLStreamConfig struct {
BaseOSContainerImage string `json:"baseOSContainerImage"`
BaseOSExtensionsContainerImage string `json:"baseOSExtensionsContainerImage"`
}
type OSImageURLStreamsConfig struct {
Default string `json:"default"`
Streams map[string]OSImageURLStreamConfig `json:"streams"`
}

if err := json.Unmarshal(in, img); err != nil {
return nil, fmt.Errorf("could not parse images.json bytes: %w", err)
func (o *OSImageURLStreamsConfig) GetOSImageURLsForStream(stream string) OSImageURLStreamConfig {
tagetStream, exists := o.Streams[stream]
if !exists {
return o.GetOSImageURLsForDefaultStream()
}
return tagetStream
}

return img, nil
func (o *OSImageURLStreamsConfig) GetOSImageURLsForDefaultStream() OSImageURLStreamConfig {
return o.Streams[o.Default]
}

// Reads the contents of the provided ConfigMap into an Images struct.
func ParseImagesFromConfigMap(cm *corev1.ConfigMap) (*Images, error) {
if err := validateMCOConfigMap(cm, MachineConfigOperatorImagesConfigMapName, []string{"images.json"}, nil); err != nil {
return nil, err
func (o *OSImageURLStreamsConfig) OSImageURLStreamExists(stream string) bool {
_, exists := o.Streams[stream]
return exists
}

func NewOSImageURLStreamsConfigFromBytes(buf []byte) (*OSImageURLStreamsConfig, error) {
streamsConfig := &OSImageURLStreamsConfig{}
if err := json.Unmarshal(buf, &streamsConfig); err != nil {
return nil, fmt.Errorf("could not parse streams config bytes: %w", err)
}
if streamsConfig.Default == "" {
return nil, fmt.Errorf("invalid osimagerul ConfigMap. The default stream cannot be empty")
}

return ParseImagesFromBytes([]byte(cm.Data["images.json"]))
if _, ok := streamsConfig.Streams[streamsConfig.Default]; !ok {
return nil, fmt.Errorf(
"invalid osimagerul ConfigMap. The default stream %s is not part of the declared streams %s",
streamsConfig.Default,
strings.Join(slices.Collect(maps.Keys(streamsConfig.Streams)), ", "),
)
}
return streamsConfig, nil
}

func NewOSImageURLStreamsConfigDefault(osContainerImage, osContainerExtensionImage string) *OSImageURLStreamsConfig {
return &OSImageURLStreamsConfig{
Default: osImageUrlConfigMapStreamsDefaultImagesTag,
Streams: map[string]OSImageURLStreamConfig{
osImageUrlConfigMapStreamsDefaultImagesTag: {
BaseOSContainerImage: osContainerImage,
BaseOSExtensionsContainerImage: osContainerExtensionImage,
},
},
}
}

// Holds the contents of the machine-config-osimageurl ConfigMap.
type OSImageURLConfig struct {
BaseOSContainerImage string
BaseOSExtensionsContainerImage string
OSImageURL string
ReleaseVersion string
ReleaseVersion string
StreamsConfig OSImageURLStreamsConfig
}

func ParseImagesFromBytes(in []byte) (*Images, error) {
img := &Images{}
if err := json.Unmarshal(in, img); err != nil {
return nil, fmt.Errorf("could not parse images.json bytes: %w", err)
}
return img, nil
}

// Reads the contents of the provided ConfigMap into an OSImageURLConfig struct.
func ParseOSImageURLConfigMap(cm *corev1.ConfigMap) (*OSImageURLConfig, error) {
reqKeys := []string{"baseOSContainerImage", "baseOSExtensionsContainerImage", "osImageURL", "releaseVersion"}

if err := validateMCOConfigMap(cm, MachineConfigOSImageURLConfigMapName, reqKeys, nil); err != nil {
if err := validateMCOConfigMap(
cm,
MachineConfigOSImageURLConfigMapName,
[]string{"baseOSContainerImage", "baseOSExtensionsContainerImage", "releaseVersion"},
); err != nil {
return nil, err
}

// For now handle the streams like they are optional and populate them with a sane default
// in case they are not present
streamsData, ok := cm.Data[osImageUrlConfigMapStreamsField]
var streamsConfig *OSImageURLStreamsConfig
if ok {
var err error
streamsConfig, err = NewOSImageURLStreamsConfigFromBytes([]byte(streamsData))
if err != nil {
return nil, err
}
} else {
// The pre-streams fields that holds the cluster-wide OS images
// Always pointing to the rhel-coreos (osImageUrlConfigMapStreamsDefaultImagesTag) tag
legacyBaseOSContainerImage := cm.Data["baseOSContainerImage"]
legacyBaseOSExtensionsContainerImage := cm.Data["baseOSExtensionsContainerImage"]
streamsConfig = NewOSImageURLStreamsConfigDefault(legacyBaseOSContainerImage, legacyBaseOSExtensionsContainerImage)
}

return &OSImageURLConfig{
BaseOSContainerImage: cm.Data["baseOSContainerImage"],
BaseOSExtensionsContainerImage: cm.Data["baseOSExtensionsContainerImage"],
OSImageURL: cm.Data["osImageURL"],
ReleaseVersion: cm.Data["releaseVersion"],
ReleaseVersion: cm.Data["releaseVersion"],
StreamsConfig: *streamsConfig,
}, nil
}

// Validates a given ConfigMap in the MCO namespace. Valid in this case means the following:
// 1. The name matches what was provided.
// 2. The namespace is set to the MCO's namespace.
// 3. The data field has all of the expected keys.
// 4. The BinarayData field has all of the expected keys.
func validateMCOConfigMap(cm *corev1.ConfigMap, name string, reqDataKeys, reqBinaryKeys []string) error {
func validateMCOConfigMap(cm *corev1.ConfigMap, name string, reqDataKeys []string) error {
if cm.Name != name {
return fmt.Errorf("invalid ConfigMap, expected %s", name)
}
Expand All @@ -116,15 +179,17 @@ func validateMCOConfigMap(cm *corev1.ConfigMap, name string, reqDataKeys, reqBin
}
}
}
return nil
}

if reqBinaryKeys != nil {
for _, reqKey := range reqBinaryKeys {
if _, ok := cm.BinaryData[reqKey]; !ok {
return fmt.Errorf("expecting missing binary data key %s to be present in ConfigMap %s", reqKey, cm.Name)
}
}
func parseJsonFromConfigMap[T any](cm *corev1.ConfigMap, name string, value *T) error {
if err := validateMCOConfigMap(cm, MachineConfigOperatorImagesConfigMapName, []string{name}); err != nil {
return err
}

if err := json.Unmarshal([]byte(cm.Data[name]), value); err != nil {
return fmt.Errorf("could not parse %s bytes: %w", name, err)
}
return nil
}

Expand All @@ -145,5 +210,9 @@ func GetImagesConfig(ctx context.Context, kubeclient clientset.Interface) (*Imag
return nil, fmt.Errorf("could not get configmap %s: %w", MachineConfigOperatorImagesConfigMapName, err)
}

return ParseImagesFromConfigMap(cm)
images := &Images{}
if err := parseJsonFromConfigMap(cm, imagesConfigMapImagesField, images); err != nil {
return nil, fmt.Errorf("could not parse configmap %s: %w", cm.Name, err)
}
return images, nil
}
Loading