Skip to content

Commit 2806c36

Browse files
Merge pull request #536 from openshift-assisted/post-discovery-ign
Add post discovery custom ignition
2 parents 6b21d6c + f255a3b commit 2806c36

6 files changed

Lines changed: 141 additions & 3 deletions

File tree

bootstrap/api/v1alpha1/openshiftassistedconfig_types.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ import (
2323
clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
2424
)
2525

26-
const DiscoveryIgnitionOverrideAnnotation = "openshiftassistedconfig.cluster.x-k8s.io/discovery-ignition-override"
26+
const (
27+
// DiscoveryIgnitionOverrideAnnotation is the annotation key for a JSON ignition config
28+
// applied to the discovery ignition (pre-install boot).
29+
DiscoveryIgnitionOverrideAnnotation = "openshiftassistedconfig.cluster.x-k8s.io/discovery-ignition-override"
30+
// IgnitionOverrideAnnotation is the annotation key for a JSON ignition config (v3.1.0)
31+
// to merge into host.IgnitionConfigOverrides (install-time / post-discovery ignition).
32+
IgnitionOverrideAnnotation = "openshiftassistedconfig.cluster.x-k8s.io/ignition-override"
33+
)
2734

2835
// OpenshiftAssistedConfigSpec defines the desired state of OpenshiftAssistedConfig
2936
type OpenshiftAssistedConfigSpec struct {

bootstrap/api/v1alpha2/openshiftassistedconfig_types.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ import (
2323
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
2424
)
2525

26-
const DiscoveryIgnitionOverrideAnnotation = "openshiftassistedconfig.cluster.x-k8s.io/discovery-ignition-override"
26+
const (
27+
// DiscoveryIgnitionOverrideAnnotation is the annotation key for a JSON ignition config
28+
// applied to the discovery ignition (pre-install boot).
29+
DiscoveryIgnitionOverrideAnnotation = "openshiftassistedconfig.cluster.x-k8s.io/discovery-ignition-override"
30+
// IgnitionOverrideAnnotation is the annotation key for a JSON ignition config (v3.1.0)
31+
// to merge into host.IgnitionConfigOverrides (install-time / post-discovery ignition).
32+
IgnitionOverrideAnnotation = "openshiftassistedconfig.cluster.x-k8s.io/ignition-override"
33+
)
2734

2835
// OpenshiftAssistedConfigSpec defines the desired state of OpenshiftAssistedConfig
2936
type OpenshiftAssistedConfigSpec struct {

bootstrap/internal/controller/agent_controller.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ func (r *AgentReconciler) canApproveAgent(ctx context.Context, agent *aiv1beta1.
138138
return true, nil
139139
}
140140

141+
// getIgnitionConfig builds the install-time (post-discovery) ignition config for the agent:
142+
// node registration (hostname, kubelet labels, provider ID) plus any override from the
143+
// openshiftassistedconfig.cluster.x-k8s.io/ignition-override annotation.
141144
func getIgnitionConfig(config *bootstrapv1alpha2.OpenshiftAssistedConfig) (string, error) {
142145
dynamic, static, err := parseLabels(config.Spec.NodeRegistration.KubeletExtraLabels)
143146
if err != nil {
@@ -198,7 +201,19 @@ fi
198201
opts := ignition.IgnitionOptions{
199202
NodeNameEnvVar: config.Spec.NodeRegistration.Name,
200203
}
201-
return ignition.GetIgnitionConfigOverrides(opts, kubeletCustomLabels)
204+
baseIgnition, err := ignition.GetIgnitionConfigOverrides(opts, kubeletCustomLabels)
205+
if err != nil {
206+
return "", err
207+
}
208+
// Merge in install-time ignition override from annotation (openshiftassistedconfig.cluster.x-k8s.io/ignition-override)
209+
if override, ok := config.GetAnnotations()[bootstrapv1alpha2.IgnitionOverrideAnnotation]; ok && override != "" && json.Valid([]byte(override)) {
210+
merged, mergeErr := ignition.MergeIgnitionConfigStrings(baseIgnition, override)
211+
if mergeErr != nil {
212+
return "", fmt.Errorf("invalid %s annotation: %w", bootstrapv1alpha2.IgnitionOverrideAnnotation, mergeErr)
213+
}
214+
return merged, nil
215+
}
216+
return baseIgnition, nil
202217
}
203218

204219
// parseEnvVarRef parses a value that may be an environment variable reference.

bootstrap/internal/controller/agent_controller_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,70 @@ var _ = Describe("getIgnitionConfig", func() {
473473
Entry("contains space", "$VAR NAME"),
474474
)
475475

476+
Context("ignition-override annotation", func() {
477+
const validOverride = `{"ignition":{"version":"3.1.0"},"storage":{"files":[{"path":"/etc/override-file","contents":{"source":"data:,"},"mode":384}]}}`
478+
479+
It("should merge valid ignition-override annotation into host ignition", func() {
480+
config := &bootstrapv1alpha2.OpenshiftAssistedConfig{
481+
ObjectMeta: metav1.ObjectMeta{
482+
Annotations: map[string]string{
483+
bootstrapv1alpha2.IgnitionOverrideAnnotation: validOverride,
484+
},
485+
},
486+
Spec: bootstrapv1alpha2.OpenshiftAssistedConfigSpec{
487+
NodeRegistration: bootstrapv1alpha2.NodeRegistrationOptions{
488+
KubeletExtraLabels: []string{"zone=east"},
489+
},
490+
},
491+
}
492+
493+
ignitionJSON, err := getIgnitionConfig(config)
494+
Expect(err).NotTo(HaveOccurred())
495+
Expect(ignitionJSON).To(ContainSubstring(`/etc/override-file`))
496+
Expect(ignitionJSON).To(ContainSubstring(`kubelet_custom_labels`))
497+
})
498+
499+
It("should ignore annotation when value is not valid JSON", func() {
500+
config := &bootstrapv1alpha2.OpenshiftAssistedConfig{
501+
ObjectMeta: metav1.ObjectMeta{
502+
Annotations: map[string]string{
503+
bootstrapv1alpha2.IgnitionOverrideAnnotation: `not-json`,
504+
},
505+
},
506+
Spec: bootstrapv1alpha2.OpenshiftAssistedConfigSpec{
507+
NodeRegistration: bootstrapv1alpha2.NodeRegistrationOptions{
508+
KubeletExtraLabels: []string{"zone=east"},
509+
},
510+
},
511+
}
512+
513+
ignitionJSON, err := getIgnitionConfig(config)
514+
Expect(err).NotTo(HaveOccurred())
515+
Expect(ignitionJSON).NotTo(ContainSubstring(`/etc/override-file`))
516+
Expect(ignitionJSON).To(ContainSubstring(`kubelet_custom_labels`))
517+
})
518+
519+
It("should return error when annotation is valid JSON but invalid ignition", func() {
520+
config := &bootstrapv1alpha2.OpenshiftAssistedConfig{
521+
ObjectMeta: metav1.ObjectMeta{
522+
Annotations: map[string]string{
523+
// Unknown ignition version so parse fails
524+
bootstrapv1alpha2.IgnitionOverrideAnnotation: `{"ignition":{"version":"99.0.0"}}`,
525+
},
526+
},
527+
Spec: bootstrapv1alpha2.OpenshiftAssistedConfigSpec{
528+
NodeRegistration: bootstrapv1alpha2.NodeRegistrationOptions{
529+
KubeletExtraLabels: []string{"zone=east"},
530+
},
531+
},
532+
}
533+
534+
_, err := getIgnitionConfig(config)
535+
Expect(err).To(HaveOccurred())
536+
Expect(err.Error()).To(ContainSubstring(bootstrapv1alpha2.IgnitionOverrideAnnotation))
537+
})
538+
})
539+
476540
Context("ProviderID", func() {
477541
It("should write KUBELET_PROVIDERID with static value", func() {
478542
config := &bootstrapv1alpha2.OpenshiftAssistedConfig{

bootstrap/internal/ignition/ignition.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"strings"
77

8+
"github.com/coreos/ignition/v2/config/v3_1"
89
config_types "github.com/coreos/ignition/v2/config/v3_1/types"
910
"github.com/go-logr/logr"
1011
logutil "github.com/openshift-assisted/cluster-api-provider-openshift-assisted/util/log"
@@ -225,3 +226,28 @@ func CreateIgnitionFile(path, user, content string, mode int, overwrite bool) co
225226
},
226227
}
227228
}
229+
230+
// MergeIgnitionConfigStrings merges overrideIgnition into baseIgnition.
231+
// Both arguments must be valid Ignition config JSON (e.g. v3.1.0). The result is base plus
232+
// override merged: the override appends or overrides fields per Ignition merge semantics.
233+
// Returns the merged config as a JSON string. If overrideIgnition is empty, baseIgnition
234+
// is returned unchanged.
235+
func MergeIgnitionConfigStrings(baseIgnition, overrideIgnition string) (string, error) {
236+
if overrideIgnition == "" {
237+
return baseIgnition, nil
238+
}
239+
baseConfig, _, err := v3_1.ParseCompatibleVersion([]byte(baseIgnition))
240+
if err != nil {
241+
return "", err
242+
}
243+
overrideConfig, _, err := v3_1.ParseCompatibleVersion([]byte(overrideIgnition))
244+
if err != nil {
245+
return "", err
246+
}
247+
merged := v3_1.Merge(baseConfig, overrideConfig)
248+
out, err := json.Marshal(merged)
249+
if err != nil {
250+
return "", err
251+
}
252+
return string(out), nil
253+
}

bootstrap/internal/ignition/ignition_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,23 @@ var _ = Describe("Ignition utils", func() {
110110
Expect(foundHostnameScript).To(BeTrue(), "set_hostname script should be present with ${VAR} notation")
111111
})
112112
})
113+
114+
When("merging ignition configs", func() {
115+
const base = `{"ignition":{"version":"3.1.0"},"storage":{"files":[{"path":"/base","contents":{"source":"data:,"},"mode":384}]}}`
116+
const override = `{"ignition":{"version":"3.1.0"},"storage":{"files":[{"path":"/override","contents":{"source":"data:,"},"mode":384}]}}`
117+
118+
It("MergeIgnitionConfigStrings merges override into base", func() {
119+
merged, err := MergeIgnitionConfigStrings(base, override)
120+
Expect(err).NotTo(HaveOccurred())
121+
cfg, _, err := config_31.Parse([]byte(merged))
122+
Expect(err).NotTo(HaveOccurred())
123+
Expect(cfg.Storage.Files).To(HaveLen(2))
124+
})
125+
126+
It("MergeIgnitionConfigStrings returns base when override is empty", func() {
127+
merged, err := MergeIgnitionConfigStrings(base, "")
128+
Expect(err).NotTo(HaveOccurred())
129+
Expect(merged).To(Equal(base))
130+
})
131+
})
113132
})

0 commit comments

Comments
 (0)