mirror of https://github.com/fluxcd/cli-utils.git
323 lines
8.1 KiB
Go
323 lines
8.1 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
|
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
|
)
|
|
|
|
// writeFile writes a file under the test directory
|
|
func writeFile(t *testing.T, path string, value []byte) {
|
|
err := ioutil.WriteFile(path, value, 0600)
|
|
if !assert.NoError(t, err) {
|
|
assert.FailNow(t, err.Error())
|
|
}
|
|
}
|
|
|
|
var readFileA = []byte(`
|
|
apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: objA
|
|
namespace: namespaceA
|
|
`)
|
|
|
|
var readFileB = []byte(`
|
|
apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: objB
|
|
namespace: namespaceB
|
|
`)
|
|
|
|
var readFileC = []byte(`
|
|
apiVersion: v1
|
|
kind: Pod
|
|
metadata:
|
|
name: objC
|
|
`)
|
|
|
|
func TestComplete(t *testing.T) {
|
|
tests := map[string]struct {
|
|
args []string
|
|
files map[string][]byte
|
|
isError bool
|
|
expectedErrMessage string
|
|
expectedNamespace string
|
|
}{
|
|
"Empty args returns error": {
|
|
args: []string{},
|
|
isError: true,
|
|
expectedErrMessage: "need one 'directory' arg; have 0",
|
|
},
|
|
"More than one argument should fail": {
|
|
args: []string{"foo", "bar"},
|
|
isError: true,
|
|
expectedErrMessage: "need one 'directory' arg; have 2",
|
|
},
|
|
"Non-directory arg should fail": {
|
|
args: []string{"foo"},
|
|
isError: true,
|
|
expectedErrMessage: "invalid directory argument: foo",
|
|
},
|
|
"More than one namespace should fail": {
|
|
args: []string{},
|
|
files: map[string][]byte{
|
|
"a_test.yaml": readFileA,
|
|
"b_test.yaml": readFileB,
|
|
},
|
|
isError: true,
|
|
expectedErrMessage: "resources belong to different namespaces",
|
|
},
|
|
"If at least one resource doesn't have namespace, it should use the default": {
|
|
args: []string{},
|
|
files: map[string][]byte{
|
|
"b_test.yaml": readFileB,
|
|
"c_test.yaml": readFileC,
|
|
},
|
|
isError: false,
|
|
expectedNamespace: "foo",
|
|
},
|
|
"No resources without namespace should use the default namespace": {
|
|
args: []string{},
|
|
files: map[string][]byte{
|
|
"c_test.yaml": readFileC,
|
|
},
|
|
isError: false,
|
|
expectedNamespace: "foo",
|
|
},
|
|
}
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
var err error
|
|
dir, err := ioutil.TempDir("", "test-dir")
|
|
if !assert.NoError(t, err) {
|
|
assert.FailNow(t, err.Error())
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
for fileName, fileContent := range tc.files {
|
|
writeFile(t, filepath.Join(dir, fileName), fileContent)
|
|
}
|
|
if len(tc.files) > 0 {
|
|
tc.args = append(tc.args, dir)
|
|
}
|
|
|
|
tf := cmdtesting.NewTestFactory().WithNamespace("foo")
|
|
defer tf.Cleanup()
|
|
ioStreams, _, out, _ := genericclioptions.NewTestIOStreams()
|
|
io := NewInitOptions(tf, ioStreams)
|
|
err = io.Complete(tc.args)
|
|
|
|
if err != nil {
|
|
if !tc.isError {
|
|
t.Errorf("Expected error, but did not receive one")
|
|
return
|
|
}
|
|
assert.Contains(t, err.Error(), tc.expectedErrMessage)
|
|
return
|
|
}
|
|
assert.Contains(t, out.String(), tc.expectedNamespace)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindNamespace(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
namespace string
|
|
enforceNamespace bool
|
|
files map[string][]byte
|
|
expectedNamespace string
|
|
}{
|
|
"fallback to default": {
|
|
namespace: "foo",
|
|
enforceNamespace: false,
|
|
files: map[string][]byte{
|
|
"a_test.yaml": readFileA,
|
|
"b_test.yaml": readFileB,
|
|
},
|
|
expectedNamespace: "foo",
|
|
},
|
|
"enforce namespace": {
|
|
namespace: "bar",
|
|
enforceNamespace: true,
|
|
files: map[string][]byte{
|
|
"a_test.yaml": readFileA,
|
|
},
|
|
expectedNamespace: "bar",
|
|
},
|
|
"use namespace from resource if all the same": {
|
|
namespace: "bar",
|
|
enforceNamespace: false,
|
|
files: map[string][]byte{
|
|
"a_test.yaml": readFileA,
|
|
},
|
|
expectedNamespace: "namespaceA",
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
var err error
|
|
dir, err := ioutil.TempDir("", "test-dir")
|
|
if !assert.NoError(t, err) {
|
|
assert.FailNow(t, err.Error())
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
for fileName, fileContent := range tc.files {
|
|
writeFile(t, filepath.Join(dir, fileName), fileContent)
|
|
}
|
|
|
|
fakeLoader := &fakeNamespaceLoader{
|
|
namespace: tc.namespace,
|
|
enforceNamespace: tc.enforceNamespace,
|
|
}
|
|
|
|
namespace, err := findNamespace(fakeLoader, dir)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tc.expectedNamespace, namespace)
|
|
})
|
|
}
|
|
}
|
|
|
|
type fakeNamespaceLoader struct {
|
|
namespace string
|
|
enforceNamespace bool
|
|
}
|
|
|
|
func (f *fakeNamespaceLoader) Namespace() (string, bool, error) {
|
|
return f.namespace, f.enforceNamespace, nil
|
|
}
|
|
|
|
func TestDefaultInventoryID(t *testing.T) {
|
|
tf := cmdtesting.NewTestFactory().WithNamespace("foo")
|
|
defer tf.Cleanup()
|
|
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
|
|
io := NewInitOptions(tf, ioStreams)
|
|
actual, err := io.defaultInventoryID()
|
|
if err != nil {
|
|
t.Errorf("Unxpected error during UUID generation: %v", err)
|
|
}
|
|
// Example UUID: dd647113-a354-48fa-9b93-cc1b7a85aadb
|
|
var uuidRegexp = `^[a-z0-9]{8}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{4}\-[a-z0-9]{12}$`
|
|
re := regexp.MustCompile(uuidRegexp)
|
|
if !re.MatchString(actual) {
|
|
t.Errorf("Expected UUID; got (%s)", actual)
|
|
}
|
|
}
|
|
|
|
func TestValidateInventoryID(t *testing.T) {
|
|
tests := map[string]struct {
|
|
inventoryID string
|
|
isValid bool
|
|
}{
|
|
"Empty InventoryID fails": {
|
|
inventoryID: "",
|
|
isValid: false,
|
|
},
|
|
"InventoryID greater than sixty-three chars fails": {
|
|
inventoryID: "88888888888888888888888888888888888888888888888888888888888888888",
|
|
isValid: false,
|
|
},
|
|
"Non-allowed characters fails": {
|
|
inventoryID: "&foo",
|
|
isValid: false,
|
|
},
|
|
"Initial dot fails": {
|
|
inventoryID: ".foo",
|
|
isValid: false,
|
|
},
|
|
"Initial dash fails": {
|
|
inventoryID: "-foo",
|
|
isValid: false,
|
|
},
|
|
"Initial underscore fails": {
|
|
inventoryID: "_foo",
|
|
isValid: false,
|
|
},
|
|
"Trailing dot fails": {
|
|
inventoryID: "foo.",
|
|
isValid: false,
|
|
},
|
|
"Trailing dash fails": {
|
|
inventoryID: "foo-",
|
|
isValid: false,
|
|
},
|
|
"Trailing underscore fails": {
|
|
inventoryID: "foo_",
|
|
isValid: false,
|
|
},
|
|
"Initial digit succeeds": {
|
|
inventoryID: "90-foo.bar_test",
|
|
isValid: true,
|
|
},
|
|
"Allowed characters succeed": {
|
|
inventoryID: "f_oo90bar-t.est90",
|
|
isValid: true,
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
actualValid := validateInventoryID(tc.inventoryID)
|
|
if tc.isValid != actualValid {
|
|
t.Errorf("InventoryID: %s. Expected valid (%t), got (%t)", tc.inventoryID, tc.isValid, actualValid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFillInValues(t *testing.T) {
|
|
tests := map[string]struct {
|
|
namespace string
|
|
inventoryID string
|
|
}{
|
|
"Basic namespace/inventoryID": {
|
|
namespace: "foo",
|
|
inventoryID: "bar",
|
|
},
|
|
}
|
|
|
|
for name, tc := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
tf := cmdtesting.NewTestFactory().WithNamespace("foo")
|
|
defer tf.Cleanup()
|
|
ioStreams, _, _, _ := genericclioptions.NewTestIOStreams() //nolint:dogsled
|
|
io := NewInitOptions(tf, ioStreams)
|
|
io.Namespace = tc.namespace
|
|
io.InventoryID = tc.inventoryID
|
|
actual := io.fillInValues()
|
|
expectedLabel := fmt.Sprintf("cli-utils.sigs.k8s.io/inventory-id: %s", tc.inventoryID)
|
|
if !strings.Contains(actual, expectedLabel) {
|
|
t.Errorf("\nExpected label (%s) not found in inventory object: %s\n", expectedLabel, actual)
|
|
}
|
|
expectedNamespace := fmt.Sprintf("namespace: %s", tc.namespace)
|
|
if !strings.Contains(actual, expectedNamespace) {
|
|
t.Errorf("\nExpected namespace (%s) not found in inventory object: %s\n", expectedNamespace, actual)
|
|
}
|
|
matched, err := regexp.MatchString(`name: inventory-\d{8}\n`, actual)
|
|
if err != nil {
|
|
t.Errorf("unexpected error parsing inventory name: %s", err)
|
|
}
|
|
if !matched {
|
|
t.Errorf("expected inventory name (e.g. inventory-12345678), got (%s)", actual)
|
|
}
|
|
if !strings.Contains(actual, "kind: ConfigMap") {
|
|
t.Errorf("\nExpected `kind: ConfigMap` not found in inventory object: %s\n", actual)
|
|
}
|
|
})
|
|
}
|
|
}
|