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
3 changes: 2 additions & 1 deletion pkg/daemon/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const (
CurrentImageAnnotationKey = "machineconfiguration.openshift.io/currentImage"
// DesiredImageAnnotationKey is used to specify the desired OS image pullspec for a machine
DesiredImageAnnotationKey = "machineconfiguration.openshift.io/desiredImage"

// MachineConfigServerURLAnnotationKey stores the MCS base URL for status reporting
MachineConfigServerURLAnnotationKey = "machineconfiguration.openshift.io/machineConfigServerURL"
// CurrentMachineConfigAnnotationKey is used to fetch current MachineConfig for a machine
CurrentMachineConfigAnnotationKey = "machineconfiguration.openshift.io/currentConfig"
// DesiredMachineConfigAnnotationKey is used to specify the desired MachineConfig for a machine
Expand Down
33 changes: 1 addition & 32 deletions pkg/operator/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ import (
"encoding/pem"
"errors"
"fmt"
"net"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -620,7 +617,7 @@ func (optr *Operator) syncRenderConfig(_ *renderConfig, _ *configv1.ClusterOpera
templatectrl.DockerRegistryKey: imgs.DockerRegistry,
}

ignitionHost, err := getIgnitionHost(&infra.Status)
ignitionHost, err := server.GetIgnitionHost(&infra.Status)
if err != nil {
return err
}
Expand Down Expand Up @@ -698,34 +695,6 @@ func (optr *Operator) getTrustedBundle(proxy *configv1.Proxy) ([]byte, error) {
return trustBundle, nil
}

func getIgnitionHost(infraStatus *configv1.InfrastructureStatus) (string, error) {
internalURL := infraStatus.APIServerInternalURL
internalURLParsed, err := url.Parse(internalURL)
if err != nil {
return "", err
}
securePortStr := strconv.Itoa(server.SecurePort)
ignitionHost := fmt.Sprintf("%s:%s", internalURLParsed.Hostname(), securePortStr)
if infraStatus.PlatformStatus != nil {
switch infraStatus.PlatformStatus.Type {
case configv1.BareMetalPlatformType:
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.BareMetal.APIServerInternalIPs[0], securePortStr)
case configv1.OpenStackPlatformType:
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.OpenStack.APIServerInternalIPs[0], securePortStr)
case configv1.OvirtPlatformType:
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.Ovirt.APIServerInternalIPs[0], securePortStr)
case configv1.VSpherePlatformType:
if infraStatus.PlatformStatus.VSphere != nil {
if len(infraStatus.PlatformStatus.VSphere.APIServerInternalIPs) > 0 {
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.VSphere.APIServerInternalIPs[0], securePortStr)
}
}
}
}

return ignitionHost, nil
}

