Skip to content

Commit adc8d89

Browse files
authored
feat: Reintroduce manifest parser (#101)
1 parent 3f2db8a commit adc8d89

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

pkg/k8s/parser/parser.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package parser
5+
6+
import (
7+
"bytes"
8+
"io"
9+
"strings"
10+
11+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
12+
"k8s.io/apimachinery/pkg/util/yaml"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
)
15+
16+
func StringsToObjects(strs ...string) ([]client.Object, error) {
17+
rs := make([]io.Reader, 0, len(strs))
18+
for _, s := range strs {
19+
rs = append(rs, strings.NewReader(s))
20+
}
21+
22+
return DecodeReadersToObjects(rs...)
23+
}
24+
25+
func BytesToObjects(bs ...[]byte) ([]client.Object, error) {
26+
rs := make([]io.Reader, 0, len(bs))
27+
for _, b := range bs {
28+
rs = append(rs, bytes.NewReader(b))
29+
}
30+
31+
return DecodeReadersToObjects(rs...)
32+
}
33+
34+
func DecodeReadersToObjects(rs ...io.Reader) ([]client.Object, error) {
35+
var objs []client.Object
36+
for _, r := range rs {
37+
decoder := yaml.NewYAMLOrJSONDecoder(r, 1024)
38+
for {
39+
u := &unstructured.Unstructured{}
40+
err := decoder.Decode(u)
41+
if err != nil {
42+
if err == io.EOF {
43+
break
44+
}
45+
return nil, err
46+
}
47+
objs = append(objs, u)
48+
}
49+
}
50+
return objs, nil
51+
}

pkg/k8s/parser/parser_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2023 D2iQ, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package parser_test
5+
6+
import (
7+
"io"
8+
"strings"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
15+
16+
"github.com/d2iq-labs/capi-runtime-extensions/pkg/k8s/parser"
17+
)
18+
19+
func dummyUnstructured(apiVersion, kind, namespace, name string) *unstructured.Unstructured {
20+
u := &unstructured.Unstructured{}
21+
u.SetAPIVersion(apiVersion)
22+
u.SetKind(kind)
23+
u.SetNamespace(namespace)
24+
u.SetName(name)
25+
return u
26+
}
27+
28+
var tests = []struct {
29+
name string
30+
inputs []string
31+
want []client.Object
32+
wantErr string
33+
}{{
34+
name: "empty input, nil slice output",
35+
inputs: nil,
36+
}, {
37+
name: "single empty input, nil slice output",
38+
inputs: []string{``},
39+
}, {
40+
name: "malformed YAML input",
41+
inputs: []string{`a: b
42+
b: d
43+
c`},
44+
wantErr: "could not find expected ':'",
45+
}, {
46+
name: "malformed JSON input",
47+
inputs: []string{`{"a": "b", "c": "d"`},
48+
wantErr: "unexpected EOF",
49+
}, {
50+
name: "valid YAML, but not valid k8s object",
51+
inputs: []string{`a: b
52+
c: d`},
53+
wantErr: "Object 'Kind' is missing in",
54+
}, {
55+
name: "valid JSON, but not valid k8s object",
56+
inputs: []string{`{"a": "b", "c": "d"}`},
57+
wantErr: "Object 'Kind' is missing in",
58+
}, {
59+
name: "valid single k8s object YAML",
60+
inputs: []string{`apiVersion: some.api/v1
61+
kind: Something
62+
metadata:
63+
namespace: a
64+
name: b
65+
`},
66+
want: []client.Object{dummyUnstructured("some.api/v1", "Something", "a", "b")},
67+
}, {
68+
name: "valid multiple k8s object YAML",
69+
inputs: []string{`apiVersion: some.api/v1
70+
kind: Something
71+
metadata:
72+
namespace: a
73+
name: b
74+
---
75+
apiVersion: another.api/v1
76+
kind: SomethingElse
77+
metadata:
78+
namespace: c
79+
name: d
80+
`},
81+
want: []client.Object{
82+
dummyUnstructured("some.api/v1", "Something", "a", "b"),
83+
dummyUnstructured("another.api/v1", "SomethingElse", "c", "d"),
84+
},
85+
}, {
86+
name: "valid multiple k8s object YAML including empty docs",
87+
inputs: []string{`apiVersion: some.api/v1
88+
kind: Something
89+
metadata:
90+
namespace: a
91+
name: b
92+
---
93+
---
94+
apiVersion: another.api/v1
95+
kind: SomethingElse
96+
metadata:
97+
namespace: c
98+
name: d
99+
---
100+
`},
101+
want: []client.Object{
102+
dummyUnstructured("some.api/v1", "Something", "a", "b"),
103+
dummyUnstructured("another.api/v1", "SomethingElse", "c", "d"),
104+
},
105+
}, {
106+
name: "valid multiple k8s object YAML across multiple inputs",
107+
inputs: []string{
108+
`apiVersion: some.api/v1
109+
kind: Something
110+
metadata:
111+
namespace: a
112+
name: b
113+
---
114+
apiVersion: another.api/v1
115+
kind: SomethingElse
116+
metadata:
117+
namespace: c
118+
name: d`,
119+
`apiVersion: some.api/v2
120+
kind: Something2
121+
metadata:
122+
namespace: e
123+
name: f
124+
---
125+
apiVersion: another.api/v2
126+
kind: SomethingElse2
127+
metadata:
128+
namespace: g
129+
name: h`,
130+
},
131+
want: []client.Object{
132+
dummyUnstructured("some.api/v1", "Something", "a", "b"),
133+
dummyUnstructured("another.api/v1", "SomethingElse", "c", "d"),
134+
dummyUnstructured("some.api/v2", "Something2", "e", "f"),
135+
dummyUnstructured("another.api/v2", "SomethingElse2", "g", "h"),
136+
},
137+
}}
138+
139+
func runTests(t *testing.T, fn func([]string) ([]client.Object, error)) {
140+
t.Helper()
141+
t.Parallel()
142+
for idx := range tests {
143+
tt := tests[idx]
144+
t.Run(tt.name, func(t *testing.T) {
145+
t.Parallel()
146+
got, err := fn(tt.inputs)
147+
if tt.wantErr != "" {
148+
require.ErrorContains(t, err, tt.wantErr)
149+
} else {
150+
require.NoError(t, err)
151+
}
152+
assert.Equal(t, tt.want, got)
153+
})
154+
}
155+
}
156+
157+
func TestDecodeReaderToObjects(t *testing.T) {
158+
runTests(t, func(inputs []string) ([]client.Object, error) {
159+
readers := make([]io.Reader, 0, len(inputs))
160+
for _, s := range inputs {
161+
readers = append(readers, strings.NewReader(s))
162+
}
163+
return parser.DecodeReadersToObjects(readers...)
164+
})
165+
}
166+
167+
func TestBytesToObjects(t *testing.T) {
168+
runTests(t, func(inputs []string) ([]client.Object, error) {
169+
bs := make([][]byte, 0, len(inputs))
170+
for _, s := range inputs {
171+
bs = append(bs, []byte(s))
172+
}
173+
return parser.BytesToObjects(bs...)
174+
})
175+
}
176+
177+
func TestStringsToObjects(t *testing.T) {
178+
runTests(t, func(inputs []string) ([]client.Object, error) {
179+
return parser.StringsToObjects(inputs...)
180+
})
181+
}

0 commit comments

Comments
 (0)