Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement "$patch: delete" logic #9275

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ Extra announced endpoints can be added using the [`KubespanEndpointsConfig` docu
title = "Machine Configuration via Kernel Command Line"
description = """\
Talos Linux supports supplying zstd-compressed, base64-encoded machine configuration small documents via the kernel command line parameter `talos.config.inline`.
"""

[notes.patch-delete]
title = "Removing parts of the configuration using `$patch: delete` syntax"
description = """\
Talos Linux now supports removing parts of the configuration using the `$patch: delete` syntax similar to the kubernetes.
More information can be found [here](https://www.talos.dev/v1.8/talos-guides/configuration/patching/#strategic-merge-patches).
"""

[make_deps]
Expand Down
38 changes: 34 additions & 4 deletions pkg/machinery/config/configloader/configloader.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@ import (
var ErrNoConfig = errors.New("config not found")

// newConfig initializes and returns a Configurator.
func newConfig(r io.Reader) (config config.Provider, err error) {
func newConfig(r io.Reader, opt ...Opt) (config config.Provider, err error) {
var opts Opts

for _, o := range opt {
o(&opts)
}

dec := decoder.NewDecoder()

var buf bytes.Buffer

// preserve the original contents
r = io.TeeReader(r, &buf)

manifests, err := dec.Decode(r)
manifests, err := dec.Decode(r, opts.allowPatchDelete)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -59,6 +65,30 @@ func NewFromStdin() (config.Provider, error) {
}

// NewFromBytes will take a byteslice and attempt to parse a config file from it.
func NewFromBytes(source []byte) (config.Provider, error) {
return newConfig(bytes.NewReader(source))
func NewFromBytes(source []byte, o ...Opt) (config.Provider, error) {
return newConfig(bytes.NewReader(source), o...)
}

// Opts represents the options for the config loader.
type Opts struct {
allowPatchDelete bool
}

// Opt is a functional option for the config loader.
type Opt func(*Opts)

// WithAllowPatchDelete allows the loader to parse patch delete operations.
func WithAllowPatchDelete() Opt {
return func(o *Opts) {
o.allowPatchDelete = true
}
}

// Selector represents a delete selector for a document.
type Selector = decoder.Selector

// ErrZeroedDocument is returned when the document is empty after applying the delete selector.
var ErrZeroedDocument = decoder.ErrZeroedDocument

// ErrLookupFailed is returned when the lookup failed.
var ErrLookupFailed = decoder.ErrLookupFailed
41 changes: 16 additions & 25 deletions pkg/machinery/config/configloader/internal/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const (
type Decoder struct{}

// Decode decodes all known manifests.
func (d *Decoder) Decode(r io.Reader) ([]config.Document, error) {
return parse(r)
func (d *Decoder) Decode(r io.Reader, allowPatchDelete bool) ([]config.Document, error) {
return parse(r, allowPatchDelete)
}

// NewDecoder initializes and returns a `Decoder`.
Expand All @@ -54,7 +54,8 @@ type documentID struct {
Name string
}

func parse(r io.Reader) (decoded []config.Document, err error) {
//nolint:gocyclo
func parse(r io.Reader, allowPatchDelete bool) (decoded []config.Document, err error) {
// Recover from yaml.v3 panics because we rely on machine configuration loading _a lot_.
defer func() {
if p := recover(); p != nil {
Expand All @@ -71,7 +72,7 @@ func parse(r io.Reader) (decoded []config.Document, err error) {
knownDocuments := map[documentID]struct{}{}

// Iterate through all defined documents.
for {
for i := 0; ; i++ {
var manifests yaml.Node

if err = dec.Decode(&manifests); err != nil {
Expand All @@ -86,6 +87,17 @@ func parse(r io.Reader) (decoded []config.Document, err error) {
return nil, errors.New("expected a document")
}

if allowPatchDelete {
decoded, err = AppendDeletesTo(&manifests, decoded, i)
if err != nil {
return nil, err
}

if manifests.IsZero() {
continue
}
}

for _, manifest := range manifests.Content {
id := documentID{
APIVersion: findValue(manifest, ManifestAPIVersionKey, false),
Expand Down Expand Up @@ -167,24 +179,3 @@ func decode(manifest *yaml.Node) (target config.Document, err error) {

return target, nil
}

func findValue(node *yaml.Node, key string, required bool) string {
if node.Kind != yaml.MappingNode {
panic(errors.New("expected a mapping node"))
}

for i := 0; i < len(node.Content)-1; i += 2 {
keyNode := node.Content[i]
val := node.Content[i+1]

if keyNode.Kind == yaml.ScalarNode && keyNode.Value == key {
return val.Value
}
}

if required {
panic(fmt.Errorf("missing '%s'", key))
}

return ""
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ config:
t.Parallel()

d := decoder.NewDecoder()
actual, err := d.Decode(bytes.NewReader(tt.source))
actual, err := d.Decode(bytes.NewReader(tt.source), false)

if tt.expected != nil {
assert.Equal(t, tt.expected, actual)
Expand Down Expand Up @@ -340,7 +340,7 @@ func TestDecoderV1Alpha1Config(t *testing.T) {
require.NoError(t, err)

d := decoder.NewDecoder()
_, err = d.Decode(bytes.NewReader(contents))
_, err = d.Decode(bytes.NewReader(contents), false)

assert.NoError(t, err)
})
Expand All @@ -354,7 +354,7 @@ func TestDoubleV1Alpha1(t *testing.T) {
contents := must.Value(files.ReadFile("v1alpha1.yaml"))(t)

d := decoder.NewDecoder()
_, err := d.Decode(bytes.NewReader(contents))
_, err := d.Decode(bytes.NewReader(contents), false)
require.Error(t, err)
require.ErrorContains(t, err, "not allowed")
}
Expand All @@ -367,7 +367,7 @@ func BenchmarkDecoderV1Alpha1Config(b *testing.B) {

for range b.N {
d := decoder.NewDecoder()
_, err = d.Decode(bytes.NewReader(contents))
_, err = d.Decode(bytes.NewReader(contents), false)

assert.NoError(b, err)
}
Expand Down
Loading
Loading