mirror of https://github.com/fluxcd/cli-utils.git
381 lines
10 KiB
Go
381 lines
10 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
"sigs.k8s.io/cli-utils/pkg/testutil"
|
|
)
|
|
|
|
const (
|
|
packageDir = "test-pkg-dir"
|
|
subFolder = "sub-folder"
|
|
inventoryFilename = "inventory.yaml"
|
|
secondInventoryFilename = "inventory-2.yaml"
|
|
podAFilename = "pod-a.yaml"
|
|
podBFilename = "pod-b.yaml"
|
|
configSeparator = "---"
|
|
)
|
|
|
|
var (
|
|
inventoryFilePath = filepath.Join(packageDir, inventoryFilename)
|
|
secondInventoryFilePath = filepath.Join(packageDir, subFolder, secondInventoryFilename)
|
|
podAFilePath = filepath.Join(packageDir, podAFilename)
|
|
podBFilePath = filepath.Join(packageDir, podBFilename)
|
|
)
|
|
|
|
func setupTestFilesystem(t *testing.T) testutil.TestFilesystem {
|
|
// Create the test filesystem, and add package config files
|
|
// to it.
|
|
t.Log("Creating test filesystem")
|
|
tf := testutil.Setup(t, packageDir)
|
|
t.Logf("Adding File: %s", inventoryFilePath)
|
|
tf.WriteFile(t, inventoryFilePath, inventoryConfigMap)
|
|
t.Logf("Adding File: %s", secondInventoryFilePath)
|
|
tf.WriteFile(t, secondInventoryFilePath, secondInventoryConfigMap)
|
|
t.Logf("Adding File: %s", podAFilePath)
|
|
tf.WriteFile(t, podAFilePath, podA)
|
|
t.Logf("Adding File: %s", podBFilePath)
|
|
tf.WriteFile(t, podBFilePath, podB)
|
|
return tf
|
|
}
|
|
|
|
var inventoryConfigMap = []byte(`
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
namespace: test-namespace
|
|
name: inventory
|
|
labels:
|
|
cli-utils.sigs.k8s.io/inventory-id: test-inventory
|
|
`)
|
|
|
|
var secondInventoryConfigMap = []byte(`
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
namespace: test-namespace
|
|
name: inventory-2
|
|
labels:
|
|
cli-utils.sigs.k8s.io/inventory-id: test-inventory
|
|
`)
|
|
|
|
var podA = []byte(`
|
|
apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: pod-a
|
|
namespace: test-namespace
|
|
labels:
|
|
name: test-pod-label
|
|
spec:
|
|
containers:
|
|
- name: kubernetes-pause
|
|
image: registry.k8s.io/pause:2.0
|
|
`)
|
|
|
|
var podB = []byte(`
|
|
apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: pod-b
|
|
namespace: test-namespace
|
|
labels:
|
|
name: test-pod-label
|
|
spec:
|
|
containers:
|
|
- name: kubernetes-pause
|
|
image: registry.k8s.io/pause:2.0
|
|
`)
|
|
|
|
func buildMultiResourceConfig(configs ...[]byte) []byte {
|
|
r := []byte{}
|
|
for i, config := range configs {
|
|
if i > 0 {
|
|
r = append(r, []byte(configSeparator)...)
|
|
}
|
|
r = append(r, config...)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func TestProcessPaths(t *testing.T) {
|
|
tf := setupTestFilesystem(t)
|
|
defer tf.Clean()
|
|
|
|
trueVal := true
|
|
testCases := map[string]struct {
|
|
paths []string
|
|
expectedFileNameFlags genericclioptions.FileNameFlags
|
|
errFromDemandOneDirectory string
|
|
}{
|
|
"empty slice means reading from StdIn": {
|
|
paths: []string{},
|
|
expectedFileNameFlags: genericclioptions.FileNameFlags{
|
|
Filenames: &[]string{"-"},
|
|
},
|
|
},
|
|
"single file in slice is error; must be directory": {
|
|
paths: []string{podAFilePath},
|
|
expectedFileNameFlags: genericclioptions.FileNameFlags{
|
|
Filenames: nil,
|
|
Recursive: nil,
|
|
},
|
|
errFromDemandOneDirectory: "argument 'test-pkg-dir/pod-a.yaml' is not but must be a directory",
|
|
},
|
|
"single dir in slice": {
|
|
paths: []string{tf.GetRootDir()},
|
|
expectedFileNameFlags: genericclioptions.FileNameFlags{
|
|
Filenames: &[]string{tf.GetRootDir()},
|
|
Recursive: &trueVal,
|
|
},
|
|
},
|
|
"multiple arguments is an error": {
|
|
paths: []string{podAFilePath, podBFilePath},
|
|
expectedFileNameFlags: genericclioptions.FileNameFlags{
|
|
Filenames: nil,
|
|
Recursive: nil,
|
|
},
|
|
errFromDemandOneDirectory: "specify exactly one directory path argument; rejecting [test-pkg-dir/pod-a.yaml test-pkg-dir/pod-b.yaml]",
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
fileNameFlags, err := DemandOneDirectory(tc.paths)
|
|
assert.Equal(t, tc.expectedFileNameFlags, fileNameFlags)
|
|
if err != nil && err.Error() != tc.errFromDemandOneDirectory {
|
|
assert.Equal(t, err.Error(), tc.errFromDemandOneDirectory)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFilterInputFile(t *testing.T) {
|
|
tf := testutil.Setup(t)
|
|
defer tf.Clean()
|
|
|
|
testCases := map[string]struct {
|
|
configObjects [][]byte
|
|
expectedObjects [][]byte
|
|
}{
|
|
"Empty config objects writes empty file": {
|
|
configObjects: [][]byte{},
|
|
expectedObjects: [][]byte{},
|
|
},
|
|
"Only inventory obj writes empty file": {
|
|
configObjects: [][]byte{inventoryConfigMap},
|
|
expectedObjects: [][]byte{},
|
|
},
|
|
"Only pods writes both pods": {
|
|
configObjects: [][]byte{podA, podB},
|
|
expectedObjects: [][]byte{podA, podB},
|
|
},
|
|
"Basic case of inventory obj and two pods": {
|
|
configObjects: [][]byte{inventoryConfigMap, podA, podB},
|
|
expectedObjects: [][]byte{podA, podB},
|
|
},
|
|
"Basic case of inventory obj and two pods in different order": {
|
|
configObjects: [][]byte{podB, inventoryConfigMap, podA},
|
|
expectedObjects: [][]byte{podB, podA},
|
|
},
|
|
}
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
// Build a single file of multiple resource configs, and
|
|
// call the tested function FilterInputFile. This writes
|
|
// the passed file to the test filesystem, filtering
|
|
// the inventory object if it exists in the passed file.
|
|
in := buildMultiResourceConfig(tc.configObjects...)
|
|
err := FilterInputFile(bytes.NewReader(in), tf.GetRootDir())
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error in FilterInputFile: %s", err)
|
|
}
|
|
// Retrieve the files from the test filesystem.
|
|
actualFiles, err := os.ReadDir(tf.GetRootDir())
|
|
if err != nil {
|
|
t.Fatalf("Error reading test filesystem directory: %s", err)
|
|
}
|
|
// Since we remove the generated file for each test, there should
|
|
// not be more than one file in the test filesystem.
|
|
if len(actualFiles) > 1 {
|
|
t.Fatalf("Wrong number of files (%d) in dir: %s", len(actualFiles), tf.GetRootDir())
|
|
}
|
|
// If there is a generated file, then read it into actualStr.
|
|
actualStr := ""
|
|
if len(actualFiles) != 0 {
|
|
actualFilename := actualFiles[0].Name()
|
|
defer os.Remove(actualFilename)
|
|
actual, err := os.ReadFile(actualFilename)
|
|
if err != nil {
|
|
t.Fatalf("Error reading created file (%s): %s", actualFilename, err)
|
|
}
|
|
actualStr = strings.TrimSpace(string(actual))
|
|
}
|
|
// Build the expected string from the expectedObjects. This expected
|
|
// string should not have the inventory object config in it.
|
|
expected := buildMultiResourceConfig(tc.expectedObjects...)
|
|
expectedStr := strings.TrimSpace(string(expected))
|
|
if expectedStr != actualStr {
|
|
t.Errorf("Expected file contents (%s) not equal to actual file contents (%s)",
|
|
expectedStr, actualStr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandDir(t *testing.T) {
|
|
tf := setupTestFilesystem(t)
|
|
defer tf.Clean()
|
|
|
|
testCases := map[string]struct {
|
|
packageDirPath string
|
|
expandedInventory string
|
|
expandedPaths []string
|
|
isError bool
|
|
}{
|
|
"empty path is error": {
|
|
packageDirPath: "",
|
|
isError: true,
|
|
},
|
|
"path that is not dir is error": {
|
|
packageDirPath: "fakedir1",
|
|
isError: true,
|
|
},
|
|
"root package dir excludes inventory object": {
|
|
packageDirPath: tf.GetRootDir(),
|
|
expandedInventory: "inventory.yaml",
|
|
expandedPaths: []string{
|
|
"pod-a.yaml",
|
|
"pod-b.yaml",
|
|
},
|
|
isError: false,
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
actualInventory, actualPaths, err := ExpandDir(tc.packageDirPath)
|
|
if tc.isError {
|
|
if err == nil {
|
|
t.Fatalf("expected error but received none")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("received unexpected error %#v", err)
|
|
return
|
|
}
|
|
actualFilename := filepath.Base(actualInventory)
|
|
if tc.expandedInventory != actualFilename {
|
|
t.Errorf("expected inventory template filepath (%s), got (%s)", tc.expandedInventory, actualFilename)
|
|
}
|
|
if len(tc.expandedPaths) != len(actualPaths) {
|
|
t.Errorf("expected (%d) resource filepaths, got (%d)", len(tc.expandedPaths), len(actualPaths))
|
|
}
|
|
for _, expectedPath := range tc.expandedPaths {
|
|
found := false
|
|
for _, actualPath := range actualPaths {
|
|
actualFilename := filepath.Base(actualPath)
|
|
if expectedPath == actualFilename {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected filename (%s) not found", expectedPath)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpandDirErrors(t *testing.T) {
|
|
tf := setupTestFilesystem(t)
|
|
defer tf.Clean()
|
|
|
|
testCases := map[string]struct {
|
|
packageDirPath []string
|
|
expandedPaths []string
|
|
isError bool
|
|
}{
|
|
"empty path is error": {
|
|
packageDirPath: []string{},
|
|
isError: true,
|
|
},
|
|
"more than one path is error": {
|
|
packageDirPath: []string{"fakedir1", "fakedir2"},
|
|
isError: true,
|
|
},
|
|
"path that is not dir is error": {
|
|
packageDirPath: []string{"fakedir1"},
|
|
isError: true,
|
|
},
|
|
"root package dir excludes inventory object": {
|
|
packageDirPath: []string{tf.GetRootDir()},
|
|
expandedPaths: []string{
|
|
filepath.Join(packageDir, "pod-a.yaml"),
|
|
filepath.Join(packageDir, "pod-b.yaml"),
|
|
},
|
|
isError: false,
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
trueVal := true
|
|
filenameFlags := genericclioptions.FileNameFlags{
|
|
Filenames: &tc.packageDirPath,
|
|
Recursive: &trueVal,
|
|
}
|
|
actualFlags, err := ExpandPackageDir(filenameFlags)
|
|
if tc.isError && err == nil {
|
|
t.Fatalf("expected error but received none")
|
|
}
|
|
if !tc.isError {
|
|
if err != nil {
|
|
t.Fatalf("unexpected error received: %v", err)
|
|
}
|
|
actualPaths := *actualFlags.Filenames
|
|
if len(tc.expandedPaths) != len(actualPaths) {
|
|
t.Errorf("expected config filepaths (%s), got (%s)",
|
|
tc.expandedPaths, actualPaths)
|
|
}
|
|
for _, expected := range tc.expandedPaths {
|
|
if !filepathExists(expected, actualPaths) {
|
|
t.Errorf("expected config filepath (%s) in actual filepaths (%s)",
|
|
expected, actualPaths)
|
|
}
|
|
}
|
|
// Check the inventory object is not in the filename flags.
|
|
for _, actualPath := range actualPaths {
|
|
if strings.Contains(actualPath, "inventory.yaml") {
|
|
t.Errorf("inventory object should be excluded")
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// filepathExists returns true if the passed "filepath" is a substring
|
|
// of any of the passed full "filepaths"; false otherwise. For example:
|
|
// if filepath = "test/a.yaml", and filepaths includes "/tmp/test/a.yaml",
|
|
// this function returns true.
|
|
func filepathExists(filepath string, filepaths []string) bool {
|
|
for _, fp := range filepaths {
|
|
if strings.Contains(fp, filepath) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|