1 package chariot
2
3 import (
4 "crypto/md5"
5 crypto "crypto/rand"
6 "fmt"
7 "math/rand"
8 "regexp"
9 "testing"
10
11 "gopkg.in/yaml.v2"
12 )
13
14
15 func randomChariotYamlObject() []byte {
16
17 var groupVersion = "clusterregistry.k8s.io/v1alpha1"
18
19
20 var kinds = [3]string{"Cluster", "Namespace", "Tenant"}
21 var kind = kinds[rand.Int()%len(kinds)]
22
23
24 var nameBytes [16]byte
25
26 crypto.Read(nameBytes[:])
27 var name = fmt.Sprintf("%x", nameBytes)
28
29
30 var namespace string
31 if rand.Int()%2 == 0 {
32 var namespaces = [5]string{"bulldozer", "ci", "godoc", "jack-bot", "policy-bot"}
33 namespace = namespaces[rand.Int()%len(namespaces)]
34 }
35
36
37 var labels map[string]string
38 if rand.Int()%2 == 0 {
39 var clusterNameBytes [4]byte
40 var clusterTypeBytes [4]byte
41 var fleetNameBytes [4]byte
42 crypto.Read(clusterNameBytes[:])
43 crypto.Read(clusterTypeBytes[:])
44 crypto.Read(fleetNameBytes[:])
45 labels = map[string]string{
46 "cluster.edge.ncr.com": fmt.Sprintf("%x", clusterNameBytes),
47 "cluster.edge.ncr.com/type": fmt.Sprintf("%x", clusterTypeBytes),
48 "fleet.edge.ncr.com": fmt.Sprintf("%x", fleetNameBytes),
49 }
50 }
51
52
53 var req struct {
54 APIVersion string `yaml:"apiVersion"`
55 Kind string `yaml:"kind"`
56 Metadata struct {
57 Name string `yaml:"name"`
58 Namespace string `yaml:"namespace"`
59 Labels map[string]string `yaml:"labels"`
60 } `yaml:"metadata"`
61 }
62 req.APIVersion = groupVersion
63 req.Kind = kind
64 req.Metadata.Name = name
65 req.Metadata.Namespace = namespace
66 req.Metadata.Labels = labels
67
68 y, _ := yaml.Marshal(req)
69 return y
70 }
71
72
73 func TestRequestDirOption(t *testing.T) {
74 var req = &Request{
75 Operation: "CREATE",
76 Banner: "my-banner",
77 Cluster: "",
78 Dir: "",
79 Owner: "test",
80 Objects: [][]byte{
81 randomChariotYamlObject(),
82 randomChariotYamlObject(),
83 randomChariotYamlObject(),
84 randomChariotYamlObject(),
85 randomChariotYamlObject(),
86 randomChariotYamlObject(),
87 randomChariotYamlObject(),
88 randomChariotYamlObject(),
89 },
90 }
91
92
93 _, err := req.StorageObjects()
94 if err != nil {
95 t.Fatal(err)
96 } else if req.Dir != defaultChariotDir {
97 t.Fatalf("The req.StorageObjects() function should have set the directory to 'chariot' as a default value when Dir is empty. Got %q", req.Dir)
98 }
99 req.Dir = defaultChariotDir
100 _, err = req.StorageObjects()
101 if err != nil {
102 t.Fatalf("The req.StorageObjects() function should accept the directory value 'chariot' and not return an error.. Got %v", err)
103 } else if req.Dir != defaultChariotDir {
104 t.Fatalf("The req.StorageObjects() function should keep the directory value 'chariot' when StorageObjects is called.. Got %v", req.Dir)
105 }
106 for dir := range whitelistedDirs {
107 req.Dir = dir
108 _, err := req.StorageObjects()
109 if err != nil {
110 t.Fatalf("The req.StorageObjects() function should accept the whitelisted directory value %q and not return an error.. Got %v", dir, err)
111 } else if req.Dir != dir {
112 t.Fatalf("The req.StorageObjects() function should keep the whitelisted directory value %q when StorageObjects is called.. Got %v", dir, req.Dir)
113 }
114 }
115
116 var badDirs = []string{"jhgkjhfgwljhfgwljhfw", "gwgwigwigwigi", "../foo", ".", "hello/../../foo/../bar/../", ".."}
117 for _, dir := range badDirs {
118 req.Dir = dir
119 _, err = req.StorageObjects()
120 if err == nil {
121 t.Fatalf("The req.StorageObjects() function should return an error for this invalid directory: %q", dir)
122 }
123 }
124 t.Logf("Successfully validated the Request.Dir optional field validation")
125 }
126
127
128 func TestRequestStorageObjectsFunction(t *testing.T) {
129 var req = &Request{
130 Operation: "CREATE",
131 Banner: "my-banner",
132 Cluster: "",
133 Owner: "test",
134 Objects: [][]byte{
135 randomChariotYamlObject(),
136 randomChariotYamlObject(),
137 randomChariotYamlObject(),
138 randomChariotYamlObject(),
139 randomChariotYamlObject(),
140 randomChariotYamlObject(),
141 randomChariotYamlObject(),
142 randomChariotYamlObject(),
143 },
144 }
145
146 bannerWideObjects, err := req.StorageObjects()
147 if err != nil {
148 t.Fatal(err)
149 }
150
151
152 req.Cluster = "test"
153 clusterWideObjects, err := req.StorageObjects()
154 if err != nil {
155 t.Fatal(err)
156 }
157
158
159 t.Logf("Checking that banner-wide and cluster-wide objects are idempotent")
160 for i, b := range req.Objects {
161 var (
162 originalObj = string(b)
163 bannerWideObj = bannerWideObjects[i].Content
164 clusterWideObj = clusterWideObjects[i].Content
165 )
166 if originalObj != bannerWideObj {
167 t.Logf("Original Object: %q", originalObj)
168 t.Logf("Banner Wide Object: %q", bannerWideObj)
169 t.Fatalf("Banner wide object is not idempotent")
170 } else if originalObj != clusterWideObj {
171 t.Logf("Original Object: %q", originalObj)
172 t.Logf("Cluster Wide Object: %q", clusterWideObj)
173 t.Fatalf("Cluster wide object is not idempotent")
174 }
175 }
176 t.Logf("Successfully checked that banner-wide and cluster-wide objects are idempotent")
177
178
179 t.Logf("Checking that banner-wide and cluster-wide objects are hashed properly")
180 var reTrimHash = regexp.MustCompile(fmt.Sprintf("^(.*/%s/)|([.]yaml)$", req.Dir))
181 for i, b := range req.Objects {
182 var gvknn struct {
183 APIVersion string `yaml:"apiVersion"`
184 Kind string `yaml:"kind"`
185 Metadata struct {
186 Name string `yaml:"name"`
187 Namespace string `yaml:"namespace"`
188 } `yaml:"metadata"`
189 }
190 if err := yaml.Unmarshal(b, &gvknn); err != nil {
191 t.Fatal(err)
192 }
193
194 var unhashed = fmt.Sprintf(HashFormatGVKNN, gvknn.APIVersion, gvknn.Kind, gvknn.Metadata.Name, gvknn.Metadata.Namespace)
195
196 var expectedHash = fmt.Sprintf("%x", md5.Sum([]byte(unhashed)))
197 t.Logf("Checking hash %q", expectedHash)
198
199 if expectedHash != reTrimHash.ReplaceAllString(bannerWideObjects[i].Location, "") {
200 t.Fatalf("Expected hash %q did not match hash at location %q", expectedHash, bannerWideObjects[i].Location)
201 }
202 if expectedHash != reTrimHash.ReplaceAllString(clusterWideObjects[i].Location, "") {
203 t.Fatalf("Expected hash %q did not match hash at location %q", expectedHash, clusterWideObjects[i].Location)
204 }
205 }
206 t.Logf("Successfully checked that banner-wide and cluster-wide objects are hashed properly")
207
208
209 t.Logf("Checking that banner-wide and cluster-wide object locations are set properly")
210 const reGoodBannerWideLocFmt = "^gs://%s/%s/[a-f0-9]{32}[.]yaml$"
211 const reGoodClusterWideLocFmt = "^gs://%s/%s/%s/[a-f0-9]{32}[.]yaml$"
212 t.Logf("Checking banner-wide regex: %q", reGoodBannerWideLocFmt)
213 t.Logf("Checking cluster-wide regex: %q", reGoodClusterWideLocFmt)
214 var reGoodBannerWideLoc = regexp.MustCompile(fmt.Sprintf(reGoodBannerWideLocFmt, req.Banner, req.Dir))
215 var reGoodClusterWideLoc = regexp.MustCompile(fmt.Sprintf(reGoodClusterWideLocFmt, req.Banner, req.Cluster, req.Dir))
216 for i := range req.Objects {
217 var bwo = bannerWideObjects[i].Location
218 var cwo = clusterWideObjects[i].Location
219 t.Logf("Banner-wide location: %q", bwo)
220 if !reGoodBannerWideLoc.MatchString(bwo) {
221 t.Fatalf("The banner-wide object location does not satisfy the regex: %q", bwo)
222 }
223 t.Logf("Cluster-wide location: %q", cwo)
224 if !reGoodClusterWideLoc.MatchString(cwo) {
225 t.Fatalf("The cluster-wide object location does not satisfy the regex: %q", cwo)
226 }
227 }
228 t.Logf("Successfully checked that banner-wide and cluster-wide objects have valid locations")
229 }
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251 func TestStorageObjectGetGcsBucketAndPathParsingWorks(t *testing.T) {
252 var objects = [][]byte{
253 randomChariotYamlObject(),
254 randomChariotYamlObject(),
255 randomChariotYamlObject(),
256 randomChariotYamlObject(),
257 randomChariotYamlObject(),
258 randomChariotYamlObject(),
259 randomChariotYamlObject(),
260 randomChariotYamlObject(),
261 }
262 var dirs = []string{
263 defaultChariotDir,
264 "fluxcfg",
265 "multi/path",
266 }
267 var banners = []string{
268 "testbanner",
269 "test-banner",
270 "test.banner",
271 "test_banner",
272 "test1banner",
273 "test-banner2",
274 "3test.banner",
275 "4test_banner4",
276 }
277 var clusters = []string{
278 "testcluster",
279 "test-cluster",
280 "test.cluster",
281 "test_cluster",
282 "test1cluster",
283 "test-cluster2",
284 "3test.cluster",
285 "4test_cluster4",
286 }
287
288 var numChecks int
289 for _, dir := range dirs {
290 for _, object := range objects {
291 var gvknn struct {
292 APIVersion string `yaml:"apiVersion"`
293 Kind string `yaml:"kind"`
294 Metadata struct {
295 Name string `yaml:"name"`
296 Namespace string `yaml:"namespace"`
297 } `yaml:"metadata"`
298 }
299 if err := yaml.Unmarshal(object, &gvknn); err != nil {
300 t.Fatal(err)
301 }
302
303 var unhashed = fmt.Sprintf(HashFormatGVKNN, gvknn.APIVersion, gvknn.Kind, gvknn.Metadata.Name, gvknn.Metadata.Namespace)
304
305 var expectedHash = fmt.Sprintf("%x", md5.Sum([]byte(unhashed)))
306
307
308 for _, banner := range banners {
309 var bw = StorageObject{
310 Location: fmt.Sprintf("gs://%s/%s/%s.yaml", banner, dir, expectedHash),
311 }
312
313 if banner != bw.getGcsBucket() {
314 const msg = "Banner %q does not equal to the calculated bucket %q from location %q"
315 t.Fatalf(msg, banner, bw.getGcsBucket(), bw.Location)
316 }
317
318 var expectedPath = fmt.Sprintf("%s/%s.yaml", dir, expectedHash)
319 if expectedPath != bw.getGcsPath() {
320 const msg = "Expected path %q does not equal the calculated path %q from location %q"
321 t.Fatalf(msg, expectedPath, bw.getGcsPath(), bw.Location)
322 }
323
324
325
326
327
328 for _, cluster := range clusters {
329 var cw = StorageObject{
330 Location: fmt.Sprintf("gs://%s/clusters/%s/%s/%s.yaml", banner, cluster, dir, expectedHash),
331 }
332
333 if banner != cw.getGcsBucket() {
334 const msg = "Banner %q does not equal to the calculated bucket %q from location %q"
335 t.Fatalf(msg, banner, cw.getGcsBucket(), cw.Location)
336 }
337
338 var expectedPath = fmt.Sprintf("clusters/%s/%s/%s.yaml", cluster, dir, expectedHash)
339 if expectedPath != cw.getGcsPath() {
340 const msg = "Expected path %q does not equal the calculated path %q from location %q"
341 t.Fatalf(msg, expectedPath, cw.getGcsPath(), cw.Location)
342 }
343
344
345
346 numChecks++
347 }
348 }
349 }
350 }
351 t.Logf("Successfully verified %d combinations of StorageObject.Location for their 'bucket' and 'path' parsing", numChecks)
352 }
353
View as plain text