Skip to content

Commit

Permalink
feat: add support for strategic merge patches
Browse files Browse the repository at this point in the history
This commit implements strategic merge patch by reusing Talos
configpatcher patch handling. Furthermore, a unit test scenario has been
added to ensure its functionality.

Fixes: #1259

Signed-off-by: Ksawery Kuczyński <ksawerykuczynskikk@gmail.com>
Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
ksawio97 authored and smira committed Apr 18, 2024
1 parent 5f9acdf commit b3f0131
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 32 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ This provider's versions are compatible with the following versions of Talos:
| Sidero Provider (v0.5) | ✓ (+) | ✓ (+) |||||| | | | |
| Sidero Provider (v0.6) | | | |||||||||

> Note: Sidero Metal is not compatible with multi-document Talos Linux machine configuration, as it relies on JSON patch to apply configuration patches.
## Support

Join our [Slack](https://slack.dev.talos-systems.io)!

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions app/sidero-controller-manager/api/v1alpha2/server_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,13 @@ type ServerSpec struct {
BMC *BMC `json:"bmc,omitempty"`
ManagementAPI *ManagementAPI `json:"managementApi,omitempty"`
ConfigPatches []ConfigPatches `json:"configPatches,omitempty"`
Accepted bool `json:"accepted"`
Cordoned bool `json:"cordoned,omitempty"`
PXEBootAlways bool `json:"pxeBootAlways,omitempty"`
// StrategicPatches are Talos machine configuration strategic merge patches.
//
// +optional
StrategicPatches []string `json:"strategicPatches,omitempty"`
Accepted bool `json:"accepted"`
Cordoned bool `json:"cordoned,omitempty"`
PXEBootAlways bool `json:"pxeBootAlways,omitempty"`
// BootFromDiskMethod specifies the method to exit iPXE to force boot from disk.
//
// If not set, controller default is used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ type ServerClassSpec struct {
// Set of config patches to apply to the machine configuration to the servers provisioned via this server class.
// +optional
ConfigPatches []ConfigPatches `json:"configPatches,omitempty"`
// Strategic merge patches to apply to the machine configuration to the servers provisioned via this server class.
// +optional
// +k8s:conversion-gen=false
StrategicPatches []string `json:"strategicPatches,omitempty"`
// BootFromDiskMethod specifies the method to exit iPXE to force boot from disk.
//
// If not set, controller default is used.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,12 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
strategicPatches:
description: Strategic merge patches to apply to the machine configuration
to the servers provisioned via this server class.
items:
type: string
type: array
type: object
status:
description: ServerClassStatus defines the observed state of ServerClass.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,12 @@ spec:
If not set, controller default is used.
Valid values: uefi, bios.
type: string
strategicPatches:
description: StrategicPatches are Talos machine configuration strategic
merge patches.
items:
type: string
type: array
required:
- accepted
type: object
Expand Down
76 changes: 76 additions & 0 deletions app/sidero-controller-manager/internal/metadata/fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func fixture() []client.Object {
fixture3,
fixture4,
fixture5,
fixture6,
} {
objects = append(objects, fixture()...)
}
Expand Down Expand Up @@ -217,6 +218,81 @@ cluster: {}
`)
}

// fixture6 creates a server with Server- & ServerClass-level with strategic merge config patches.
func fixture6() []client.Object {
oldConfigPatch := "machine:\n network:\n hostname: invalid6"
newConfigPatch := "machine:\n network:\n hostname: example6"

return []client.Object{
&infrav1.ServerBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "6666-7777-8888",
},
Spec: infrav1.ServerBindingSpec{
MetalMachineRef: corev1.ObjectReference{
Name: "metal-machine-6",
},
ServerClassRef: &corev1.ObjectReference{
Name: "server-class-6",
},
},
},
&infrav1.MetalMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "metal-machine-6",
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "cluster.x-k8s.io/v1beta1",
Kind: "Machine",
Name: "machine-6",
},
},
},
Spec: infrav1.MetalMachineSpec{},
},
&capiv1.Machine{
ObjectMeta: metav1.ObjectMeta{
Name: "machine-6",
},
Spec: capiv1.MachineSpec{
Bootstrap: capiv1.Bootstrap{
DataSecretName: pointer.To("bootstrap6"),
},
},
},
&metalv1.ServerClass{
ObjectMeta: metav1.ObjectMeta{
Name: "server-class-6",
},
Spec: metalv1.ServerClassSpec{
StrategicPatches: []string{oldConfigPatch},
},
},
&metalv1.Server{
ObjectMeta: metav1.ObjectMeta{
Name: "6666-7777-8888",
},
Spec: metalv1.ServerSpec{
StrategicPatches: []string{newConfigPatch},
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "bootstrap6",
},
Data: map[string][]byte{
"value": []byte(`
version: v1alpha1
machine:
kubelet:
extraArgs:
node-labels: foo=bar
`),
},
},
}
}

func fixtureSimple(uuid string, index int, config string) []client.Object {
return []client.Object{
&infrav1.ServerBinding{
Expand Down
74 changes: 48 additions & 26 deletions app/sidero-controller-manager/internal/metadata/metadata_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,30 +195,24 @@ func (m *metadataConfigs) FetchConfig(w http.ResponseWriter, r *http.Request) {
}
}

// Handle patches added to serverclass object
if serverClassObj != nil && len(serverClassObj.Spec.ConfigPatches) > 0 {
decodedData, ewc = patchConfigs(decodedData, serverClassObj.Spec.ConfigPatches)
if ewc.errorObj != nil {
throwError(
w,
ewc,
)
decodedData, ewc = handlePatches(decodedData, serverClassObj.Spec.ConfigPatches, serverClassObj.Spec.StrategicPatches)
if ewc.errorObj != nil {
throwError(
w,
ewc,
)

return
}
return
}

// Handle patches added to server object
if len(serverObj.Spec.ConfigPatches) > 0 {
decodedData, ewc = patchConfigs(decodedData, serverObj.Spec.ConfigPatches)
if ewc.errorObj != nil {
throwError(
w,
ewc,
)
decodedData, ewc = handlePatches(decodedData, serverObj.Spec.ConfigPatches, serverObj.Spec.StrategicPatches)
if ewc.errorObj != nil {
throwError(
w,
ewc,
)

return
}
return
}

// Append or add a node label to kubelet extra args.
Expand All @@ -242,21 +236,49 @@ func (m *metadataConfigs) FetchConfig(w http.ResponseWriter, r *http.Request) {
log.Printf("successfully returned metadata for %q", uuid)
}

// patchConfigs is responsible for applying a set of configPatches to the bootstrap data.
func patchConfigs(decodedData []byte, patches []metalv1.ConfigPatches) ([]byte, errorWithCode) {
// this function is responsible for applying rfc6902 and a strategic merge patch to bootstrap data.
func handlePatches(decodedData []byte, patches []metalv1.ConfigPatches, strategicPatches []string) ([]byte, errorWithCode) {
var ewc errorWithCode

// Handle rfc6902 patches
if len(patches) > 0 {
decodedData, ewc = patchRFC6902Configs(decodedData, patches)
if ewc.errorObj != nil {
return decodedData, ewc
}
}

// Handle strategic merge patch
for _, strategicPatch := range strategicPatches {
decodedData, ewc = patchConfig(decodedData, []byte(strategicPatch))
if ewc.errorObj != nil {
return decodedData, ewc
}
}

return decodedData, errorWithCode{}
}

// patchRFC6902Configs is responsible for applying rfc6902 configPatches to the bootstrap data.
func patchRFC6902Configs(decodedData []byte, patches []metalv1.ConfigPatches) ([]byte, errorWithCode) {
marshalledPatches, err := json.Marshal(patches)
if err != nil {
return nil, errorWithCode{http.StatusInternalServerError, fmt.Errorf("failure marshaling config patches from server: %s", err)}
}

patch, err := configpatcher.LoadPatch(marshalledPatches)
return patchConfig(decodedData, marshalledPatches)
}

// patchConfig is responsible for applying marshaled rfc6902 configPatches or a strategic merge patch to the bootstrap data.
func patchConfig(decodedData []byte, patches []byte) ([]byte, errorWithCode) {
patch, err := configpatcher.LoadPatch(patches)
if err != nil {
return nil, errorWithCode{http.StatusInternalServerError, fmt.Errorf("failure loading rfc6902 patches from server: %s", err)}
return nil, errorWithCode{http.StatusInternalServerError, fmt.Errorf("failure loading patches from server: %s", err)}
}

patched, err := configpatcher.Apply(configpatcher.WithBytes(decodedData), []configpatcher.Patch{patch})
if err != nil {
return nil, errorWithCode{http.StatusInternalServerError, fmt.Errorf("failure applying rfc6902 patches to machine config: %s", err)}
return nil, errorWithCode{http.StatusInternalServerError, fmt.Errorf("failure applying patches to machine config: %s", err)}
}

result, err := patched.Bytes()
Expand Down Expand Up @@ -351,7 +373,7 @@ func labelNodes(decodedData []byte, serverName string) ([]byte, errorWithCode) {

patch.Value.Raw = value

return patchConfigs(decodedData, []metalv1.ConfigPatches{patch})
return patchRFC6902Configs(decodedData, []metalv1.ConfigPatches{patch})
default:
return nil, errorWithCode{http.StatusInternalServerError, fmt.Errorf("unknown config type")}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ func TestMetadataService(t *testing.T) {
expectedCode: http.StatusOK,
expectedBody: "cluster: {}\nmachine:\n kubelet:\n extraArgs:\n node-labels: metal.sidero.dev/uuid=5555-6666-7777\nversion: v1alpha1\n",
},
{
name: "server and server class as strategic merge patch",
path: "/configdata?uuid=6666-7777-8888",

expectedCode: http.StatusOK,
expectedBody: "cluster: null\nmachine:\n certSANs: []\n kubelet:\n extraArgs:\n node-labels: foo=bar,metal.sidero.dev/uuid=6666-7777-8888\n network:\n hostname: example6\n token: \"\"\n type: \"\"\nversion: v1alpha1\n",
},
} {
test := test

Expand Down
8 changes: 7 additions & 1 deletion hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ github_repo = "talos-systems/sidero"
match_deps = "^github.com/(talos-systems/[a-zA-Z0-9-]+)$"

# previous release
previous = "v0.6.2"
previous = "v0.6.3"

pre_release = false

preface = """\
"""

[notes]

[notes.patches]
title = "Patches"
description = """\
Sidero Metal now supports Talos Linux machine configuration strategic merge patches via 'strategicPatches' field on the `Server` and `ServerClass` CRDs.
"""

0 comments on commit b3f0131

Please sign in to comment.