diff --git a/go.mod b/go.mod index 448c616fa9..38b0628e69 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,10 @@ module github.com/openshift/library-go go 1.26.0 +replace github.com/openshift/api => github.com/bhperry/openshift-api v0.0.0-20260713144529-6c01886662a4 + +replace github.com/openshift/client-go => github.com/bhperry/openshift-client-go v0.0.0-20260716151504-c6335807bf79 + require ( github.com/RangelReale/osincli v0.0.0-20160924135400-fababb0555f2 github.com/blang/semver/v4 v4.0.0 diff --git a/go.sum b/go.sum index 4faf94f0d9..736ffcd18e 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bhperry/openshift-api v0.0.0-20260713144529-6c01886662a4 h1:BUDKaQuVUj1zyTKsx3i8OGl7diLg3KUKvjhP3WJ1wk4= +github.com/bhperry/openshift-api v0.0.0-20260713144529-6c01886662a4/go.mod h1:7WJ3IPaK6nmWT8bDcaNooHqd0H5WepjVqV/10VlkMEM= +github.com/bhperry/openshift-client-go v0.0.0-20260716151504-c6335807bf79 h1:T1Ld4Rf0ZdfeaNX+HXzSCNqtvp5b774qa8Rj59wp7RE= +github.com/bhperry/openshift-client-go v0.0.0-20260716151504-c6335807bf79/go.mod h1:Ffgh01R2/DnIbnMmnuBtg1hYIKvQBFp3J2tkISzgGhU= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -248,12 +252,8 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= -github.com/openshift/api v0.0.0-20260715165912-72066cc9718b h1:gN3SihCYEwoIksD+f24wHhwiEgvaV0RxNjgmkDvBBeg= -github.com/openshift/api v0.0.0-20260715165912-72066cc9718b/go.mod h1:k6qH5QOVa5GDln2VVm8Jz4NV3Z7R2SATHFLwGS6Wh3M= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee h1:+Sp5GGnjHDhT/a/nQ1xdp43UscBMr7G5wxsYotyhzJ4= github.com/openshift/build-machinery-go v0.0.0-20250530140348-dc5b2804eeee/go.mod h1:8jcm8UPtg2mCAsxfqKil1xrmRMI3a+XU2TZ9fF8A7TE= -github.com/openshift/client-go v0.0.0-20260715172546-dac61734e0ec h1:UDjX+mot5IVLpcChyBqLXG1oSB29s4UkqFmgNb0Xsqc= -github.com/openshift/client-go v0.0.0-20260715172546-dac61734e0ec/go.mod h1:iMHec0APKVjOH8GfL/RxddX8DuiuSvPlRe+s7KDqlyA= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/pkg/operator/staticpod/controller/installer/installer_controller.go b/pkg/operator/staticpod/controller/installer/installer_controller.go index f6c4dc5538..ec5aba1470 100644 --- a/pkg/operator/staticpod/controller/installer/installer_controller.go +++ b/pkg/operator/staticpod/controller/installer/installer_controller.go @@ -12,6 +12,7 @@ import ( "time" "github.com/davecgh/go-spew/spew" + "github.com/pkg/errors" operatorv1 "github.com/openshift/api/operator/v1" applyoperatorv1 "github.com/openshift/client-go/operator/applyconfigurations/operator/v1" @@ -34,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + corev1listers "k8s.io/client-go/listers/core/v1" "k8s.io/klog/v2" "k8s.io/utils/clock" "k8s.io/utils/ptr" @@ -94,6 +96,7 @@ type InstallerController struct { configMapsGetter corev1client.ConfigMapsGetter secretsGetter corev1client.SecretsGetter podsGetter corev1client.PodsGetter + nodeLister corev1listers.NodeLister eventRecorder events.Recorder now func() time.Time // for test plumbing @@ -179,6 +182,7 @@ func NewInstallerController( secrets []revision.RevisionResource, command []string, kubeInformersForTargetNamespace informers.SharedInformerFactory, + kubeInformersClusterScoped informers.SharedInformerFactory, operatorClient v1helpers.StaticPodOperatorClient, configMapsGetter corev1client.ConfigMapsGetter, secretsGetter corev1client.SecretsGetter, @@ -197,6 +201,7 @@ func NewInstallerController( configMapsGetter: configMapsGetter, secretsGetter: secretsGetter, podsGetter: podsGetter, + nodeLister: kubeInformersClusterScoped.Core().V1().Nodes().Lister(), eventRecorder: eventRecorder.WithComponentSuffix("installer-controller"), now: time.Now, startupMonitorEnabled: func() (bool, error) { @@ -218,7 +223,12 @@ func NewInstallerController( // informers are needed here because their Getter are cached lister based kubeInformersForTargetNamespace.Core().V1().Pods().Informer(), kubeInformersForTargetNamespace.Core().V1().ConfigMaps().Informer(), - kubeInformersForTargetNamespace.Core().V1().Secrets().Informer()) + kubeInformersForTargetNamespace.Core().V1().Secrets().Informer(), + ). + WithBareInformers( + // Don't need to sync on node changes, just wait for informer cache sync + kubeInformersClusterScoped.Core().V1().Nodes().Informer(), + ) return c } @@ -599,6 +609,7 @@ func prepareNodeStatusApplyConfigurationFor(nodeStatuses []operatorv1.NodeStatus func nodeStatusToNodeStatusApplyConfigurations(nodeStatus operatorv1.NodeStatus) *applyoperatorv1.NodeStatusApplyConfiguration { nodeStatusApplyConfiguration := applyoperatorv1.NodeStatus(). WithNodeName(nodeStatus.NodeName). + WithNodeUID(nodeStatus.NodeUID). WithCurrentRevision(nodeStatus.CurrentRevision). WithTargetRevision(nodeStatus.TargetRevision). WithLastFailedRevision(nodeStatus.LastFailedRevision). @@ -615,6 +626,7 @@ func nodeStatusToNodeStatusApplyConfigurations(nodeStatus operatorv1.NodeStatus) func nodeStatusApplyConfigToOperatorNodeStatus(nodeStatusApplyConfiguration *applyoperatorv1.NodeStatusApplyConfiguration) *operatorv1.NodeStatus { return &operatorv1.NodeStatus{ NodeName: ptr.Deref(nodeStatusApplyConfiguration.NodeName, ""), + NodeUID: ptr.Deref(nodeStatusApplyConfiguration.NodeUID, ""), CurrentRevision: ptr.Deref(nodeStatusApplyConfiguration.CurrentRevision, 0), TargetRevision: ptr.Deref(nodeStatusApplyConfiguration.TargetRevision, 0), LastFailedRevision: ptr.Deref(nodeStatusApplyConfiguration.LastFailedRevision, 0), @@ -1123,6 +1135,25 @@ func (c InstallerController) ensureRequiredResourcesExist(ctx context.Context, r return fmt.Errorf("missing required resources: %v", aggregatedErr) } +func (c *InstallerController) hasNodeUIDChanged(nodeName, nodeUid string) (bool, error) { + if nodeUid == "" { + return false, nil + } + node, err := c.nodeLister.Get(nodeName) + if apierrors.IsNotFound(err) { + klog.Warningf("Skipping node status update for %s: node not found", nodeName) + return true, nil + } + if err != nil { + return false, err + } + if string(node.UID) != nodeUid { + klog.Warningf("Skipping node status update for %s: node UID changed (stored=%s, actual=%s)", nodeName, nodeUid, node.UID) + return true, nil + } + return false, nil +} + func (c *InstallerController) Sync(ctx context.Context, syncCtx factory.SyncContext) error { operatorSpec, originalOperatorStatus, operatorResourceVersion, err := c.operatorClient.GetStaticPodOperatorState() if err != nil { @@ -1164,6 +1195,17 @@ func (c *InstallerController) Sync(ctx context.Context, syncCtx factory.SyncCont return nil } + // Check for stale node status entries with mismatched UID + for _, nodeStatus := range originalOperatorStatus.NodeStatuses { + if uidChanged, err := c.hasNodeUIDChanged(nodeStatus.NodeName, nodeStatus.NodeUID); err != nil { + return errors.Wrapf(err, "error validating uid for node %s", nodeStatus.NodeName) + } else if uidChanged { + // Avoid applying node statuses when a stale entry is found + // Sync will retrigger when the old node status is removed by the node controller + return nil + } + } + err = c.ensureRequiredResourcesExistFn(ctx, originalOperatorStatus.LatestAvailableRevision) // Only manage installation pods when all required certs are present. @@ -1204,6 +1246,7 @@ func deepCopyNodeStatusWithoutOldFailedState(ns *operatorv1.NodeStatus) *operato if ns.TargetRevision == 0 || ns.TargetRevision != ns.LastFailedRevision { return &operatorv1.NodeStatus{ NodeName: ns.NodeName, + NodeUID: ns.NodeUID, CurrentRevision: ns.CurrentRevision, TargetRevision: ns.TargetRevision, } diff --git a/pkg/operator/staticpod/controller/installer/installer_controller_test.go b/pkg/operator/staticpod/controller/installer/installer_controller_test.go index 3b18e46e13..a59062c100 100644 --- a/pkg/operator/staticpod/controller/installer/installer_controller_test.go +++ b/pkg/operator/staticpod/controller/installer/installer_controller_test.go @@ -555,6 +555,7 @@ func TestNewNodeStateForInstallInProgress(t *testing.T) { []revision.RevisionResource{{Name: "test-secret"}}, []string{"/bin/true", "--foo=test", "--bar"}, kubeInformers, + kubeInformers, fakeStaticPodOperatorClient, kubeClient.CoreV1(), kubeClient.CoreV1(), @@ -690,6 +691,7 @@ func testSync(t *testing.T, firstInstallerBehaviour testSyncInstallerBehaviour, []revision.RevisionResource{{Name: "test-secret"}}, podCommand, kubeInformers, + kubeInformers, fakeStaticPodOperatorClient, kubeClient.CoreV1(), kubeClient.CoreV1(), @@ -1099,6 +1101,7 @@ func TestCreateInstallerPod(t *testing.T) { []revision.RevisionResource{{Name: "test-secret"}}, []string{"/bin/true"}, kubeInformers, + kubeInformers, fakeStaticPodOperatorClient, kubeClient.CoreV1(), kubeClient.CoreV1(), @@ -1268,6 +1271,7 @@ func TestEnsureInstallerPod(t *testing.T) { tt.secrets, []string{"/bin/true"}, kubeInformers, + kubeInformers, fakeStaticPodOperatorClient, kubeClient.CoreV1(), kubeClient.CoreV1(), @@ -2011,6 +2015,7 @@ func TestCreateInstallerPodMultiNode(t *testing.T) { []revision.RevisionResource{{Name: "test-secret"}}, []string{"/bin/true"}, kubeInformers, + kubeInformers, fakeStaticPodOperatorClient, kubeClient.CoreV1(), kubeClient.CoreV1(), diff --git a/pkg/operator/staticpod/controller/node/node_controller.go b/pkg/operator/staticpod/controller/node/node_controller.go index 910ff9d213..90dd9653a3 100644 --- a/pkg/operator/staticpod/controller/node/node_controller.go +++ b/pkg/operator/staticpod/controller/node/node_controller.go @@ -84,22 +84,24 @@ func (c *NodeController) sync(ctx context.Context, syncCtx factory.SyncContext) nodes = append(nodes, extraNodes...) } + nodeUIDs := map[string]string{} + for _, node := range nodes { + nodeUIDs[node.Name] = string(node.UID) + } + jsonPatch := jsonpatch.New() var removedNodeStatusesCounter int newTargetNodeStates := []*applyoperatorv1.NodeStatusApplyConfiguration{} // remove entries for missing nodes for i, nodeState := range originalOperatorStatus.NodeStatuses { - found := false - for _, node := range nodes { - if nodeState.NodeName == node.Name { - found = true - } - } - if found { - newTargetNodeState := applyoperatorv1.NodeStatus().WithNodeName(originalOperatorStatus.NodeStatuses[i].NodeName) + currentUID, found := nodeUIDs[nodeState.NodeName] + if found && (nodeState.NodeUID == currentUID || nodeState.NodeUID == "") { + newTargetNodeState := applyoperatorv1.NodeStatus(). + WithNodeName(nodeState.NodeName). + WithNodeUID(currentUID) newTargetNodeStates = append(newTargetNodeStates, newTargetNodeState) } else { - syncCtx.Recorder().Warningf("MasterNodeRemoved", "Observed removal of master node %s", nodeState.NodeName) + syncCtx.Recorder().Warningf("MasterNodeRemoved", "Observed removal of master node %s (UID: %s)", nodeState.NodeName, nodeState.NodeUID) // each delete operation is applied to the object, // which modifies the array. Thus, we need to // adjust the indices to find the correct node to remove. @@ -116,16 +118,19 @@ func (c *NodeController) sync(ctx context.Context, syncCtx factory.SyncContext) for _, node := range nodes { found := false for _, nodeState := range originalOperatorStatus.NodeStatuses { + // NodeUID is intentionally ignored here. Nodes that are replaced with the same name will get added in the next sync + // to ensure previous nodeStatus from a stale cache isn't merged in with the fresh status. if nodeState.NodeName == node.Name { found = true + break } } if found { continue } - syncCtx.Recorder().Eventf("MasterNodeObserved", "Observed new master node %s", node.Name) - newTargetNodeState := applyoperatorv1.NodeStatus().WithNodeName(node.Name) + syncCtx.Recorder().Eventf("MasterNodeObserved", "Observed new master node %s (UID: %s)", node.Name, node.UID) + newTargetNodeState := applyoperatorv1.NodeStatus().WithNodeName(node.Name).WithNodeUID(string(node.UID)) newTargetNodeStates = append(newTargetNodeStates, newTargetNodeState) } diff --git a/pkg/operator/staticpod/controller/node/node_controller_test.go b/pkg/operator/staticpod/controller/node/node_controller_test.go index 20784fbca9..e1bd821fe1 100644 --- a/pkg/operator/staticpod/controller/node/node_controller_test.go +++ b/pkg/operator/staticpod/controller/node/node_controller_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/kubernetes/fake" clientgotesting "k8s.io/client-go/testing" clocktesting "k8s.io/utils/clock/testing" @@ -28,6 +29,7 @@ import ( func fakeMasterNode(name string) *corev1.Node { n := &corev1.Node{} n.Name = name + n.UID = uuid.NewUUID() n.Labels = map[string]string{ "node-role.kubernetes.io/master": "", } @@ -98,6 +100,21 @@ func validateJSONPatch(expected, actual *jsonpatch.PatchSet) error { return nil } +func validateNodeStatuses(nodeStatuses []operatorv1.NodeStatus, nodes ...*corev1.Node) error { + if len(nodes) != len(nodeStatuses) { + return fmt.Errorf("expected %d node status, got %d", len(nodes), len(nodeStatuses)) + } + for i, node := range nodes { + if nodeStatuses[i].NodeName != node.Name { + return fmt.Errorf("expected %q as node name, got %q", node.Name, nodeStatuses[i].NodeName) + } + if nodeStatuses[i].NodeUID != string(node.UID) { + return fmt.Errorf("expected %q as node UID, got %q", node.UID, nodeStatuses[i].NodeUID) + } + } + return nil +} + func TestNodeControllerDegradedConditionType(t *testing.T) { scenarios := []struct { name string @@ -729,3 +746,123 @@ func TestNewNodeController(t *testing.T) { } } + +func TestNodeControllerTrackNodeUIDs(t *testing.T) { + node1 := fakeMasterNode("test-node-1") + node2 := fakeMasterNode("test-node-2") + + tests := []struct { + name string + syncCycles int + startNodes []runtime.Object + startNodeStatus []operatorv1.NodeStatus + expectedNodes [][2]string + evaluateNodeStatus func(s []operatorv1.NodeStatus, cycle int) error + }{ + { + name: "node exists missing uid in status", + syncCycles: 1, + startNodes: []runtime.Object{node1}, + startNodeStatus: []operatorv1.NodeStatus{ + { + NodeName: "test-node-1", + }, + }, + evaluateNodeStatus: func(s []operatorv1.NodeStatus, cycle int) error { + return validateNodeStatuses(s, node1) + }, + }, + { + name: "node exists matching uid in status", + syncCycles: 1, + startNodes: []runtime.Object{node1}, + startNodeStatus: []operatorv1.NodeStatus{ + { + NodeName: "test-node-1", + NodeUID: string(node1.UID), + }, + }, + evaluateNodeStatus: func(s []operatorv1.NodeStatus, cycle int) error { + return validateNodeStatuses(s, node1) + }, + }, + { + name: "node replaced mismatched uid in status", + syncCycles: 2, + startNodes: []runtime.Object{node1}, + startNodeStatus: []operatorv1.NodeStatus{ + { + NodeName: "test-node-1", + NodeUID: string(uuid.NewUUID()), + }, + }, + evaluateNodeStatus: func(s []operatorv1.NodeStatus, cycle int) error { + if cycle == 0 { + // On first sync, old node status is removed + return validateNodeStatuses(s) + } else { + // On second sync, new node status is added + return validateNodeStatuses(s, node1) + } + }, + }, + { + name: "node removed", + syncCycles: 1, + startNodes: []runtime.Object{node2}, + startNodeStatus: []operatorv1.NodeStatus{ + { + NodeName: "test-node-1", + NodeUID: string(node1.UID), + }, + }, + evaluateNodeStatus: func(s []operatorv1.NodeStatus, cycle int) error { + return validateNodeStatuses(s, node2) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + kubeClient := fake.NewSimpleClientset(test.startNodes...) + fakeLister := v1helpers.NewFakeNodeLister(kubeClient) + fakeStaticPodOperatorClient := v1helpers.NewFakeStaticPodOperatorClient( + &operatorv1.StaticPodOperatorSpec{ + OperatorSpec: operatorv1.OperatorSpec{ + ManagementState: operatorv1.Managed, + }, + }, + &operatorv1.StaticPodOperatorStatus{ + OperatorStatus: operatorv1.OperatorStatus{ + LatestAvailableRevision: 1, + }, + NodeStatuses: test.startNodeStatus, + }, + nil, + nil, + ) + + eventRecorder := events.NewRecorder(kubeClient.CoreV1().Events("test"), "test-operator", &corev1.ObjectReference{}, clocktesting.NewFakePassiveClock(time.Now())) + + c := &NodeController{ + operatorClient: fakeStaticPodOperatorClient, + nodeLister: fakeLister, + masterNodesSelector: masterNodesSelector(t), + } + + // override the lister so we don't have to run the informer to list nodes + c.nodeLister = fakeLister + for i := range test.syncCycles { + if err := c.sync(context.TODO(), factory.NewSyncContext("NodeController", eventRecorder)); err != nil { + t.Fatal(err) + } + + _, status, _, _ := fakeStaticPodOperatorClient.GetStaticPodOperatorState() + if err := test.evaluateNodeStatus(status.NodeStatuses, i); err != nil { + t.Errorf("%s: failed to evaluate node status: %v", test.name, err) + } + } + }) + + } +} diff --git a/pkg/operator/staticpod/controllers.go b/pkg/operator/staticpod/controllers.go index 62722c66c3..8b2777c817 100644 --- a/pkg/operator/staticpod/controllers.go +++ b/pkg/operator/staticpod/controllers.go @@ -283,6 +283,7 @@ func (b *staticPodOperatorControllerBuilder) ToControllers() (manager.Controller b.revisionSecrets, b.installCommand, operandInformers, + clusterInformers, b.staticPodOperatorClient, configMapClient, secretClient, diff --git a/pkg/operator/v1helpers/test_helpers.go b/pkg/operator/v1helpers/test_helpers.go index 66a7b6d89c..012da38edb 100644 --- a/pkg/operator/v1helpers/test_helpers.go +++ b/pkg/operator/v1helpers/test_helpers.go @@ -301,7 +301,11 @@ func (n *fakeNodeLister) List(selector labels.Selector) ([]*corev1.Node, error) } func (n *fakeNodeLister) Get(name string) (*corev1.Node, error) { - panic("implement me") + node, err := n.client.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + return node, nil } // NewFakeOperatorClient returns a fake operator client suitable to use in static pod controller unit tests. @@ -499,6 +503,7 @@ func mergeStaticPodOperatorStatusApplyConfiguration(currentOperatorStatus *v1.Op for _, nodeStatus := range applyConfiguration.NodeStatuses { newNodeStatus := operatorv1.NodeStatus{ NodeName: ptr.Deref(nodeStatus.NodeName, ""), + NodeUID: ptr.Deref(nodeStatus.NodeUID, ""), CurrentRevision: ptr.Deref(nodeStatus.CurrentRevision, 0), TargetRevision: ptr.Deref(nodeStatus.TargetRevision, 0), LastFailedRevision: ptr.Deref(nodeStatus.LastFailedRevision, 0), diff --git a/vendor/github.com/openshift/api/Makefile b/vendor/github.com/openshift/api/Makefile index b6c0de378c..e0e97ed1ac 100644 --- a/vendor/github.com/openshift/api/Makefile +++ b/vendor/github.com/openshift/api/Makefile @@ -4,7 +4,7 @@ all: build update: update-non-codegen update-codegen RUNTIME ?= podman -RUNTIME_IMAGE_NAME ?= registry.ci.openshift.org/openshift/release:rhel-9-release-golang-1.26-openshift-5.0 +RUNTIME_IMAGE_NAME ?= registry.ci.openshift.org/openshift/release:rhel-9-release-golang-1.25-openshift-4.22 EXCLUDE_DIRS := _output/ dependencymagnet/ hack/ third_party/ tls/ tools/ vendor/ tests/ GO_PACKAGES :=$(addsuffix ...,$(addprefix ./,$(filter-out $(EXCLUDE_DIRS), $(wildcard */)))) diff --git a/vendor/github.com/openshift/api/config/v1/types_infrastructure.go b/vendor/github.com/openshift/api/config/v1/types_infrastructure.go index 5d9f10374e..e8aaa810f5 100644 --- a/vendor/github.com/openshift/api/config/v1/types_infrastructure.go +++ b/vendor/github.com/openshift/api/config/v1/types_infrastructure.go @@ -660,6 +660,7 @@ type AzurePlatformStatus struct { // // +default={"dnsType": "PlatformDefault"} // +kubebuilder:default={"dnsType": "PlatformDefault"} + // +openshift:enable:FeatureGate=AzureClusterHostedDNSInstall // +optional CloudLoadBalancerConfig *CloudLoadBalancerConfig `json:"cloudLoadBalancerConfig,omitempty"` diff --git a/vendor/github.com/openshift/api/config/v1/zz_generated.featuregated-crd-manifests.yaml b/vendor/github.com/openshift/api/config/v1/zz_generated.featuregated-crd-manifests.yaml index 76f78df82d..f8a272d496 100644 --- a/vendor/github.com/openshift/api/config/v1/zz_generated.featuregated-crd-manifests.yaml +++ b/vendor/github.com/openshift/api/config/v1/zz_generated.featuregated-crd-manifests.yaml @@ -396,6 +396,7 @@ infrastructures.config.openshift.io: FeatureGates: - AWSClusterHostedDNSInstall - AWSDualStackInstall + - AzureClusterHostedDNSInstall - AzureDualStackInstall - DualReplica - DyanmicServiceEndpointIBMCloud diff --git a/vendor/github.com/openshift/api/features.md b/vendor/github.com/openshift/api/features.md index c78d402696..77737db5fc 100644 --- a/vendor/github.com/openshift/api/features.md +++ b/vendor/github.com/openshift/api/features.md @@ -97,6 +97,7 @@ | OSStreams| | Enabled | Enabled | Enabled | | Enabled | Enabled | Enabled | | AWSClusterHostedDNSInstall| Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | | AWSServiceLBNetworkSecurityGroup| Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | +| AzureClusterHostedDNSInstall| Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | | AzureWorkloadIdentity| Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | | BootImageSkewEnforcement| Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | | BuildCSIVolumes| Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | Enabled | diff --git a/vendor/github.com/openshift/api/features/features.go b/vendor/github.com/openshift/api/features/features.go index b45bef7705..0ed0d37c7b 100644 --- a/vendor/github.com/openshift/api/features/features.go +++ b/vendor/github.com/openshift/api/features/features.go @@ -272,6 +272,14 @@ var ( enable(inTechPreviewNoUpgrade(), inDevPreviewNoUpgrade()). mustRegister() + FeatureGateAzureClusterHostedDNSInstall = newFeatureGate("AzureClusterHostedDNSInstall"). + reportProblemsToJiraComponent("Installer"). + contactPerson("sadasu"). + productScope(ocpSpecific). + enhancementPR("https://github.com/openshift/enhancements/pull/1468"). + enable(inDefault(), inOKD(), inTechPreviewNoUpgrade(), inDevPreviewNoUpgrade()). + mustRegister() + FeatureGateMixedCPUsAllocation = newFeatureGate("MixedCPUsAllocation"). reportProblemsToJiraComponent("NodeTuningOperator"). contactPerson("titzhak"). diff --git a/vendor/github.com/openshift/api/operator/v1/types.go b/vendor/github.com/openshift/api/operator/v1/types.go index 3a2141abb9..599ceb03e1 100644 --- a/vendor/github.com/openshift/api/operator/v1/types.go +++ b/vendor/github.com/openshift/api/operator/v1/types.go @@ -266,6 +266,18 @@ type NodeStatus struct { // +required NodeName string `json:"nodeName"` + // nodeUID is the UID of the node. + // This field is used to detect that a node has been deleted and recreated + // with the same name. When the UID changes, it indicates the node is a + // new instance and the controller should treat this status entry as stale. + // When omitted, UID-based node replacement detection is not available + // for this entry. + // +kubebuilder:validation:MinLength=36 + // +kubebuilder:validation:MaxLength=36 + // +kubebuilder:validation:Format=uuid + // +optional + NodeUID string `json:"nodeUID,omitempty"` + // currentRevision is the generation of the most recently successful deployment. // Can not be set on creation of a nodeStatus. Updates must only increase the value. // +kubebuilder:validation:XValidation:rule="self >= oldSelf",message="must only increase" diff --git a/vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go b/vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go index 114b5c7a68..2825e758b5 100644 --- a/vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go +++ b/vendor/github.com/openshift/api/operator/v1/zz_generated.swagger_doc_generated.go @@ -37,6 +37,7 @@ func (MyOperatorResource) SwaggerDoc() map[string]string { var map_NodeStatus = map[string]string{ "": "NodeStatus provides information about the current state of a particular node managed by this operator.", "nodeName": "nodeName is the name of the node", + "nodeUID": "nodeUID is the UID of the node. This field is used to detect that a node has been deleted and recreated with the same name. When the UID changes, it indicates the node is a new instance and the controller should treat this status entry as stale. When omitted, UID-based node replacement detection is not available for this entry.", "currentRevision": "currentRevision is the generation of the most recently successful deployment. Can not be set on creation of a nodeStatus. Updates must only increase the value.", "targetRevision": "targetRevision is the generation of the deployment we're trying to apply. Can not be set on creation of a nodeStatus.", "lastFailedRevision": "lastFailedRevision is the generation of the deployment we tried and failed to deploy.", diff --git a/vendor/github.com/openshift/api/operator/v1alpha1/types_clusterapi.go b/vendor/github.com/openshift/api/operator/v1alpha1/types_clusterapi.go index 291cedc145..719893f8a5 100644 --- a/vendor/github.com/openshift/api/operator/v1alpha1/types_clusterapi.go +++ b/vendor/github.com/openshift/api/operator/v1alpha1/types_clusterapi.go @@ -244,24 +244,13 @@ type ClusterAPIInstallerComponentSource struct { Image ClusterAPIInstallerComponentImage `json:"image,omitzero"` } -// The image name validation regex is derived from the OCI distribution reference: -// https://github.com/distribution/reference/blob/main/regexp.go -// It matches: domainAndPort "/" remoteName -// -// domainAndPort = (domainName | "[" ipv6 "]") [ ":" port ] -// domainName = label ("." label)* -// label = alphanumeric, may contain hyphens but must start/end with [a-zA-Z0-9] -// remoteName = pathComponent ("/" pathComponent)* -// pathComponent = [a-z0-9]+ separated by ".", "_", "__", or "-"+ - -// ImageDigestFormat is a type that conforms to the format host[:port][/namespace[/namespace...]]/name@sha256:. -// The host may be a dotted domain name (including IPv4 addresses like 192.168.1.1), or an IPv6 address in brackets (e.g. [fd00::1]). +// ImageDigestFormat is a type that conforms to the format host[:port][/namespace]/name@sha256:. // The digest must be 64 characters long, and consist only of lowercase hexadecimal characters, a-f and 0-9. // The length of the field must be between 1 to 447 characters. // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=447 // +kubebuilder:validation:XValidation:rule=`(self.split('@').size() == 2 && self.split('@')[1].matches('^sha256:[a-f0-9]{64}$'))`,message="the OCI Image reference must end with a valid '@sha256:' suffix, where '' is 64 characters long" -// +kubebuilder:validation:XValidation:rule=`(self.split('@')[0].matches('^(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:\\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))*|\\[(?:[a-fA-F0-9:]+)\\])(?::[0-9]+)?/[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]+)[a-z0-9]+)*)*$'))`,message="the OCI Image name should follow the host[:port][/namespace[/namespace...]]/name format, where host is a domain name, IPv4, or IPv6 address in brackets" +// +kubebuilder:validation:XValidation:rule=`(self.split('@')[0].matches('^([a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+(:[0-9]{2,5})?/([a-zA-Z0-9-_]{0,61}/)?[a-zA-Z0-9-_.]*?$'))`,message="the OCI Image name should follow the host[:port][/namespace]/name format, resembling a valid URL without the scheme" type ImageDigestFormat string // ClusterAPIInstallerComponentImage defines an image source for a component. diff --git a/vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go b/vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go index 7e41cc30bf..9c2b799bf5 100644 --- a/vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go +++ b/vendor/github.com/openshift/client-go/operator/applyconfigurations/internal/internal.go @@ -3181,6 +3181,9 @@ var schemaYAML = typed.YAMLObject(`types: type: scalar: string default: "" + - name: nodeUID + type: + scalar: string - name: targetRevision type: scalar: numeric diff --git a/vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go b/vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go index f107c370d2..ab2db04ade 100644 --- a/vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go +++ b/vendor/github.com/openshift/client-go/operator/applyconfigurations/operator/v1/nodestatus.go @@ -13,6 +13,13 @@ import ( type NodeStatusApplyConfiguration struct { // nodeName is the name of the node NodeName *string `json:"nodeName,omitempty"` + // nodeUID is the UID of the node. + // This field is used to detect that a node has been deleted and recreated + // with the same name. When the UID changes, it indicates the node is a + // new instance and the controller should treat this status entry as stale. + // When omitted, UID-based node replacement detection is not available + // for this entry. + NodeUID *string `json:"nodeUID,omitempty"` // currentRevision is the generation of the most recently successful deployment. // Can not be set on creation of a nodeStatus. Updates must only increase the value. CurrentRevision *int32 `json:"currentRevision,omitempty"` @@ -47,6 +54,14 @@ func (b *NodeStatusApplyConfiguration) WithNodeName(value string) *NodeStatusApp return b } +// WithNodeUID sets the NodeUID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NodeUID field is set to the value of the last call. +func (b *NodeStatusApplyConfiguration) WithNodeUID(value string) *NodeStatusApplyConfiguration { + b.NodeUID = &value + return b +} + // WithCurrentRevision sets the CurrentRevision field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the CurrentRevision field is set to the value of the last call. diff --git a/vendor/modules.txt b/vendor/modules.txt index cb5b965766..3431c99aaf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -314,8 +314,8 @@ github.com/opencontainers/image-spec/specs-go/v1 ## explicit; go 1.19 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/pkg/pwalkdir -# github.com/openshift/api v0.0.0-20260715165912-72066cc9718b -## explicit; go 1.26.0 +# github.com/openshift/api v0.0.0-20260715165912-72066cc9718b => github.com/bhperry/openshift-api v0.0.0-20260713144529-6c01886662a4 +## explicit; go 1.25.0 github.com/openshift/api github.com/openshift/api/annotations github.com/openshift/api/apiextensions @@ -404,7 +404,7 @@ github.com/openshift/build-machinery-go/make/targets/golang github.com/openshift/build-machinery-go/make/targets/openshift github.com/openshift/build-machinery-go/make/targets/openshift/operator github.com/openshift/build-machinery-go/scripts -# github.com/openshift/client-go v0.0.0-20260715172546-dac61734e0ec +# github.com/openshift/client-go v0.0.0-20260715172546-dac61734e0ec => github.com/bhperry/openshift-client-go v0.0.0-20260716151504-c6335807bf79 ## explicit; go 1.26.0 github.com/openshift/client-go/apps/applyconfigurations/apps/v1 github.com/openshift/client-go/apps/applyconfigurations/internal @@ -1731,3 +1731,5 @@ sigs.k8s.io/structured-merge-diff/v6/value # sigs.k8s.io/yaml v1.6.0 ## explicit; go 1.22 sigs.k8s.io/yaml +# github.com/openshift/api => github.com/bhperry/openshift-api v0.0.0-20260713144529-6c01886662a4 +# github.com/openshift/client-go => github.com/bhperry/openshift-client-go v0.0.0-20260716151504-c6335807bf79