1
2
3
4 package common
5
6 import (
7 "bytes"
8 "os"
9 "path/filepath"
10 "strings"
11 "testing"
12
13 "github.com/stretchr/testify/assert"
14 "k8s.io/cli-runtime/pkg/genericclioptions"
15 "sigs.k8s.io/cli-utils/pkg/testutil"
16 )
17
18 const (
19 packageDir = "test-pkg-dir"
20 subFolder = "sub-folder"
21 inventoryFilename = "inventory.yaml"
22 secondInventoryFilename = "inventory-2.yaml"
23 podAFilename = "pod-a.yaml"
24 podBFilename = "pod-b.yaml"
25 configSeparator = "---"
26 )
27
28 var (
29 inventoryFilePath = filepath.Join(packageDir, inventoryFilename)
30 secondInventoryFilePath = filepath.Join(packageDir, subFolder, secondInventoryFilename)
31 podAFilePath = filepath.Join(packageDir, podAFilename)
32 podBFilePath = filepath.Join(packageDir, podBFilename)
33 )
34
35 func setupTestFilesystem(t *testing.T) testutil.TestFilesystem {
36
37
38 t.Log("Creating test filesystem")
39 tf := testutil.Setup(t, packageDir)
40 t.Logf("Adding File: %s", inventoryFilePath)
41 tf.WriteFile(t, inventoryFilePath, inventoryConfigMap)
42 t.Logf("Adding File: %s", secondInventoryFilePath)
43 tf.WriteFile(t, secondInventoryFilePath, secondInventoryConfigMap)
44 t.Logf("Adding File: %s", podAFilePath)
45 tf.WriteFile(t, podAFilePath, podA)
46 t.Logf("Adding File: %s", podBFilePath)
47 tf.WriteFile(t, podBFilePath, podB)
48 return tf
49 }
50
51 var inventoryConfigMap = []byte(`
52 apiVersion: v1
53 kind: ConfigMap
54 metadata:
55 namespace: test-namespace
56 name: inventory
57 labels:
58 cli-utils.sigs.k8s.io/inventory-id: test-inventory
59 `)
60
61 var secondInventoryConfigMap = []byte(`
62 apiVersion: v1
63 kind: ConfigMap
64 metadata:
65 namespace: test-namespace
66 name: inventory-2
67 labels:
68 cli-utils.sigs.k8s.io/inventory-id: test-inventory
69 `)
70
71 var podA = []byte(`
72 apiVersion: v1
73 kind: Pod
74 metadata:
75 name: pod-a
76 namespace: test-namespace
77 labels:
78 name: test-pod-label
79 spec:
80 containers:
81 - name: kubernetes-pause
82 image: k8s.gcr.io/pause:2.0
83 `)
84
85 var podB = []byte(`
86 apiVersion: v1
87 kind: Pod
88 metadata:
89 name: pod-b
90 namespace: test-namespace
91 labels:
92 name: test-pod-label
93 spec:
94 containers:
95 - name: kubernetes-pause
96 image: k8s.gcr.io/pause:2.0
97 `)
98
99 func buildMultiResourceConfig(configs ...[]byte) []byte {
100 r := []byte{}
101 for i, config := range configs {
102 if i > 0 {
103 r = append(r, []byte(configSeparator)...)
104 }
105 r = append(r, config...)
106 }
107 return r
108 }
109
110 func TestProcessPaths(t *testing.T) {
111 tf := setupTestFilesystem(t)
112 defer tf.Clean()
113
114 trueVal := true
115 testCases := map[string]struct {
116 paths []string
117 expectedFileNameFlags genericclioptions.FileNameFlags
118 errFromDemandOneDirectory string
119 }{
120 "empty slice means reading from StdIn": {
121 paths: []string{},
122 expectedFileNameFlags: genericclioptions.FileNameFlags{
123 Filenames: &[]string{"-"},
124 },
125 },
126 "single file in slice is error; must be directory": {
127 paths: []string{podAFilePath},
128 expectedFileNameFlags: genericclioptions.FileNameFlags{
129 Filenames: nil,
130 Recursive: nil,
131 },
132 errFromDemandOneDirectory: "argument 'test-pkg-dir/pod-a.yaml' is not but must be a directory",
133 },
134 "single dir in slice": {
135 paths: []string{tf.GetRootDir()},
136 expectedFileNameFlags: genericclioptions.FileNameFlags{
137 Filenames: &[]string{tf.GetRootDir()},
138 Recursive: &trueVal,
139 },
140 },
141 "multiple arguments is an error": {
142 paths: []string{podAFilePath, podBFilePath},
143 expectedFileNameFlags: genericclioptions.FileNameFlags{
144 Filenames: nil,
145 Recursive: nil,
146 },
147 errFromDemandOneDirectory: "specify exactly one directory path argument; rejecting [test-pkg-dir/pod-a.yaml test-pkg-dir/pod-b.yaml]",
148 },
149 }
150
151 for tn, tc := range testCases {
152 t.Run(tn, func(t *testing.T) {
153 fileNameFlags, err := DemandOneDirectory(tc.paths)
154 assert.Equal(t, tc.expectedFileNameFlags, fileNameFlags)
155 if err != nil && err.Error() != tc.errFromDemandOneDirectory {
156 assert.Equal(t, err.Error(), tc.errFromDemandOneDirectory)
157 }
158 })
159 }
160 }
161
162 func TestFilterInputFile(t *testing.T) {
163 tf := testutil.Setup(t)
164 defer tf.Clean()
165
166 testCases := map[string]struct {
167 configObjects [][]byte
168 expectedObjects [][]byte
169 }{
170 "Empty config objects writes empty file": {
171 configObjects: [][]byte{},
172 expectedObjects: [][]byte{},
173 },
174 "Only inventory obj writes empty file": {
175 configObjects: [][]byte{inventoryConfigMap},
176 expectedObjects: [][]byte{},
177 },
178 "Only pods writes both pods": {
179 configObjects: [][]byte{podA, podB},
180 expectedObjects: [][]byte{podA, podB},
181 },
182 "Basic case of inventory obj and two pods": {
183 configObjects: [][]byte{inventoryConfigMap, podA, podB},
184 expectedObjects: [][]byte{podA, podB},
185 },
186 "Basic case of inventory obj and two pods in different order": {
187 configObjects: [][]byte{podB, inventoryConfigMap, podA},
188 expectedObjects: [][]byte{podB, podA},
189 },
190 }
191 for tn, tc := range testCases {
192 t.Run(tn, func(t *testing.T) {
193
194
195
196
197 in := buildMultiResourceConfig(tc.configObjects...)
198 err := FilterInputFile(bytes.NewReader(in), tf.GetRootDir())
199 if err != nil {
200 t.Fatalf("Unexpected error in FilterInputFile: %s", err)
201 }
202
203 actualFiles, err := os.ReadDir(tf.GetRootDir())
204 if err != nil {
205 t.Fatalf("Error reading test filesystem directory: %s", err)
206 }
207
208
209 if len(actualFiles) > 1 {
210 t.Fatalf("Wrong number of files (%d) in dir: %s", len(actualFiles), tf.GetRootDir())
211 }
212
213 actualStr := ""
214 if len(actualFiles) != 0 {
215 actualFilename := actualFiles[0].Name()
216 defer os.Remove(actualFilename)
217 actual, err := os.ReadFile(actualFilename)
218 if err != nil {
219 t.Fatalf("Error reading created file (%s): %s", actualFilename, err)
220 }
221 actualStr = strings.TrimSpace(string(actual))
222 }
223
224
225 expected := buildMultiResourceConfig(tc.expectedObjects...)
226 expectedStr := strings.TrimSpace(string(expected))
227 if expectedStr != actualStr {
228 t.Errorf("Expected file contents (%s) not equal to actual file contents (%s)",
229 expectedStr, actualStr)
230 }
231 })
232 }
233 }
234
235 func TestExpandDir(t *testing.T) {
236 tf := setupTestFilesystem(t)
237 defer tf.Clean()
238
239 testCases := map[string]struct {
240 packageDirPath string
241 expandedInventory string
242 expandedPaths []string
243 isError bool
244 }{
245 "empty path is error": {
246 packageDirPath: "",
247 isError: true,
248 },
249 "path that is not dir is error": {
250 packageDirPath: "fakedir1",
251 isError: true,
252 },
253 "root package dir excludes inventory object": {
254 packageDirPath: tf.GetRootDir(),
255 expandedInventory: "inventory.yaml",
256 expandedPaths: []string{
257 "pod-a.yaml",
258 "pod-b.yaml",
259 },
260 isError: false,
261 },
262 }
263
264 for tn, tc := range testCases {
265 t.Run(tn, func(t *testing.T) {
266 actualInventory, actualPaths, err := ExpandDir(tc.packageDirPath)
267 if tc.isError {
268 if err == nil {
269 t.Fatalf("expected error but received none")
270 }
271 return
272 }
273 if err != nil {
274 t.Fatalf("received unexpected error %#v", err)
275 return
276 }
277 actualFilename := filepath.Base(actualInventory)
278 if tc.expandedInventory != actualFilename {
279 t.Errorf("expected inventory template filepath (%s), got (%s)", tc.expandedInventory, actualFilename)
280 }
281 if len(tc.expandedPaths) != len(actualPaths) {
282 t.Errorf("expected (%d) resource filepaths, got (%d)", len(tc.expandedPaths), len(actualPaths))
283 }
284 for _, expectedPath := range tc.expandedPaths {
285 found := false
286 for _, actualPath := range actualPaths {
287 actualFilename := filepath.Base(actualPath)
288 if expectedPath == actualFilename {
289 found = true
290 break
291 }
292 }
293 if !found {
294 t.Errorf("expected filename (%s) not found", expectedPath)
295 }
296 }
297 })
298 }
299 }
300
301 func TestExpandDirErrors(t *testing.T) {
302 tf := setupTestFilesystem(t)
303 defer tf.Clean()
304
305 testCases := map[string]struct {
306 packageDirPath []string
307 expandedPaths []string
308 isError bool
309 }{
310 "empty path is error": {
311 packageDirPath: []string{},
312 isError: true,
313 },
314 "more than one path is error": {
315 packageDirPath: []string{"fakedir1", "fakedir2"},
316 isError: true,
317 },
318 "path that is not dir is error": {
319 packageDirPath: []string{"fakedir1"},
320 isError: true,
321 },
322 "root package dir excludes inventory object": {
323 packageDirPath: []string{tf.GetRootDir()},
324 expandedPaths: []string{
325 filepath.Join(packageDir, "pod-a.yaml"),
326 filepath.Join(packageDir, "pod-b.yaml"),
327 },
328 isError: false,
329 },
330 }
331
332 for tn, tc := range testCases {
333 t.Run(tn, func(t *testing.T) {
334 trueVal := true
335 filenameFlags := genericclioptions.FileNameFlags{
336 Filenames: &tc.packageDirPath,
337 Recursive: &trueVal,
338 }
339 actualFlags, err := ExpandPackageDir(filenameFlags)
340 if tc.isError && err == nil {
341 t.Fatalf("expected error but received none")
342 }
343 if !tc.isError {
344 if err != nil {
345 t.Fatalf("unexpected error received: %v", err)
346 }
347 actualPaths := *actualFlags.Filenames
348 if len(tc.expandedPaths) != len(actualPaths) {
349 t.Errorf("expected config filepaths (%s), got (%s)",
350 tc.expandedPaths, actualPaths)
351 }
352 for _, expected := range tc.expandedPaths {
353 if !filepathExists(expected, actualPaths) {
354 t.Errorf("expected config filepath (%s) in actual filepaths (%s)",
355 expected, actualPaths)
356 }
357 }
358
359 for _, actualPath := range actualPaths {
360 if strings.Contains(actualPath, "inventory.yaml") {
361 t.Errorf("inventory object should be excluded")
362 }
363 }
364 }
365 })
366 }
367 }
368
369
370
371
372
373 func filepathExists(filepath string, filepaths []string) bool {
374 for _, fp := range filepaths {
375 if strings.Contains(fp, filepath) {
376 return true
377 }
378 }
379 return false
380 }
381
View as plain text