Skip to content

Commit

Permalink
feat: support verbatim patch values
Browse files Browse the repository at this point in the history
  • Loading branch information
porridge committed Feb 18, 2025
1 parent 74a2a4f commit 0124fbf
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 13 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ type K8sObjectOverlayPatch struct {
// All values are strings but are converted into appropriate type based on schema.
//+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Value",order=2
Value string `json:"value,omitempty"`
// Verbatim value to add, delete or replace.
// Same as Value, however but the content is not interpreted as YAML, but treated as literal string instead.
// At least one of Value and Verbatim must be empty.
Verbatim string `json:"verbatim,omitempty"`
}

```
Expand Down Expand Up @@ -132,8 +136,9 @@ func mapOverlayPatches(patches []*v1alpha1.K8sObjectOverlayPatch) []*types.K8sOb
out := make([]*types.K8sObjectOverlayPatch, len(patches))
for i, p := range patches {
out[i] = &types.K8sObjectOverlayPatch{
Path: p.Path,
Value: p.Value,
Path: p.Path,
Value: p.Value,
Verbatim: p.Verbatim,
}
}
return out
Expand Down Expand Up @@ -164,5 +169,7 @@ spec:
- path: data.foo
value: bar
- path: data.baz
value: qux
verbatim: |
qux
separate lines
```
36 changes: 26 additions & 10 deletions pkg/patch/patch.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ func YAMLManifestPatch(baseYAML string, defaultNamespace string, overlays []*typ
if err != nil {
return "", err
}
for i, overlay := range overlays {
errs = util.AppendErr(errs, validateOverlay(i, overlay))
}

matches := make(map[*types.K8sObjectOverlay]object.K8sObjects)
// Try to apply the defined overlays.
Expand Down Expand Up @@ -175,6 +178,16 @@ func YAMLManifestPatch(baseYAML string, defaultNamespace string, overlays []*typ
return ret.String(), errs.ToError()
}

func validateOverlay(overlayIndex int, overlay *types.K8sObjectOverlay) error {
var errs util.Errors
for patchIndex, patch := range overlay.Patches {
if patch.Value != "" && patch.Verbatim != "" {
errs = util.AppendErr(errs, fmt.Errorf("value and verbatim cannot be used together in overlay %d patch %d", overlayIndex, patchIndex))
}
}
return errs.ToError()
}

// applyPatches applies the given patches against the given object. It returns the resulting patched YAML if successful,
// or a list of errors otherwise.
func applyPatches(base *object.K8sObject, patches []*types.K8sObjectOverlayPatch) (outYAML string, errs util.Errors) {
Expand All @@ -189,25 +202,28 @@ func applyPatches(base *object.K8sObject, patches []*types.K8sObjectOverlayPatch
return "", util.NewErrs(err)
}
for _, p := range patches {

var v = &structpb.Value{}
if err := util.UnmarshalWithJSONPB(p.Value, v, false); err != nil {
errs = util.AppendErr(errs, err)
continue
var value interface{}
if p.Verbatim != "" && p.Value == "" {
value = p.Verbatim
} else {
var v = &structpb.Value{}
if err := util.UnmarshalWithJSONPB(p.Value, v, false); err != nil {
errs = util.AppendErr(errs, err)
continue
}
value = v.AsInterface()
}

if strings.TrimSpace(p.Path) == "" {
scope.V(2).Info("skipping empty path", "value", p.Value)
scope.V(2).Info("skipping empty path", "value", value)
continue
}
scope.Info("applying", "path", p.Path, "value", p.Value)
scope.Info("applying", "path", p.Path, "value", value)
inc, _, err := tpath.GetPathContext(bo, util.PathFromString(p.Path), true)
if err != nil {
errs = util.AppendErr(errs, err)
continue
}

err = tpath.WritePathContext(inc, v.AsInterface(), false)
err = tpath.WritePathContext(inc, value, false)
if err != nil {
errs = util.AppendErr(errs, err)
}
Expand Down
76 changes: 76 additions & 0 deletions pkg/patch/patch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ package patch
import (
"fmt"
"github.com/stackrox/k8s-overlay-patch/pkg/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"testing"

Expand All @@ -27,6 +29,20 @@ type KubernetesResourcesSpec struct {
Overlays []*types.K8sObjectOverlay `json:"overlays"`
}

func TestYAMLManifestPatchValidation(t *testing.T) {
_, err := YAMLManifestPatch("", "", []*types.K8sObjectOverlay{
{
Patches: []*types.K8sObjectOverlayPatch{
{
Value: "a",
Verbatim: "b",
},
},
},
})
assert.ErrorContains(t, err, "value and verbatim cannot be used together in overlay 0 patch 0")
}

func TestPatchYAMLManifestSuccess(t *testing.T) {
base := `
apiVersion: apps/v1
Expand Down Expand Up @@ -493,6 +509,66 @@ spec:
}
}

func TestPatchYAMLConfigMap(t *testing.T) {
base := `
apiVersion: v1
kind: ConfigMap
metadata:
name: a-cm
namespace: stackrox
data:
postgresql.conf: |
hba_file = '/etc/stackrox.d/config/pg_hba.conf'
foo = "bar"
log_timezone = 'Etc/UTC' # comment
more = true
hba.conf: |-
# PostgreSQL Client Authentication Configuration File
local all all scram-sha-256
`

overlays := `overlays:
- apiVersion: v1
kind: ConfigMap
name: a-cm
patches:
- path: data.postgresql\.conf
verbatim: |
hba_file = '/etc/stackrox.d/config/pg_hba.conf'
foo = "bar"
log_timezone = 'Etc/UTC' # comment
more = false
`
want := `
apiVersion: v1
kind: ConfigMap
metadata:
name: a-cm
namespace: stackrox
data:
postgresql.conf: |
hba_file = '/etc/stackrox.d/config/pg_hba.conf'
foo = "bar"
log_timezone = 'Etc/UTC' # comment
more = false
hba.conf: |-
# PostgreSQL Client Authentication Configuration File
local all all scram-sha-256
`
rc := &KubernetesResourcesSpec{}
if err := yaml.Unmarshal([]byte(overlays), rc); err != nil {
t.Fatalf("yaml.Unmarshal(): got error %s for string:\n%s\n", err, overlays)
}
t.Logf("%+v", rc.Overlays[0].Patches[0].Value)
got, err := YAMLManifestPatch(base, "stackrox", rc.Overlays)
require.NoError(t, err, "YAMLManifestPatch failed")
if !util.IsYAMLEqual(got, want) {
t.Errorf("YAMLManifestPatch(): got:\n%s\n\nwant:\n%s\nDiff:\n%s\n", got, want, util.YAMLDiff(got, want))
}
}

func makeOverlayHeader(path, value string) string {
const (
patchCommon = `overlays:
Expand Down
4 changes: 4 additions & 0 deletions pkg/types/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ type K8sObjectOverlayPatch struct {
// For replace, path should reference an existing node.
// All values are strings but are converted into appropriate type based on schema.
Value string `json:"value,omitempty"`
// Verbatim value to add, delete or replace.
// Same as Value, however but the content is not interpreted as YAML, but treated as literal string instead.
// At least one of Value and Verbatim must be empty.
Verbatim string `json:"verbatim,omitempty"`
}

type OverlayObject struct {
Expand Down

0 comments on commit 0124fbf

Please sign in to comment.