func (optr *Operator) syncMachineConfigPools(config *renderConfig, _ *configv1.ClusterOperator) error {
generatedPools, err := manifests.GetMachineConfigPools()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/bootstrap_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (bsc *bootstrapServer) GetConfig(cr poolRequest) (*runtime.RawExtension, er
addDataAndMaybeAppendToIgnition(cloudProviderCAPath, cc.Spec.CloudProviderCAData, &ignConf)

appenders := newAppendersBuilder(nil, bsc.kubeconfigFunc, bsc.certs, bsc.serverBaseDir).
WithNodeAnnotations(currConf, "").
WithNodeAnnotations(currConf, "", "").
build()

for _, a := range appenders {
Expand Down
19 changes: 18 additions & 1 deletion pkg/server/cluster_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
corelisterv1 "k8s.io/client-go/listers/core/v1"
Expand Down Expand Up @@ -56,6 +57,7 @@ type clusterServer struct {

kubeconfigFunc kubeconfigFunc
apiserverURL string
mcsURL string
}

const minResyncPeriod = 20 * time.Minute
Expand Down Expand Up @@ -85,6 +87,7 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
machineConfigClient := clientsBuilder.MachineConfigClientOrDie("machine-config-shared-informer")
kubeClient := clientsBuilder.KubeClientOrDie("kube-client-shared-informer")
routeClient := clientsBuilder.RouteClientOrDie("route-client")
configClient := clientsBuilder.ConfigClientOrDie("config-client")
sharedInformerFactory := mcfginformers.NewSharedInformerFactory(machineConfigClient, resyncPeriod()())
kubeNamespacedSharedInformer := informers.NewSharedInformerFactoryWithOptions(kubeClient, resyncPeriod()(), informers.WithNamespace("openshift-machine-config-operator"))

Expand Down Expand Up @@ -112,6 +115,19 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
return nil, errors.New("failed to wait for cache sync")
}

// Fetch Infrastructure to compute MCS URL
ctx := context.Background()
infra, err := configClient.ConfigV1().Infrastructures().Get(ctx, "cluster", metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("failed to get cluster infrastructure: %w", err)
}

ignitionHost, err := GetIgnitionHost(&infra.Status)
if err != nil {
return nil, fmt.Errorf("failed to compute ignition host: %w", err)
}
mcsURL := fmt.Sprintf("https://%s", ignitionHost)

return &clusterServer{
machineConfigPoolLister: mcpLister,
machineConfigLister: mcLister,
Expand All @@ -123,6 +139,7 @@ func NewClusterServer(kubeConfig, apiserverURL string) (Server, error) {
routeclient: routeClient,
kubeconfigFunc: func() ([]byte, []byte, error) { return kubeconfigFromSecret(bootstrapTokenDir, apiserverURL, nil) },
apiserverURL: apiserverURL,
mcsURL: mcsURL,
}, nil
}

Expand Down Expand Up @@ -187,7 +204,7 @@ func (cs *clusterServer) GetConfig(cr poolRequest) (*runtime.RawExtension, error
desiredImage := cs.resolveDesiredImageForPool(mp)

appenders := newAppendersBuilder(cr.version, cs.kubeconfigFunc, []string{}, "").
WithNodeAnnotations(currConf, desiredImage).
WithNodeAnnotations(currConf, desiredImage, cs.mcsURL).
WithCustomAppender(appendDesiredOSImage(desiredImage)).
build()

Expand Down
48 changes: 43 additions & 5 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package server

import (
"fmt"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/clarketm/json"
Expand All @@ -14,6 +16,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"

configv1 "github.com/openshift/api/config/v1"
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
build "github.com/openshift/machine-config-operator/pkg/controller/build/constants"
ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common"
Expand Down Expand Up @@ -70,9 +73,9 @@ func newAppendersBuilder(version *semver.Version, kubeconfigFn kubeconfigFunc, c
}

// WithNodeAnnotations adds the node annotations appender with the specified config and image.
func (ab *appendersBuilder) WithNodeAnnotations(currMachineConfig, image string) *appendersBuilder {
func (ab *appendersBuilder) WithNodeAnnotations(currMachineConfig, image, mcsURL string) *appendersBuilder {
ab.customAppenders = append(ab.customAppenders, func(cfg *ign3types.Config, mc *mcfgv1.MachineConfig) error {
return appendNodeAnnotations(cfg, currMachineConfig, image, mc)
return appendNodeAnnotations(cfg, currMachineConfig, image, mcsURL, mc)
})
return ab
}
Expand Down Expand Up @@ -196,8 +199,8 @@ func appendKubeConfig(conf *ign3types.Config, f kubeconfigFunc) error {
return nil
}

func appendNodeAnnotations(conf *ign3types.Config, currConf, image string, mc *mcfgv1.MachineConfig) error {
anno, err := getNodeAnnotation(currConf, image, mc)
func appendNodeAnnotations(conf *ign3types.Config, currConf, image, mcsURL string, mc *mcfgv1.MachineConfig) error {
anno, err := getNodeAnnotation(currConf, image, mcsURL, mc)
if err != nil {
return err
}
Expand All @@ -208,14 +211,18 @@ func appendNodeAnnotations(conf *ign3types.Config, currConf, image string, mc *m
return nil
}

func getNodeAnnotation(conf, image string, mc *mcfgv1.MachineConfig) (string, error) {
func getNodeAnnotation(conf, image, mcsURL string, mc *mcfgv1.MachineConfig) (string, error) {
nodeAnnotations := map[string]string{
daemonconsts.CurrentMachineConfigAnnotationKey: conf,
daemonconsts.DesiredMachineConfigAnnotationKey: conf,
daemonconsts.FirstPivotMachineConfigAnnotationKey: conf,
daemonconsts.MachineConfigDaemonStateAnnotationKey: daemonconsts.MachineConfigDaemonStateDone,
}

if mcsURL != "" {
nodeAnnotations[daemonconsts.MachineConfigServerURLAnnotationKey] = mcsURL
}

// Determine which image to use:
// 1. Pre-built image from MC annotations (install-time hybrid OCL) takes priority
// 2. Dynamically resolved image parameter (runtime scaling)
Expand Down Expand Up @@ -308,3 +315,34 @@ func addDataAndMaybeAppendToIgnition(path string, data []byte, ignConf *ign3type
appendFileToIgnition(ignConf, path, (string(data)))
}
}

// GetIgnitionHost computes the MCS hostname:port for serving Ignition configs
// from the cluster's Infrastructure status. Platform-specific IP addresses
// take precedence over the parsed APIServerInternalURL hostname.
func GetIgnitionHost(infraStatus *configv1.InfrastructureStatus) (string, error) {
internalURL := infraStatus.APIServerInternalURL
internalURLParsed, err := url.Parse(internalURL)
if err != nil {
return "", err
}
securePortStr := strconv.Itoa(SecurePort)
ignitionHost := fmt.Sprintf("%s:%s", internalURLParsed.Hostname(), securePortStr)
if infraStatus.PlatformStatus != nil {
switch infraStatus.PlatformStatus.Type {
case configv1.BareMetalPlatformType:
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.BareMetal.APIServerInternalIPs[0], securePortStr)
case configv1.OpenStackPlatformType:
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.OpenStack.APIServerInternalIPs[0], securePortStr)
case configv1.OvirtPlatformType:
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.Ovirt.APIServerInternalIPs[0], securePortStr)
case configv1.VSpherePlatformType:
if infraStatus.PlatformStatus.VSphere != nil {
if len(infraStatus.PlatformStatus.VSphere.APIServerInternalIPs) > 0 {
ignitionHost = net.JoinHostPort(infraStatus.PlatformStatus.VSphere.APIServerInternalIPs[0], securePortStr)
}
}
}
}

return ignitionHost, nil
Comment on lines +322 to +347
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Harden GetIgnitionHost (IPv6 host:port + panic safety)

  • Replace the fallback fmt.Sprintf("%s:%s", internalURLParsed.Hostname(), securePortStr) with net.JoinHostPort(...) so IPv6 literals get valid brackets.
  • Add infraStatus == nil handling and check len(APIServerInternalIPs) > 0 before indexing [0] in the BareMetal/OpenStack/Ovirt/VSphere branches to avoid panics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/server.go` around lines 322 - 347, GetIgnitionHost currently uses
fmt.Sprintf("%s:%s", internalURLParsed.Hostname(), securePortStr) which breaks
for IPv6 and assumes infraStatus and API IP slices are non-nil/non-empty; change
the fallback to use net.JoinHostPort(internalURLParsed.Hostname(),
securePortStr) and add an initial nil check for infraStatus to return an error
if nil, then in each platform branch (BareMetal/APIServerInternalIPs,
OpenStack/APIServerInternalIPs, Ovirt/APIServerInternalIPs,
VSphere.APIServerInternalIPs) check that the slice exists and len(...)>0 before
indexing [0] to avoid panics, keeping the SecurePort and ignitionHost assignment
logic otherwise the same.

}
4 changes: 2 additions & 2 deletions pkg/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func TestBootstrapServer(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error while appending file to ignition: %v", err)
}
anno, err := getNodeAnnotation(mp.Status.Configuration.Name, "", mc)
anno, err := getNodeAnnotation(mp.Status.Configuration.Name, "", "https://api-int.test.example.com:22623", mc)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

These tests still don’t verify the new MCS URL annotation.

The hardcoded URL only goes into a local mcIgnCfg that never drives the final assertions, and the servers under test are not configured to emit that URL. These cases will keep passing even if MachineConfigServerURLAnnotationKey is never written.

Also applies to: 383-383

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/server/server_test.go` at line 188, The tests currently call
getNodeAnnotation and build a local mcIgnCfg but never assert that
MachineConfigServerURLAnnotationKey was written or configure the test servers to
emit that MCS URL; update the tests to (1) configure the test server(s) used in
these cases to return/emit the expected MCS URL, (2) set the expected URL value
and call getNodeAnnotation(mp.Status.Configuration.Name, "", "<expected-url>",
mc) as you already do, and (3) add explicit assertions that the returned
annotation map contains MachineConfigServerURLAnnotationKey with the expected
URL value (use the same unique symbols: getNodeAnnotation,
MachineConfigServerURLAnnotationKey, and mcIgnCfg to locate where to change
behavior and where to add the assertion). Ensure both occurrences (around lines
shown) are updated consistently.

if err != nil {
t.Fatalf("unexpected error while creating annotations err: %v", err)
}
Expand Down Expand Up @@ -380,7 +380,7 @@ func TestClusterServer(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error while appending file to ignition: %v", err)
}
anno, err := getNodeAnnotation(mp.Status.Configuration.Name, "", mc)
anno, err := getNodeAnnotation(mp.Status.Configuration.Name, "", "https://api-int.test.example.com:22623", mc)
if err != nil {
t.Fatalf("unexpected error while creating annotations err: %v", err)
}
Expand Down