diff --git a/go.mod b/go.mod index 7788dec..bd045ab 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/gardener/gardener v1.103.0 github.com/gardener/machine-controller-manager v0.53.1 github.com/go-logr/logr v1.4.2 + github.com/imdario/mergo v0.3.16 github.com/ironcore-dev/controller-utils v0.9.4 github.com/ironcore-dev/vgopath v0.1.7 github.com/onsi/ginkgo/v2 v2.21.0 @@ -30,6 +31,7 @@ require ( k8s.io/utils v0.0.0-20240902221715-702e33fdd3c3 sigs.k8s.io/controller-runtime v0.17.6 sigs.k8s.io/controller-tools v0.14.0 + sigs.k8s.io/yaml v1.4.0 ) require ( @@ -71,7 +73,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -130,5 +131,4 @@ require ( k8s.io/metrics v0.29.8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/hack/api-reference/api.md b/hack/api-reference/api.md index 0718ca4..c0ab137 100644 --- a/hack/api-reference/api.md +++ b/hack/api-reference/api.md @@ -341,6 +341,20 @@ string +secretRef
+ + +Kubernetes core/v1.LocalObjectReference + + + + +(Optional) +

SecretRef is a reference to a secret containing the ignition config.

+ + + + override
bool diff --git a/hack/api-reference/config.md b/hack/api-reference/config.md index 45f76ef..453983f 100644 --- a/hack/api-reference/config.md +++ b/hack/api-reference/config.md @@ -46,7 +46,9 @@ string clientConnection
-invalid type + +Kubernetes v1alpha1.ClientConnectionConfiguration + @@ -72,7 +74,9 @@ ETCD healthCheckConfig
-invalid type + +github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1.HealthCheckConfig + @@ -205,7 +209,9 @@ string capacity
-invalid type + +k8s.io/apimachinery/pkg/api/resource.Quantity + diff --git a/pkg/apis/metal/types_worker.go b/pkg/apis/metal/types_worker.go index d3bd919..e51fb36 100644 --- a/pkg/apis/metal/types_worker.go +++ b/pkg/apis/metal/types_worker.go @@ -4,6 +4,7 @@ package metal import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -47,6 +48,7 @@ type MachineImage struct { // IgnitionConfig contains ignition settings. type IgnitionConfig struct { - Raw string - Override bool + Raw string + SecretRef *corev1.LocalObjectReference + Override bool } diff --git a/pkg/apis/metal/v1alpha1/types_worker.go b/pkg/apis/metal/v1alpha1/types_worker.go index b7711b3..94ce8d3 100644 --- a/pkg/apis/metal/v1alpha1/types_worker.go +++ b/pkg/apis/metal/v1alpha1/types_worker.go @@ -4,6 +4,7 @@ package v1alpha1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -54,6 +55,10 @@ type IgnitionConfig struct { // +optional Raw string `json:"raw,omitempty"` + // SecretRef is a reference to a secret containing the ignition config. + // +optional + SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"` + // Override configures, if ignition keys set by the os-extension are overridden // by extra ignition. // +optional diff --git a/pkg/apis/metal/v1alpha1/zz_generated.conversion.go b/pkg/apis/metal/v1alpha1/zz_generated.conversion.go index f5b56c3..32bb196 100644 --- a/pkg/apis/metal/v1alpha1/zz_generated.conversion.go +++ b/pkg/apis/metal/v1alpha1/zz_generated.conversion.go @@ -12,6 +12,7 @@ import ( unsafe "unsafe" metal "github.com/ironcore-dev/gardener-extension-provider-metal/pkg/apis/metal" + v1 "k8s.io/api/core/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -316,6 +317,7 @@ func Convert_metal_ControlPlaneConfig_To_v1alpha1_ControlPlaneConfig(in *metal.C func autoConvert_v1alpha1_IgnitionConfig_To_metal_IgnitionConfig(in *IgnitionConfig, out *metal.IgnitionConfig, s conversion.Scope) error { out.Raw = in.Raw + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) out.Override = in.Override return nil } @@ -327,6 +329,7 @@ func Convert_v1alpha1_IgnitionConfig_To_metal_IgnitionConfig(in *IgnitionConfig, func autoConvert_metal_IgnitionConfig_To_v1alpha1_IgnitionConfig(in *metal.IgnitionConfig, out *IgnitionConfig, s conversion.Scope) error { out.Raw = in.Raw + out.SecretRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.SecretRef)) out.Override = in.Override return nil } diff --git a/pkg/apis/metal/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/metal/v1alpha1/zz_generated.deepcopy.go index 63e5e01..520f4cd 100644 --- a/pkg/apis/metal/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/metal/v1alpha1/zz_generated.deepcopy.go @@ -9,6 +9,7 @@ package v1alpha1 import ( + v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -171,6 +172,11 @@ func (in *ControlPlaneConfig) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IgnitionConfig) DeepCopyInto(out *IgnitionConfig) { *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(v1.LocalObjectReference) + **out = **in + } return } @@ -397,7 +403,7 @@ func (in *WorkerConfig) DeepCopyInto(out *WorkerConfig) { if in.ExtraIgnition != nil { in, out := &in.ExtraIgnition, &out.ExtraIgnition *out = new(IgnitionConfig) - **out = **in + (*in).DeepCopyInto(*out) } if in.ExtraServerLabels != nil { in, out := &in.ExtraServerLabels, &out.ExtraServerLabels diff --git a/pkg/apis/metal/v1alpha1/zz_generated.defaults.go b/pkg/apis/metal/v1alpha1/zz_generated.defaults.go index d1bc6c9..b3026ac 100644 --- a/pkg/apis/metal/v1alpha1/zz_generated.defaults.go +++ b/pkg/apis/metal/v1alpha1/zz_generated.defaults.go @@ -16,5 +16,16 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&WorkerConfig{}, func(obj interface{}) { SetObjectDefaults_WorkerConfig(obj.(*WorkerConfig)) }) return nil } + +func SetObjectDefaults_WorkerConfig(in *WorkerConfig) { + if in.ExtraIgnition != nil { + if in.ExtraIgnition.SecretRef != nil { + if in.ExtraIgnition.SecretRef.Name == "" { + in.ExtraIgnition.SecretRef.Name = "" + } + } + } +} diff --git a/pkg/apis/metal/zz_generated.deepcopy.go b/pkg/apis/metal/zz_generated.deepcopy.go index 4798ad3..f79938b 100644 --- a/pkg/apis/metal/zz_generated.deepcopy.go +++ b/pkg/apis/metal/zz_generated.deepcopy.go @@ -9,6 +9,7 @@ package metal import ( + v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -171,6 +172,11 @@ func (in *ControlPlaneConfig) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IgnitionConfig) DeepCopyInto(out *IgnitionConfig) { *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(v1.LocalObjectReference) + **out = **in + } return } @@ -397,7 +403,7 @@ func (in *WorkerConfig) DeepCopyInto(out *WorkerConfig) { if in.ExtraIgnition != nil { in, out := &in.ExtraIgnition, &out.ExtraIgnition *out = new(IgnitionConfig) - **out = **in + (*in).DeepCopyInto(*out) } if in.ExtraServerLabels != nil { in, out := &in.ExtraServerLabels, &out.ExtraServerLabels diff --git a/pkg/controller/worker/machines.go b/pkg/controller/worker/machines.go index d9f77ff..8742c15 100644 --- a/pkg/controller/worker/machines.go +++ b/pkg/controller/worker/machines.go @@ -13,11 +13,13 @@ import ( v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants" "github.com/gardener/gardener/pkg/apis/extensions/v1alpha1" machinecontrollerv1alpha1 "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" + "github.com/imdario/mergo" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" metalv1alpha1 "github.com/ironcore-dev/gardener-extension-provider-metal/pkg/apis/metal/v1alpha1" "github.com/ironcore-dev/gardener-extension-provider-metal/pkg/metal" @@ -118,9 +120,14 @@ func (w *workerDelegate) generateMachineClassAndSecrets(ctx context.Context) ([] metal.ImageFieldName: machineImage, metal.ServerLabelsFieldName: serverLabels, } + if workerConfig.ExtraIgnition != nil { - machineClassProviderSpec[metal.IgnitionFieldName] = workerConfig.ExtraIgnition.Raw - machineClassProviderSpec[metal.IgnitionOverrideFieldName] = workerConfig.ExtraIgnition.Override + if mergedIgnition, err := w.mergeIgnitionConfig(ctx, workerConfig); err != nil { + return nil, nil, err + } else if mergedIgnition != "" { + machineClassProviderSpec[metal.IgnitionFieldName] = mergedIgnition + machineClassProviderSpec[metal.IgnitionOverrideFieldName] = workerConfig.ExtraIgnition.Override + } } for zoneIndex, zone := range pool.Zones { @@ -227,3 +234,53 @@ func (w *workerDelegate) getServerLabelsForMachine(machineType string, workerCon } return combinedLabels, nil } + +func (w *workerDelegate) mergeIgnitionConfig(ctx context.Context, workerConfig *metalv1alpha1.WorkerConfig) (string, error) { + rawIgnition := &map[string]interface{}{} + + if workerConfig.ExtraIgnition.Raw != "" { + if err := yaml.Unmarshal([]byte(workerConfig.ExtraIgnition.Raw), rawIgnition); err != nil { + return "", err + } + } + + if workerConfig.ExtraIgnition.SecretRef != nil { + secret := &corev1.Secret{} + secretKey := client.ObjectKey{Namespace: w.worker.Namespace, Name: workerConfig.ExtraIgnition.SecretRef.Name} + if err := w.client.Get(ctx, secretKey, secret); err != nil { + return "", fmt.Errorf("failed to get ignition secret %s: %w", workerConfig.ExtraIgnition.SecretRef, err) + } + + secretContent, ok := secret.Data[metal.IgnitionFieldName] + if !ok { + return "", fmt.Errorf("ignition key not found in secret %s", workerConfig.ExtraIgnition.SecretRef) + } + + ignitionSecret := map[string]interface{}{} + + if err := yaml.Unmarshal(secretContent, &ignitionSecret); err != nil { + return "", err + } + + // append ignition + opt := mergo.WithAppendSlice + + // merge both ignitions + err := mergo.Merge(rawIgnition, ignitionSecret, opt) + if err != nil { + return "", err + } + } + + // avoid converting empty string to an empty map with non-zero length + if len(*rawIgnition) == 0 { + return "", nil + } + + mergedIgnition, err := yaml.Marshal(rawIgnition) + if err != nil { + return "", err + } + + return string(mergedIgnition), nil +} diff --git a/pkg/controller/worker/machines_test.go b/pkg/controller/worker/machines_test.go index 894ea7b..42247be 100644 --- a/pkg/controller/worker/machines_test.go +++ b/pkg/controller/worker/machines_test.go @@ -35,6 +35,15 @@ var _ = Describe("Machines", func() { workerDelegate genericworkeractuator.WorkerDelegate ) + dataYml := map[string]any{ + "a": map[string]any{ + "b": "foo", + "c": "bar", + }, + } + yamlString, err := mapToString(dataYml) + Expect(err).NotTo(HaveOccurred()) + BeforeEach(func(ctx SpecContext) { // TODO: Fix machine pool hashing workerPoolHash, err := worker.WorkerPoolHash(pool, testCluster, nil, nil) @@ -76,7 +85,7 @@ var _ = Describe("Machines", func() { "foo": "bar", "foo1": "bar1", }, - metal.IgnitionFieldName: "abc", + metal.IgnitionFieldName: yamlString, metal.IgnitionOverrideFieldName: true, } diff --git a/pkg/controller/worker/suite_test.go b/pkg/controller/worker/suite_test.go index 310c842..32e68e4 100644 --- a/pkg/controller/worker/suite_test.go +++ b/pkg/controller/worker/suite_test.go @@ -32,6 +32,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/envtest/komega" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/yaml" apiv1alpha1 "github.com/ironcore-dev/gardener-extension-provider-metal/pkg/apis/metal/v1alpha1" ) @@ -115,6 +116,7 @@ var _ = BeforeSuite(func() { func SetupTest() (*corev1.Namespace, *gardener.ChartApplier) { var chartApplier gardener.ChartApplier ns := &corev1.Namespace{} + ign := &corev1.Secret{} BeforeEach(func(ctx SpecContext) { var err error @@ -132,12 +134,44 @@ func SetupTest() (*corev1.Namespace, *gardener.ChartApplier) { volumeName := "test-volume" volumeType := "fast" + dataYml := map[string]any{ + "a": map[string]any{ + "b": "foo", + }, + } + yamlString, err := mapToString(dataYml) + Expect(err).NotTo(HaveOccurred()) + + dataYml2 := map[string]any{ + "a": map[string]any{ + "c": "bar", + }, + } + yamlString2, err := mapToString(dataYml2) + Expect(err).NotTo(HaveOccurred()) + + *ign = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "testign-", + Namespace: ns.Name, + }, + Data: map[string][]byte{ + "ignition": []byte(yamlString2), + }, + } + Expect(k8sClient.Create(ctx, ign)).To(Succeed(), "failed to create test ignition secret") + DeferCleanup(k8sClient.Delete, ign) + workerConfig = &apiv1alpha1.WorkerConfig{ ExtraServerLabels: map[string]string{ "foo1": "bar1", }, + ExtraIgnition: &apiv1alpha1.IgnitionConfig{ - Raw: "abc", + Raw: yamlString, + SecretRef: &corev1.LocalObjectReference{ + Name: ign.Name, + }, Override: true, }, } @@ -251,3 +285,11 @@ func SetupTest() (*corev1.Namespace, *gardener.ChartApplier) { return ns, &chartApplier } + +func mapToString(m map[string]interface{}) (string, error) { + yamlData, err := yaml.Marshal(m) + if err != nil { + return "", err + } + return string(yamlData), nil +}