Skip to content

Commit b9894e9

Browse files
authored
feat: support verbatim patch values (#25)
1 parent 74a2a4f commit b9894e9

File tree

5 files changed

+119
-13
lines changed

5 files changed

+119
-13
lines changed

.github/workflows/go.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ jobs:
1111
- uses: actions/checkout@v4
1212
- name: Set up Go
1313
uses: actions/setup-go@v5
14+
with:
15+
go-version-file: go.mod
1416
- name: CI
1517
run: make ci

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ type K8sObjectOverlayPatch struct {
9595
// All values are strings but are converted into appropriate type based on schema.
9696
//+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Value",order=2
9797
Value string `json:"value,omitempty"`
98+
// Verbatim value to add, delete or replace.
99+
// Same as Value, however the content is not interpreted as YAML, but treated as literal string instead.
100+
// At least one of Value and Verbatim must be empty.
101+
//+operator-sdk:csv:customresourcedefinitions:type=spec,displayName="Verbatim",order=3
102+
Verbatim string `json:"verbatim,omitempty"`
98103
}
99104

100105
```
@@ -132,8 +137,9 @@ func mapOverlayPatches(patches []*v1alpha1.K8sObjectOverlayPatch) []*types.K8sOb
132137
out := make([]*types.K8sObjectOverlayPatch, len(patches))
133138
for i, p := range patches {
134139
out[i] = &types.K8sObjectOverlayPatch{
135-
Path: p.Path,
136-
Value: p.Value,
140+
Path: p.Path,
141+
Value: p.Value,
142+
Verbatim: p.Verbatim,
137143
}
138144
}
139145
return out
@@ -164,5 +170,7 @@ spec:
164170
- path: data.foo
165171
value: bar
166172
- path: data.baz
167-
value: qux
173+
verbatim: |
174+
qux
175+
separate lines
168176
```

pkg/patch/patch.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ func YAMLManifestPatch(baseYAML string, defaultNamespace string, overlays []*typ
133133
if err != nil {
134134
return "", err
135135
}
136+
for i, overlay := range overlays {
137+
errs = util.AppendErr(errs, validateOverlay(i, overlay))
138+
}
136139

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

181+
func validateOverlay(overlayIndex int, overlay *types.K8sObjectOverlay) error {
182+
var errs util.Errors
183+
for patchIndex, patch := range overlay.Patches {
184+
if patch.Value != "" && patch.Verbatim != "" {
185+
errs = util.AppendErr(errs, fmt.Errorf("value and verbatim cannot be used together in overlay %d patch %d", overlayIndex, patchIndex))
186+
}
187+
}
188+
return errs.ToError()
189+
}
190+
178191
// applyPatches applies the given patches against the given object. It returns the resulting patched YAML if successful,
179192
// or a list of errors otherwise.
180193
func applyPatches(base *object.K8sObject, patches []*types.K8sObjectOverlayPatch) (outYAML string, errs util.Errors) {
@@ -189,25 +202,28 @@ func applyPatches(base *object.K8sObject, patches []*types.K8sObjectOverlayPatch
189202
return "", util.NewErrs(err)
190203
}
191204
for _, p := range patches {
192-
193-
var v = &structpb.Value{}
194-
if err := util.UnmarshalWithJSONPB(p.Value, v, false); err != nil {
195-
errs = util.AppendErr(errs, err)
196-
continue
205+
var value interface{}
206+
if p.Verbatim != "" && p.Value == "" {
207+
value = p.Verbatim
208+
} else {
209+
var v = &structpb.Value{}
210+
if err := util.UnmarshalWithJSONPB(p.Value, v, false); err != nil {
211+
errs = util.AppendErr(errs, err)
212+
continue
213+
}
214+
value = v.AsInterface()
197215
}
198-
199216
if strings.TrimSpace(p.Path) == "" {
200-
scope.V(2).Info("skipping empty path", "value", p.Value)
217+
scope.V(2).Info("skipping empty path", "value", value)
201218
continue
202219
}
203-
scope.Info("applying", "path", p.Path, "value", p.Value)
220+
scope.Info("applying", "path", p.Path, "value", value)
204221
inc, _, err := tpath.GetPathContext(bo, util.PathFromString(p.Path), true)
205222
if err != nil {
206223
errs = util.AppendErr(errs, err)
207224
continue
208225
}
209-
210-
err = tpath.WritePathContext(inc, v.AsInterface(), false)
226+
err = tpath.WritePathContext(inc, value, false)
211227
if err != nil {
212228
errs = util.AppendErr(errs, err)
213229
}

pkg/patch/patch_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package patch
1717
import (
1818
"fmt"
1919
"github.com/stackrox/k8s-overlay-patch/pkg/types"
20+
"github.com/stretchr/testify/assert"
21+
"github.com/stretchr/testify/require"
2022
"gopkg.in/yaml.v3"
2123
"testing"
2224

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

32+
func TestYAMLManifestPatchValidation(t *testing.T) {
33+
_, err := YAMLManifestPatch("", "", []*types.K8sObjectOverlay{
34+
{
35+
Patches: []*types.K8sObjectOverlayPatch{
36+
{
37+
Value: "a",
38+
Verbatim: "b",
39+
},
40+
},
41+
},
42+
})
43+
assert.ErrorContains(t, err, "value and verbatim cannot be used together in overlay 0 patch 0")
44+
}
45+
3046
func TestPatchYAMLManifestSuccess(t *testing.T) {
3147
base := `
3248
apiVersion: apps/v1
@@ -493,6 +509,66 @@ spec:
493509
}
494510
}
495511

512+
func TestPatchYAMLConfigMap(t *testing.T) {
513+
base := `
514+
apiVersion: v1
515+
kind: ConfigMap
516+
metadata:
517+
name: a-cm
518+
namespace: stackrox
519+
data:
520+
postgresql.conf: |
521+
hba_file = '/etc/stackrox.d/config/pg_hba.conf'
522+
foo = "bar"
523+
log_timezone = 'Etc/UTC' # comment
524+
more = true
525+
hba.conf: |-
526+
# PostgreSQL Client Authentication Configuration File
527+
local all all scram-sha-256
528+
`
529+
530+
overlays := `overlays:
531+
- apiVersion: v1
532+
kind: ConfigMap
533+
name: a-cm
534+
patches:
535+
- path: data.postgresql\.conf
536+
verbatim: |
537+
hba_file = '/etc/stackrox.d/config/pg_hba.conf'
538+
foo = "bar"
539+
540+
log_timezone = 'Etc/UTC' # comment
541+
more = false
542+
`
543+
want := `
544+
apiVersion: v1
545+
kind: ConfigMap
546+
metadata:
547+
name: a-cm
548+
namespace: stackrox
549+
data:
550+
postgresql.conf: |
551+
hba_file = '/etc/stackrox.d/config/pg_hba.conf'
552+
foo = "bar"
553+
554+
log_timezone = 'Etc/UTC' # comment
555+
more = false
556+
hba.conf: |-
557+
# PostgreSQL Client Authentication Configuration File
558+
local all all scram-sha-256
559+
`
560+
rc := &KubernetesResourcesSpec{}
561+
if err := yaml.Unmarshal([]byte(overlays), rc); err != nil {
562+
t.Fatalf("yaml.Unmarshal(): got error %s for string:\n%s\n", err, overlays)
563+
}
564+
t.Logf("%+v", rc.Overlays[0].Patches[0].Value)
565+
got, err := YAMLManifestPatch(base, "stackrox", rc.Overlays)
566+
require.NoError(t, err, "YAMLManifestPatch failed")
567+
if !util.IsYAMLEqual(got, want) {
568+
t.Errorf("YAMLManifestPatch(): got:\n%s\n\nwant:\n%s\nDiff:\n%s\n", got, want, util.YAMLDiff(got, want))
569+
}
570+
}
571+
496572
func makeOverlayHeader(path, value string) string {
497573
const (
498574
patchCommon = `overlays:

pkg/types/overlay.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ type K8sObjectOverlayPatch struct {
3939
// For replace, path should reference an existing node.
4040
// All values are strings but are converted into appropriate type based on schema.
4141
Value string `json:"value,omitempty"`
42+
// Verbatim value to add, delete or replace.
43+
// Same as Value, however the content is not interpreted as YAML, but treated as literal string instead.
44+
// At least one of Value and Verbatim must be empty.
45+
Verbatim string `json:"verbatim,omitempty"`
4246
}
4347

4448
type OverlayObject struct {

0 commit comments

Comments
 (0)