mirror of https://github.com/fluxcd/cli-utils.git
409 lines
10 KiB
Go
409 lines
10 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package manifestreader
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/fluxcd/cli-utils/pkg/object"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
|
"sigs.k8s.io/kustomize/kyaml/kio/filters"
|
|
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
func TestSetNamespaces(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
objs []*unstructured.Unstructured
|
|
defaultNamespace string
|
|
enforceNamespace bool
|
|
|
|
expectedNamespaces []string
|
|
expectedErr error
|
|
}{
|
|
"resources already have namespace": {
|
|
objs: []*unstructured.Unstructured{
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "apps",
|
|
Version: "v1",
|
|
Kind: "Deployment",
|
|
}, "default"),
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "policy",
|
|
Version: "v1beta1",
|
|
Kind: "PodDisruptionBudget",
|
|
}, "default"),
|
|
},
|
|
defaultNamespace: "foo",
|
|
enforceNamespace: false,
|
|
expectedNamespaces: []string{
|
|
"default",
|
|
"default",
|
|
},
|
|
},
|
|
"resources without namespace and mapping in RESTMapper": {
|
|
objs: []*unstructured.Unstructured{
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "apps",
|
|
Version: "v1",
|
|
Kind: "Deployment",
|
|
}, ""),
|
|
},
|
|
defaultNamespace: "foo",
|
|
enforceNamespace: false,
|
|
expectedNamespaces: []string{"foo"},
|
|
},
|
|
"resource with namespace that does match enforced ns": {
|
|
objs: []*unstructured.Unstructured{
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "apps",
|
|
Version: "v1",
|
|
Kind: "Deployment",
|
|
}, "bar"),
|
|
},
|
|
defaultNamespace: "bar",
|
|
enforceNamespace: true,
|
|
expectedNamespaces: []string{"bar"},
|
|
},
|
|
"resource with namespace that doesn't match enforced ns": {
|
|
objs: []*unstructured.Unstructured{
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "apps",
|
|
Version: "v1",
|
|
Kind: "Deployment",
|
|
}, "foo"),
|
|
},
|
|
defaultNamespace: "bar",
|
|
enforceNamespace: true,
|
|
expectedErr: &NamespaceMismatchError{
|
|
RequiredNamespace: "bar",
|
|
Namespace: "foo",
|
|
},
|
|
},
|
|
"cluster-scoped CR with CRD": {
|
|
objs: []*unstructured.Unstructured{
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "Custom",
|
|
}, ""),
|
|
toCRDUnstructured(schema.GroupVersionKind{
|
|
Group: "apiextensions.k8s.io",
|
|
Version: "v1",
|
|
Kind: "CustomResourceDefinition",
|
|
}, schema.GroupVersionKind{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "Custom",
|
|
}, "Cluster"),
|
|
},
|
|
defaultNamespace: "bar",
|
|
enforceNamespace: true,
|
|
expectedNamespaces: []string{"", ""},
|
|
},
|
|
"namespace-scoped CR with CRD": {
|
|
objs: []*unstructured.Unstructured{
|
|
toCRDUnstructured(schema.GroupVersionKind{
|
|
Group: "apiextensions.k8s.io",
|
|
Version: "v1",
|
|
Kind: "CustomResourceDefinition",
|
|
}, schema.GroupVersionKind{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "Custom",
|
|
}, "Namespaced"),
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "Custom",
|
|
}, ""),
|
|
},
|
|
defaultNamespace: "bar",
|
|
enforceNamespace: true,
|
|
expectedNamespaces: []string{"", "bar"},
|
|
},
|
|
"unknown types in CRs": {
|
|
objs: []*unstructured.Unstructured{
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "Custom",
|
|
}, ""),
|
|
toUnstructured(schema.GroupVersionKind{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "AnotherCustom",
|
|
}, ""),
|
|
},
|
|
expectedErr: &UnknownTypesError{
|
|
GroupVersionKinds: []schema.GroupVersionKind{
|
|
{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "Custom",
|
|
},
|
|
{
|
|
Group: "custom.io",
|
|
Version: "v1",
|
|
Kind: "AnotherCustom",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
tf := cmdtesting.NewTestFactory().WithNamespace("namespace")
|
|
defer tf.Cleanup()
|
|
|
|
mapper, err := tf.ToRESTMapper()
|
|
require.NoError(t, err)
|
|
crdGV := schema.GroupVersion{Group: "apiextensions.k8s.io", Version: "v1"}
|
|
crdMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{crdGV})
|
|
crdMapper.AddSpecific(crdGV.WithKind("CustomResourceDefinition"),
|
|
crdGV.WithResource("customresourcedefinitions"),
|
|
crdGV.WithResource("customresourcedefinition"), meta.RESTScopeRoot)
|
|
mapper = meta.MultiRESTMapper([]meta.RESTMapper{mapper, crdMapper})
|
|
|
|
err = SetNamespaces(mapper, tc.objs, tc.defaultNamespace, tc.enforceNamespace)
|
|
|
|
if tc.expectedErr != nil {
|
|
require.Error(t, err)
|
|
assert.Equal(t, tc.expectedErr, err)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
for i, obj := range tc.objs {
|
|
assert.Equal(t, tc.expectedNamespaces[i], obj.GetNamespace())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
var (
|
|
depID = object.ObjMetadata{
|
|
GroupKind: schema.GroupKind{
|
|
Group: "apps",
|
|
Kind: "Deployment",
|
|
},
|
|
Namespace: "default",
|
|
Name: "foo",
|
|
}
|
|
|
|
clusterRoleID = object.ObjMetadata{
|
|
GroupKind: schema.GroupKind{
|
|
Group: "rbac.authorization.k8s.io",
|
|
Kind: "ClusterRole",
|
|
},
|
|
Name: "bar",
|
|
}
|
|
)
|
|
|
|
func TestFilterLocalConfigs(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
input []*unstructured.Unstructured
|
|
expected []string
|
|
}{
|
|
"don't filter if no annotation": {
|
|
input: []*unstructured.Unstructured{
|
|
objMetaToUnstructured(depID),
|
|
objMetaToUnstructured(clusterRoleID),
|
|
},
|
|
expected: []string{
|
|
depID.Name,
|
|
clusterRoleID.Name,
|
|
},
|
|
},
|
|
"filter all if all have annotation": {
|
|
input: []*unstructured.Unstructured{
|
|
addAnnotation(t, objMetaToUnstructured(depID), filters.LocalConfigAnnotation, "true"),
|
|
addAnnotation(t, objMetaToUnstructured(clusterRoleID), filters.LocalConfigAnnotation, "false"),
|
|
},
|
|
expected: []string{},
|
|
},
|
|
"filter even if resource have other annotations": {
|
|
input: []*unstructured.Unstructured{
|
|
addAnnotation(t,
|
|
addAnnotation(
|
|
t, objMetaToUnstructured(depID),
|
|
filters.LocalConfigAnnotation, "true"),
|
|
"my-annotation", "foo"),
|
|
},
|
|
expected: []string{},
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
res := FilterLocalConfig(tc.input)
|
|
|
|
var names []string
|
|
for _, obj := range res {
|
|
names = append(names, obj.GetName())
|
|
}
|
|
|
|
// Avoid test failures due to nil slice and empty slice
|
|
// not being equal.
|
|
if len(tc.expected) == 0 && len(names) == 0 {
|
|
return
|
|
}
|
|
assert.Equal(t, tc.expected, names)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoveAnnotations(t *testing.T) {
|
|
testCases := map[string]struct {
|
|
node *yaml.RNode
|
|
removeAnnotations []kioutil.AnnotationKey
|
|
expectedAnnotations []kioutil.AnnotationKey
|
|
}{
|
|
"filter both kioutil annotations": {
|
|
node: yaml.MustParse(`
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: foo
|
|
annotations:
|
|
config.kubernetes.io/path: deployment.yaml
|
|
config.kubernetes.io/index: 0
|
|
`),
|
|
removeAnnotations: []kioutil.AnnotationKey{
|
|
kioutil.PathAnnotation,
|
|
kioutil.IndexAnnotation,
|
|
},
|
|
},
|
|
"filter only a subset of the annotations": {
|
|
node: yaml.MustParse(`
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: foo
|
|
annotations:
|
|
internal.config.kubernetes.io/path: deployment.yaml
|
|
internal.config.kubernetes.io/index: 0
|
|
`),
|
|
removeAnnotations: []kioutil.AnnotationKey{
|
|
kioutil.IndexAnnotation,
|
|
},
|
|
expectedAnnotations: []kioutil.AnnotationKey{
|
|
kioutil.PathAnnotation,
|
|
},
|
|
},
|
|
"filter none of the annotations": {
|
|
node: yaml.MustParse(`
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: foo
|
|
annotations:
|
|
internal.config.kubernetes.io/path: deployment.yaml
|
|
internal.config.kubernetes.io/index: 0
|
|
`),
|
|
removeAnnotations: []kioutil.AnnotationKey{},
|
|
expectedAnnotations: []kioutil.AnnotationKey{
|
|
kioutil.PathAnnotation,
|
|
kioutil.IndexAnnotation,
|
|
},
|
|
},
|
|
}
|
|
|
|
for tn, tc := range testCases {
|
|
t.Run(tn, func(t *testing.T) {
|
|
node := tc.node
|
|
err := RemoveAnnotations(node, tc.removeAnnotations...)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
for _, anno := range tc.removeAnnotations {
|
|
n, err := node.Pipe(yaml.GetAnnotation(anno))
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
assert.Nil(t, n)
|
|
}
|
|
for _, anno := range tc.expectedAnnotations {
|
|
n, err := node.Pipe(yaml.GetAnnotation(anno))
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
assert.NotNil(t, n)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func toUnstructured(gvk schema.GroupVersionKind, namespace string) *unstructured.Unstructured {
|
|
return &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": gvk.GroupVersion().String(),
|
|
"kind": gvk.Kind,
|
|
"metadata": map[string]interface{}{
|
|
"namespace": namespace,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func toCRDUnstructured(crdGvk schema.GroupVersionKind, crGvk schema.GroupVersionKind,
|
|
scope string) *unstructured.Unstructured {
|
|
return &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": crdGvk.GroupVersion().String(),
|
|
"kind": crdGvk.Kind,
|
|
"spec": map[string]interface{}{
|
|
"group": crGvk.Group,
|
|
"names": map[string]interface{}{
|
|
"kind": crGvk.Kind,
|
|
},
|
|
"scope": scope,
|
|
"versions": []interface{}{
|
|
map[string]interface{}{
|
|
"name": crGvk.Version,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func objMetaToUnstructured(id object.ObjMetadata) *unstructured.Unstructured {
|
|
return &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": fmt.Sprintf("%s/v1", id.GroupKind.Group),
|
|
"kind": id.GroupKind.Kind,
|
|
"metadata": map[string]interface{}{
|
|
"namespace": id.Namespace,
|
|
"name": id.Name,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func addAnnotation(t *testing.T, u *unstructured.Unstructured, name, val string) *unstructured.Unstructured {
|
|
annos, found, err := unstructured.NestedStringMap(u.Object, "metadata", "annotations")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !found {
|
|
annos = make(map[string]string)
|
|
}
|
|
annos[name] = val
|
|
err = unstructured.SetNestedStringMap(u.Object, annos, "metadata", "annotations")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return u
|
|
}
|