diff --git a/cmd/machineset/main.go b/cmd/machineset/main.go index 3f548992b..acf080fbf 100644 --- a/cmd/machineset/main.go +++ b/cmd/machineset/main.go @@ -210,20 +210,19 @@ func main() { klog.Infof("FeatureGateMachineAPIMigration initialised: %t", defaultMutableGate.Enabled(featuregate.Feature(apifeatures.FeatureGateMachineAPIMigration))) // Enable defaulting and validating webhooks - machineDefaulter, err := mapiwebhooks.NewMachineDefaulter() + defaulterConfig, err := mapiwebhooks.ResolveDefaulterConfig() if err != nil { log.Fatal(err) } + machineDefaulter := mapiwebhooks.NewMachineDefaulter(defaulterConfig) + machineValidator, err := mapiwebhooks.NewMachineValidator(mgr.GetClient(), defaultMutableGate) if err != nil { log.Fatal(err) } - machineSetDefaulter, err := mapiwebhooks.NewMachineSetDefaulter() - if err != nil { - log.Fatal(err) - } + machineSetDefaulter := mapiwebhooks.NewMachineSetDefaulter(defaulterConfig) machineSetValidator, err := mapiwebhooks.NewMachineSetValidator(mgr.GetClient(), defaultMutableGate) if err != nil { diff --git a/pkg/webhooks/machine_webhook.go b/pkg/webhooks/machine_webhook.go index 578ba5326..9cbf599ad 100644 --- a/pkg/webhooks/machine_webhook.go +++ b/pkg/webhooks/machine_webhook.go @@ -351,6 +351,44 @@ func getDNS() (*osconfigv1.DNS, error) { return dns, nil } +func resolveGCPBootImage(c client.Client) string { + archSuffix := "x86-64" + if arch == ARM64 { + archSuffix = "aarch64" + } + + machineList := &machinev1beta1.MachineList{} + if err := c.List(context.Background(), machineList, + client.InNamespace(defaultSecretNamespace), + client.MatchingLabels{machineRoleLabel: "worker"}, + ); err != nil { + klog.V(3).Infof("Failed to list worker machines for GCP boot image resolution: %v", err) + return "" + } + + for _, machine := range machineList.Items { + if machine.Spec.ProviderSpec.Value == nil { + continue + } + providerSpec := &machinev1beta1.GCPMachineProviderSpec{} + if err := json.Unmarshal(machine.Spec.ProviderSpec.Value.Raw, providerSpec); err != nil { + klog.V(4).Infof("Failed to unmarshal providerSpec for machine %s: %v", machine.Name, err) + continue + } + for _, disk := range providerSpec.Disks { + if disk == nil { + continue + } + if disk.Boot && disk.Image != "" && strings.Contains(disk.Image, archSuffix) { + klog.V(3).Infof("Resolved GCP boot image from worker machine %s: %s", machine.Name, disk.Image) + return disk.Image + } + } + } + + return "" +} + type machineAdmissionFn func(m *machinev1beta1.Machine, config *admissionConfig) (bool, []string, field.ErrorList) type admissionConfig struct { @@ -359,6 +397,7 @@ type admissionConfig struct { dnsDisconnected bool client client.Client featureGates featuregate.MutableFeatureGate + gcpBootImage string } type admissionHandler struct { @@ -440,20 +479,50 @@ func getMachineValidatorOperation(platform osconfigv1.PlatformType) machineAdmis } } -// NewDefaulter returns a new machineDefaulterHandler. -func NewMachineDefaulter() (*admission.Webhook, error) { +// ResolveDefaulterConfig resolves shared configuration needed by both Machine +// and MachineSet defaulters. Call once at startup and pass the result to both. +func ResolveDefaulterConfig() (*DefaulterConfig, error) { infra, err := getInfra() if err != nil { return nil, err } - return admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.Machine{}, createMachineDefaulter(infra.Status.PlatformStatus, infra.Status.InfrastructureName)), nil + gcpBootImage := "" + if infra.Status.PlatformStatus != nil && infra.Status.PlatformStatus.Type == osconfigv1.GCPPlatformType { + cfg, err := ctrl.GetConfig() + if err != nil { + return nil, err + } + cl, err := client.New(cfg, client.Options{Scheme: scheme.Scheme}) + if err != nil { + return nil, err + } + gcpBootImage = resolveGCPBootImage(cl) + } + + return &DefaulterConfig{ + PlatformStatus: infra.Status.PlatformStatus, + ClusterID: infra.Status.InfrastructureName, + GCPBootImage: gcpBootImage, + }, nil } -func createMachineDefaulter(platformStatus *osconfigv1.PlatformStatus, clusterID string) *machineDefaulterHandler { +// DefaulterConfig holds shared state for Machine and MachineSet defaulters. +type DefaulterConfig struct { + PlatformStatus *osconfigv1.PlatformStatus + ClusterID string + GCPBootImage string +} + +// NewDefaulter returns a new machineDefaulterHandler. +func NewMachineDefaulter(dc *DefaulterConfig) *admission.Webhook { + return admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.Machine{}, createMachineDefaulter(dc.PlatformStatus, dc.ClusterID, dc.GCPBootImage)) +} + +func createMachineDefaulter(platformStatus *osconfigv1.PlatformStatus, clusterID string, gcpBootImage string) *machineDefaulterHandler { return &machineDefaulterHandler{ admissionHandler: &admissionHandler{ - admissionConfig: &admissionConfig{clusterID: clusterID}, + admissionConfig: &admissionConfig{clusterID: clusterID, gcpBootImage: gcpBootImage}, webhookOperations: getMachineDefaulterOperation(platformStatus), }, } @@ -1332,7 +1401,7 @@ func defaultGCP(m *machinev1beta1.Machine, config *admissionConfig) (bool, []str }) } - providerSpec.Disks = defaultGCPDisks(providerSpec.Disks, config.clusterID) + providerSpec.Disks = defaultGCPDisks(providerSpec.Disks, config.clusterID, config.gcpBootImage) if len(providerSpec.GPUs) != 0 { // In case Count was not set it should default to 1, since there is no valid reason for it to be purposely set to 0. @@ -1366,7 +1435,13 @@ func defaultGCP(m *machinev1beta1.Machine, config *admissionConfig) (bool, []str return true, warnings, nil } -func defaultGCPDisks(disks []*machinev1beta1.GCPDisk, clusterID string) []*machinev1beta1.GCPDisk { +func defaultGCPDisks(disks []*machinev1beta1.GCPDisk, clusterID string, gcpBootImage string) []*machinev1beta1.GCPDisk { + image := gcpBootImage + if image == "" { + image = defaultGCPDiskImage() + klog.Infof("No GCP boot image resolved from worker machines, falling back to default: %s", image) + } + if len(disks) == 0 { return []*machinev1beta1.GCPDisk{ { @@ -1374,7 +1449,7 @@ func defaultGCPDisks(disks []*machinev1beta1.GCPDisk, clusterID string) []*machi Boot: true, SizeGB: defaultGCPDiskSizeGb, Type: defaultGCPDiskType, - Image: defaultGCPDiskImage(), + Image: image, }, } } @@ -1385,7 +1460,7 @@ func defaultGCPDisks(disks []*machinev1beta1.GCPDisk, clusterID string) []*machi } if disk.Boot && disk.Image == "" { - disk.Image = defaultGCPDiskImage() + disk.Image = image } } diff --git a/pkg/webhooks/machine_webhook_test.go b/pkg/webhooks/machine_webhook_test.go index 52b9538cd..12264e0a3 100644 --- a/pkg/webhooks/machine_webhook_test.go +++ b/pkg/webhooks/machine_webhook_test.go @@ -2263,7 +2263,7 @@ func TestMachineCreation(t *testing.T) { if !tc.disconnected { dns.Spec.PublicZone = &osconfigv1.DNSZone{} } - machineDefaulter := admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.Machine{}, createMachineDefaulter(platformStatus, tc.clusterID)) + machineDefaulter := admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.Machine{}, createMachineDefaulter(platformStatus, tc.clusterID, "")) machineValidator := admission.WithValidator[*machinev1beta1.Machine](scheme.Scheme, createMachineValidator(infra, c, dns, gate)) mgr.GetWebhookServer().Register(DefaultMachineMutatingHookPath, &webhook.Admission{Handler: machineDefaulter}) mgr.GetWebhookServer().Register(DefaultMachineValidatingHookPath, &webhook.Admission{Handler: machineValidator}) @@ -3125,7 +3125,7 @@ func TestMachineUpdate(t *testing.T) { PlatformStatus: platformStatus, }, } - machineDefaulter := admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.Machine{}, createMachineDefaulter(platformStatus, tc.clusterID)) + machineDefaulter := admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.Machine{}, createMachineDefaulter(platformStatus, tc.clusterID, "")) machineValidator := admission.WithValidator[*machinev1beta1.Machine](scheme.Scheme, createMachineValidator(infra, c, plainDNS, gate)) mgr.GetWebhookServer().Register(DefaultMachineMutatingHookPath, &webhook.Admission{Handler: machineDefaulter}) mgr.GetWebhookServer().Register(DefaultMachineValidatingHookPath, &webhook.Admission{Handler: machineValidator}) @@ -3684,7 +3684,7 @@ func TestDefaultAWSProviderSpec(t *testing.T) { Region: region, }, } - h := createMachineDefaulter(platformStatus, clusterID) + h := createMachineDefaulter(platformStatus, clusterID, "") for _, tc := range testCases { t.Run(tc.testCase, func(t *testing.T) { @@ -4410,7 +4410,7 @@ func TestDefaultAzureProviderSpec(t *testing.T) { } platformStatus := &osconfigv1.PlatformStatus{Type: osconfigv1.AzurePlatformType} - h := createMachineDefaulter(platformStatus, clusterID) + h := createMachineDefaulter(platformStatus, clusterID, "") for _, tc := range testCases { t.Run(tc.testCase, func(t *testing.T) { defaultProviderSpec := &machinev1beta1.AzureMachineProviderSpec{ @@ -5233,7 +5233,7 @@ func TestDefaultGCPProviderSpec(t *testing.T) { ProjectID: projectID, }, } - h := createMachineDefaulter(platformStatus, clusterID) + h := createMachineDefaulter(platformStatus, clusterID, "") for _, tc := range testCases { defaultProviderSpec := &machinev1beta1.GCPMachineProviderSpec{ @@ -5822,7 +5822,7 @@ func TestDefaultVSphereProviderSpec(t *testing.T) { } platformStatus := &osconfigv1.PlatformStatus{Type: osconfigv1.VSpherePlatformType} - h := createMachineDefaulter(platformStatus, clusterID) + h := createMachineDefaulter(platformStatus, clusterID, "") for _, tc := range testCases { t.Run(tc.testCase, func(t *testing.T) { @@ -5895,7 +5895,7 @@ func TestDefaultNutanixProviderSpec(t *testing.T) { } platformStatus := &osconfigv1.PlatformStatus{Type: osconfigv1.NutanixPlatformType} - h := createMachineDefaulter(platformStatus, clusterID) + h := createMachineDefaulter(platformStatus, clusterID, "") for _, tc := range testCases { t.Run(tc.testCase, func(t *testing.T) { @@ -6505,7 +6505,7 @@ func TestDefaultPowerVSProviderSpec(t *testing.T) { } platformStatus := &osconfigv1.PlatformStatus{Type: osconfigv1.PowerVSPlatformType} - h := createMachineDefaulter(platformStatus, clusterID) + h := createMachineDefaulter(platformStatus, clusterID, "") for _, tc := range testCases { t.Run(tc.testCase, func(t *testing.T) { @@ -6789,3 +6789,287 @@ func TestValidateAzureCapacityReservationGroupID(t *testing.T) { }) } } + +func TestResolveGCPBootImage(t *testing.T) { + matchingSuffix := "x86-64" + mismatchSuffix := "aarch64" + if arch == ARM64 { + matchingSuffix = "aarch64" + mismatchSuffix = "x86-64" + } + + testCases := []struct { + name string + machines []machinev1beta1.Machine + expectedImage string + }{ + { + name: "no worker machines returns empty string", + machines: nil, + expectedImage: "", + }, + { + name: "worker machine with matching arch boot disk returns its image", + machines: []machinev1beta1.Machine{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "worker-0", + Namespace: defaultSecretNamespace, + Labels: map[string]string{ + machineRoleLabel: "worker", + }, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{ + Raw: func() []byte { + ps := &machinev1beta1.GCPMachineProviderSpec{ + Disks: []*machinev1beta1.GCPDisk{ + { + Boot: true, + Image: "projects/rhcos-cloud/global/images/rhcos-10-2-current-gcp-" + matchingSuffix, + }, + }, + } + b, _ := json.Marshal(ps) + return b + }(), + }, + }, + }, + }, + }, + expectedImage: "projects/rhcos-cloud/global/images/rhcos-10-2-current-gcp-" + matchingSuffix, + }, + { + name: "worker machine with no boot disk image returns empty string", + machines: []machinev1beta1.Machine{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "worker-0", + Namespace: defaultSecretNamespace, + Labels: map[string]string{ + machineRoleLabel: "worker", + }, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{ + Raw: func() []byte { + ps := &machinev1beta1.GCPMachineProviderSpec{ + Disks: []*machinev1beta1.GCPDisk{ + {Boot: true, Image: ""}, + }, + } + b, _ := json.Marshal(ps) + return b + }(), + }, + }, + }, + }, + }, + expectedImage: "", + }, + { + name: "master machine is skipped, worker machine is used", + machines: []machinev1beta1.Machine{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "master-0", + Namespace: defaultSecretNamespace, + Labels: map[string]string{ + machineRoleLabel: "master", + }, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{ + Raw: func() []byte { + ps := &machinev1beta1.GCPMachineProviderSpec{ + Disks: []*machinev1beta1.GCPDisk{ + {Boot: true, Image: "projects/rhcos-cloud/global/images/master-image-" + matchingSuffix}, + }, + } + b, _ := json.Marshal(ps) + return b + }(), + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "worker-0", + Namespace: defaultSecretNamespace, + Labels: map[string]string{ + machineRoleLabel: "worker", + }, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{ + Raw: func() []byte { + ps := &machinev1beta1.GCPMachineProviderSpec{ + Disks: []*machinev1beta1.GCPDisk{ + {Boot: true, Image: "projects/rhcos-cloud/global/images/worker-image-" + matchingSuffix}, + }, + } + b, _ := json.Marshal(ps) + return b + }(), + }, + }, + }, + }, + }, + expectedImage: "projects/rhcos-cloud/global/images/worker-image-" + matchingSuffix, + }, + { + name: "worker with wrong arch image is skipped", + machines: []machinev1beta1.Machine{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "worker-0", + Namespace: defaultSecretNamespace, + Labels: map[string]string{ + machineRoleLabel: "worker", + }, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{ + Raw: func() []byte { + ps := &machinev1beta1.GCPMachineProviderSpec{ + Disks: []*machinev1beta1.GCPDisk{ + {Boot: true, Image: "projects/rhcos-cloud/global/images/rhcos-10-2-current-gcp-" + mismatchSuffix}, + }, + } + b, _ := json.Marshal(ps) + return b + }(), + }, + }, + }, + }, + }, + expectedImage: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + objs := make([]client.Object, len(tc.machines)) + for i := range tc.machines { + objs[i] = &tc.machines[i] + } + + c := fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + WithObjects(objs...). + Build() + + result := resolveGCPBootImage(c) + if result != tc.expectedImage { + t.Errorf("expected %q, got %q", tc.expectedImage, result) + } + }) + } +} + +func TestGCPDefaultsWithResolvedBootImage(t *testing.T) { + archSuffix := "x86-64" + if arch == ARM64 { + archSuffix = "aarch64" + } + resolvedImage := "projects/rhcos-cloud/global/images/rhcos-10-2-current-gcp-" + archSuffix + clusterID := "gcp-cluster" + platformStatus := &osconfigv1.PlatformStatus{ + Type: osconfigv1.GCPPlatformType, + GCP: &osconfigv1.GCPPlatformStatus{}, + } + h := createMachineDefaulter(platformStatus, clusterID, resolvedImage) + + providerSpec := &machinev1beta1.GCPMachineProviderSpec{ + Region: "us-central1", + Zone: "us-central1-a", + } + rawBytes, err := json.Marshal(providerSpec) + if err != nil { + t.Fatal(err) + } + + m := &machinev1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultSecretNamespace, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{Raw: rawBytes}, + }, + }, + } + + ok, _, errs := h.webhookOperations(m, h.admissionConfig) + if !ok { + t.Fatalf("expected ok, got errors: %v", errs) + } + + var result machinev1beta1.GCPMachineProviderSpec + if err := json.Unmarshal(m.Spec.ProviderSpec.Value.Raw, &result); err != nil { + t.Fatal(err) + } + + if len(result.Disks) == 0 { + t.Fatal("expected disks to be defaulted") + } + if result.Disks[0].Image != resolvedImage { + t.Errorf("expected disk image %q, got %q", resolvedImage, result.Disks[0].Image) + } +} + +func TestGCPDefaultsFallbackToHardcodedImage(t *testing.T) { + clusterID := "gcp-cluster" + platformStatus := &osconfigv1.PlatformStatus{ + Type: osconfigv1.GCPPlatformType, + GCP: &osconfigv1.GCPPlatformStatus{}, + } + h := createMachineDefaulter(platformStatus, clusterID, "") + + providerSpec := &machinev1beta1.GCPMachineProviderSpec{ + Region: "us-central1", + Zone: "us-central1-a", + } + rawBytes, err := json.Marshal(providerSpec) + if err != nil { + t.Fatal(err) + } + + m := &machinev1beta1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultSecretNamespace, + }, + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{Raw: rawBytes}, + }, + }, + } + + ok, _, errs := h.webhookOperations(m, h.admissionConfig) + if !ok { + t.Fatalf("expected ok, got errors: %v", errs) + } + + var result machinev1beta1.GCPMachineProviderSpec + if err := json.Unmarshal(m.Spec.ProviderSpec.Value.Raw, &result); err != nil { + t.Fatal(err) + } + + if len(result.Disks) == 0 { + t.Fatal("expected disks to be defaulted") + } + if result.Disks[0].Image != defaultGCPDiskImage() { + t.Errorf("expected fallback disk image %q, got %q", defaultGCPDiskImage(), result.Disks[0].Image) + } +} diff --git a/pkg/webhooks/machineset_webhook.go b/pkg/webhooks/machineset_webhook.go index d76652254..87850f4bd 100644 --- a/pkg/webhooks/machineset_webhook.go +++ b/pkg/webhooks/machineset_webhook.go @@ -66,19 +66,14 @@ func createMachineSetValidator(infra *osconfigv1.Infrastructure, client client.C } // NewMachineSetDefaulter returns a new machineSetDefaulterHandler. -func NewMachineSetDefaulter() (*admission.Webhook, error) { - infra, err := getInfra() - if err != nil { - return nil, err - } - - return createMachineSetDefaulter(infra.Status.PlatformStatus, infra.Status.InfrastructureName), nil +func NewMachineSetDefaulter(dc *DefaulterConfig) *admission.Webhook { + return createMachineSetDefaulter(dc.PlatformStatus, dc.ClusterID, dc.GCPBootImage) } -func createMachineSetDefaulter(platformStatus *osconfigv1.PlatformStatus, clusterID string) *admission.Webhook { +func createMachineSetDefaulter(platformStatus *osconfigv1.PlatformStatus, clusterID string, gcpBootImage string) *admission.Webhook { return admission.WithCustomDefaulter(scheme.Scheme, &machinev1beta1.MachineSet{}, &machineSetDefaulterHandler{ admissionHandler: &admissionHandler{ - admissionConfig: &admissionConfig{clusterID: clusterID}, + admissionConfig: &admissionConfig{clusterID: clusterID, gcpBootImage: gcpBootImage}, webhookOperations: getMachineDefaulterOperation(platformStatus), }, }) diff --git a/pkg/webhooks/machineset_webhook_test.go b/pkg/webhooks/machineset_webhook_test.go index db9deb1ce..ba2b478d9 100644 --- a/pkg/webhooks/machineset_webhook_test.go +++ b/pkg/webhooks/machineset_webhook_test.go @@ -2,6 +2,7 @@ package webhooks import ( "context" + "encoding/json" "errors" "fmt" "testing" @@ -517,7 +518,7 @@ func TestMachineSetCreation(t *testing.T) { t.Errorf("Unexpected error setting up feature gates: %v", err) } - machineSetDefaulter := createMachineSetDefaulter(platformStatus, tc.clusterID) + machineSetDefaulter := createMachineSetDefaulter(platformStatus, tc.clusterID, "") machineSetValidator := createMachineSetValidator(infra, c, dns, gate) mgr.GetWebhookServer().Register(DefaultMachineSetMutatingHookPath, &webhook.Admission{Handler: machineSetDefaulter}) mgr.GetWebhookServer().Register(DefaultMachineSetValidatingHookPath, &webhook.Admission{Handler: machineSetValidator}) @@ -1204,7 +1205,7 @@ func TestMachineSetUpdate(t *testing.T) { t.Errorf("Unexpected error setting up feature gates: %v", err) } - machineSetDefaulter := createMachineSetDefaulter(platformStatus, tc.clusterID) + machineSetDefaulter := createMachineSetDefaulter(platformStatus, tc.clusterID, "") machineSetValidator := createMachineSetValidator(infra, c, plainDNS, gate) mgr.GetWebhookServer().Register(DefaultMachineSetMutatingHookPath, &webhook.Admission{Handler: machineSetDefaulter}) mgr.GetWebhookServer().Register(DefaultMachineSetValidatingHookPath, &webhook.Admission{Handler: machineSetValidator}) @@ -1282,3 +1283,120 @@ func TestMachineSetUpdate(t *testing.T) { }) } } + +func TestMachineSetGCPDefaultsWithResolvedBootImage(t *testing.T) { + archSuffix := "x86-64" + if arch == ARM64 { + archSuffix = "aarch64" + } + resolvedImage := "projects/rhcos-cloud/global/images/rhcos-10-2-current-gcp-" + archSuffix + clusterID := "gcp-cluster" + platformStatus := &osconfigv1.PlatformStatus{ + Type: osconfigv1.GCPPlatformType, + GCP: &osconfigv1.GCPPlatformStatus{}, + } + + providerSpec := &machinev1beta1.GCPMachineProviderSpec{ + Region: "us-central1", + Zone: "us-central1-a", + } + rawBytes, err := json.Marshal(providerSpec) + if err != nil { + t.Fatal(err) + } + + ms := &machinev1beta1.MachineSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultSecretNamespace, + }, + Spec: machinev1beta1.MachineSetSpec{ + Template: machinev1beta1.MachineTemplateSpec{ + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &runtime.RawExtension{Raw: rawBytes}, + }, + }, + }, + }, + } + + h := &machineSetDefaulterHandler{ + admissionHandler: &admissionHandler{ + admissionConfig: &admissionConfig{clusterID: clusterID, gcpBootImage: resolvedImage}, + webhookOperations: getMachineDefaulterOperation(platformStatus), + }, + } + + ok, _, errs := h.defaultMachineSet(ms) + if !ok { + t.Fatalf("expected ok, got errors: %v", errs) + } + + var result machinev1beta1.GCPMachineProviderSpec + if err := json.Unmarshal(ms.Spec.Template.Spec.ProviderSpec.Value.Raw, &result); err != nil { + t.Fatal(err) + } + + if len(result.Disks) == 0 { + t.Fatal("expected disks to be defaulted") + } + if result.Disks[0].Image != resolvedImage { + t.Errorf("expected disk image %q, got %q", resolvedImage, result.Disks[0].Image) + } +} + +func TestMachineSetGCPDefaultsFallbackToHardcodedImage(t *testing.T) { + clusterID := "gcp-cluster" + platformStatus := &osconfigv1.PlatformStatus{ + Type: osconfigv1.GCPPlatformType, + GCP: &osconfigv1.GCPPlatformStatus{}, + } + + providerSpec := &machinev1beta1.GCPMachineProviderSpec{ + Region: "us-central1", + Zone: "us-central1-a", + } + rawBytes, err := json.Marshal(providerSpec) + if err != nil { + t.Fatal(err) + } + + ms := &machinev1beta1.MachineSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: defaultSecretNamespace, + }, + Spec: machinev1beta1.MachineSetSpec{ + Template: machinev1beta1.MachineTemplateSpec{ + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &runtime.RawExtension{Raw: rawBytes}, + }, + }, + }, + }, + } + + h := &machineSetDefaulterHandler{ + admissionHandler: &admissionHandler{ + admissionConfig: &admissionConfig{clusterID: clusterID, gcpBootImage: ""}, + webhookOperations: getMachineDefaulterOperation(platformStatus), + }, + } + + ok, _, errs := h.defaultMachineSet(ms) + if !ok { + t.Fatalf("expected ok, got errors: %v", errs) + } + + var result machinev1beta1.GCPMachineProviderSpec + if err := json.Unmarshal(ms.Spec.Template.Spec.ProviderSpec.Value.Raw, &result); err != nil { + t.Fatal(err) + } + + if len(result.Disks) == 0 { + t.Fatal("expected disks to be defaulted") + } + if result.Disks[0].Image != defaultGCPDiskImage() { + t.Errorf("expected fallback disk image %q, got %q", defaultGCPDiskImage(), result.Disks[0].Image) + } +}