|
|
|
|
@ -17,7 +17,10 @@ limitations under the License.
|
|
|
|
|
package kube // import "helm.sh/helm/v3/pkg/kube"
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
@ -27,11 +30,14 @@ import (
|
|
|
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
|
|
|
batchv1 "k8s.io/api/batch/v1"
|
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
|
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
|
|
|
"k8s.io/client-go/dynamic"
|
|
|
|
|
dynamicfake "k8s.io/client-go/dynamic/fake"
|
|
|
|
|
"k8s.io/kubectl/pkg/scheme"
|
|
|
|
|
)
|
|
|
|
|
@ -153,6 +159,83 @@ spec:
|
|
|
|
|
- containerPort: 80
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var podNamespace1Manifest = `
|
|
|
|
|
apiVersion: v1
|
|
|
|
|
kind: Pod
|
|
|
|
|
metadata:
|
|
|
|
|
name: pod-ns1
|
|
|
|
|
namespace: namespace-1
|
|
|
|
|
status:
|
|
|
|
|
conditions:
|
|
|
|
|
- type: Ready
|
|
|
|
|
status: "True"
|
|
|
|
|
phase: Running
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var podNamespace2Manifest = `
|
|
|
|
|
apiVersion: v1
|
|
|
|
|
kind: Pod
|
|
|
|
|
metadata:
|
|
|
|
|
name: pod-ns2
|
|
|
|
|
namespace: namespace-2
|
|
|
|
|
status:
|
|
|
|
|
conditions:
|
|
|
|
|
- type: Ready
|
|
|
|
|
status: "True"
|
|
|
|
|
phase: Running
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var podNamespace1NoStatusManifest = `
|
|
|
|
|
apiVersion: v1
|
|
|
|
|
kind: Pod
|
|
|
|
|
metadata:
|
|
|
|
|
name: pod-ns1
|
|
|
|
|
namespace: namespace-1
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var jobNamespace1CompleteManifest = `
|
|
|
|
|
apiVersion: batch/v1
|
|
|
|
|
kind: Job
|
|
|
|
|
metadata:
|
|
|
|
|
name: job-ns1
|
|
|
|
|
namespace: namespace-1
|
|
|
|
|
generation: 1
|
|
|
|
|
status:
|
|
|
|
|
succeeded: 1
|
|
|
|
|
active: 0
|
|
|
|
|
conditions:
|
|
|
|
|
- type: Complete
|
|
|
|
|
status: "True"
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var podNamespace2SucceededManifest = `
|
|
|
|
|
apiVersion: v1
|
|
|
|
|
kind: Pod
|
|
|
|
|
metadata:
|
|
|
|
|
name: pod-ns2
|
|
|
|
|
namespace: namespace-2
|
|
|
|
|
status:
|
|
|
|
|
phase: Succeeded
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var clusterRoleManifest = `
|
|
|
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
|
|
|
kind: ClusterRole
|
|
|
|
|
metadata:
|
|
|
|
|
name: test-cluster-role
|
|
|
|
|
rules:
|
|
|
|
|
- apiGroups: [""]
|
|
|
|
|
resources: ["pods"]
|
|
|
|
|
verbs: ["get", "list"]
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var namespaceManifest = `
|
|
|
|
|
apiVersion: v1
|
|
|
|
|
kind: Namespace
|
|
|
|
|
metadata:
|
|
|
|
|
name: test-namespace
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource {
|
|
|
|
|
t.Helper()
|
|
|
|
|
gvk := obj.GroupVersionKind()
|
|
|
|
|
@ -232,11 +315,11 @@ func TestStatusWaitForDelete(t *testing.T) {
|
|
|
|
|
for _, objToDelete := range objsToDelete {
|
|
|
|
|
u := objToDelete.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
go func() {
|
|
|
|
|
go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) {
|
|
|
|
|
time.Sleep(timeUntilPodDelete)
|
|
|
|
|
err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}()
|
|
|
|
|
}(gvr, u)
|
|
|
|
|
}
|
|
|
|
|
resourceList := getResourceListFromRuntimeObjs(t, c, objsToCreate)
|
|
|
|
|
err := statusWaiter.WaitForDelete(resourceList, timeout)
|
|
|
|
|
@ -448,3 +531,413 @@ func TestWatchForReady(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStatusWaitMultipleNamespaces(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
objManifests []string
|
|
|
|
|
expectErrs []error
|
|
|
|
|
testFunc func(statusWaiter, ResourceList, time.Duration) error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "pods in multiple namespaces",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "hooks in multiple namespaces",
|
|
|
|
|
objManifests: []string{jobNamespace1CompleteManifest, podNamespace2SucceededManifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WatchUntilReady(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error when resource not ready in one namespace",
|
|
|
|
|
objManifests: []string{podNamespace1NoStatusManifest, podNamespace2Manifest},
|
|
|
|
|
expectErrs: []error{errors.New("resource not ready, name: pod-ns1, kind: Pod, status: InProgress"), errors.New("context deadline exceeded")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "delete resources in multiple namespaces",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WaitForDelete(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "cluster-scoped resources work correctly with unrestricted permissions",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, clusterRoleManifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "namespace-scoped and cluster-scoped resources work together",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest, clusterRoleManifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "delete cluster-scoped resources works correctly",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, namespaceManifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WaitForDelete(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "watch cluster-scoped resources works correctly",
|
|
|
|
|
objManifests: []string{clusterRoleManifest},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WatchUntilReady(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
c := newTestClient(t)
|
|
|
|
|
fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
|
|
|
|
|
fakeMapper := testutil.NewFakeRESTMapper(
|
|
|
|
|
v1.SchemeGroupVersion.WithKind("Pod"),
|
|
|
|
|
batchv1.SchemeGroupVersion.WithKind("Job"),
|
|
|
|
|
schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"),
|
|
|
|
|
v1.SchemeGroupVersion.WithKind("Namespace"),
|
|
|
|
|
)
|
|
|
|
|
sw := statusWaiter{
|
|
|
|
|
client: fakeClient,
|
|
|
|
|
restMapper: fakeMapper,
|
|
|
|
|
}
|
|
|
|
|
objs := getRuntimeObjFromManifests(t, tt.objManifests)
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
|
u := obj.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
err := fakeClient.Tracker().Create(gvr, u, u.GetNamespace())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strings.Contains(tt.name, "delete") {
|
|
|
|
|
timeUntilDelete := time.Millisecond * 500
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
|
u := obj.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) {
|
|
|
|
|
time.Sleep(timeUntilDelete)
|
|
|
|
|
err := fakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}(gvr, u)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resourceList := getResourceListFromRuntimeObjs(t, c, objs)
|
|
|
|
|
err := tt.testFunc(sw, resourceList, time.Second*3)
|
|
|
|
|
if tt.expectErrs != nil {
|
|
|
|
|
assert.EqualError(t, err, errors.Join(tt.expectErrs...).Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type restrictedDynamicClient struct {
|
|
|
|
|
dynamic.Interface
|
|
|
|
|
allowedNamespaces map[string]bool
|
|
|
|
|
clusterScopedListAttempted bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func newRestrictedDynamicClient(baseClient dynamic.Interface, allowedNamespaces []string) *restrictedDynamicClient {
|
|
|
|
|
allowed := make(map[string]bool)
|
|
|
|
|
for _, ns := range allowedNamespaces {
|
|
|
|
|
allowed[ns] = true
|
|
|
|
|
}
|
|
|
|
|
return &restrictedDynamicClient{
|
|
|
|
|
Interface: baseClient,
|
|
|
|
|
allowedNamespaces: allowed,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *restrictedDynamicClient) Resource(resource schema.GroupVersionResource) dynamic.NamespaceableResourceInterface {
|
|
|
|
|
return &restrictedNamespaceableResource{
|
|
|
|
|
NamespaceableResourceInterface: r.Interface.Resource(resource),
|
|
|
|
|
allowedNamespaces: r.allowedNamespaces,
|
|
|
|
|
clusterScopedListAttempted: &r.clusterScopedListAttempted,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type restrictedNamespaceableResource struct {
|
|
|
|
|
dynamic.NamespaceableResourceInterface
|
|
|
|
|
allowedNamespaces map[string]bool
|
|
|
|
|
clusterScopedListAttempted *bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *restrictedNamespaceableResource) Namespace(ns string) dynamic.ResourceInterface {
|
|
|
|
|
return &restrictedResource{
|
|
|
|
|
ResourceInterface: r.NamespaceableResourceInterface.Namespace(ns),
|
|
|
|
|
namespace: ns,
|
|
|
|
|
allowedNamespaces: r.allowedNamespaces,
|
|
|
|
|
clusterScopedListAttempted: r.clusterScopedListAttempted,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *restrictedNamespaceableResource) List(_ context.Context, _ metav1.ListOptions) (*unstructured.UnstructuredList, error) {
|
|
|
|
|
*r.clusterScopedListAttempted = true
|
|
|
|
|
return nil, apierrors.NewForbidden(
|
|
|
|
|
schema.GroupResource{Resource: "pods"},
|
|
|
|
|
"",
|
|
|
|
|
fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type restrictedResource struct {
|
|
|
|
|
dynamic.ResourceInterface
|
|
|
|
|
namespace string
|
|
|
|
|
allowedNamespaces map[string]bool
|
|
|
|
|
clusterScopedListAttempted *bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *restrictedResource) List(ctx context.Context, opts metav1.ListOptions) (*unstructured.UnstructuredList, error) {
|
|
|
|
|
if r.namespace == "" {
|
|
|
|
|
*r.clusterScopedListAttempted = true
|
|
|
|
|
return nil, apierrors.NewForbidden(
|
|
|
|
|
schema.GroupResource{Resource: "pods"},
|
|
|
|
|
"",
|
|
|
|
|
fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources"),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if !r.allowedNamespaces[r.namespace] {
|
|
|
|
|
return nil, apierrors.NewForbidden(
|
|
|
|
|
schema.GroupResource{Resource: "pods"},
|
|
|
|
|
"",
|
|
|
|
|
fmt.Errorf("user does not have LIST permissions in namespace %q", r.namespace),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
return r.ResourceInterface.List(ctx, opts)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStatusWaitRestrictedRBAC(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
objManifests []string
|
|
|
|
|
allowedNamespaces []string
|
|
|
|
|
expectErrs []error
|
|
|
|
|
testFunc func(statusWaiter, ResourceList, time.Duration) error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "pods in multiple namespaces with namespace permissions",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1", "namespace-2"},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "delete pods in multiple namespaces with namespace permissions",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1", "namespace-2"},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WaitForDelete(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "hooks in multiple namespaces with namespace permissions",
|
|
|
|
|
objManifests: []string{jobNamespace1CompleteManifest, podNamespace2SucceededManifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1", "namespace-2"},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WatchUntilReady(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error when cluster-scoped resource included",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, clusterRoleManifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error when deleting cluster-scoped resource",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, namespaceManifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WaitForDelete(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error when accessing disallowed namespace",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have LIST permissions in namespace %q", "namespace-2")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
c := newTestClient(t)
|
|
|
|
|
baseFakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
|
|
|
|
|
fakeMapper := testutil.NewFakeRESTMapper(
|
|
|
|
|
v1.SchemeGroupVersion.WithKind("Pod"),
|
|
|
|
|
batchv1.SchemeGroupVersion.WithKind("Job"),
|
|
|
|
|
schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"),
|
|
|
|
|
v1.SchemeGroupVersion.WithKind("Namespace"),
|
|
|
|
|
)
|
|
|
|
|
restrictedClient := newRestrictedDynamicClient(baseFakeClient, tt.allowedNamespaces)
|
|
|
|
|
sw := statusWaiter{
|
|
|
|
|
client: restrictedClient,
|
|
|
|
|
restMapper: fakeMapper,
|
|
|
|
|
}
|
|
|
|
|
objs := getRuntimeObjFromManifests(t, tt.objManifests)
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
|
u := obj.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strings.Contains(tt.name, "delet") {
|
|
|
|
|
timeUntilDelete := time.Millisecond * 500
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
|
u := obj.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) {
|
|
|
|
|
time.Sleep(timeUntilDelete)
|
|
|
|
|
err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}(gvr, u)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resourceList := getResourceListFromRuntimeObjs(t, c, objs)
|
|
|
|
|
err := tt.testFunc(sw, resourceList, time.Second*3)
|
|
|
|
|
if tt.expectErrs != nil {
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
for _, expectedErr := range tt.expectErrs {
|
|
|
|
|
assert.Contains(t, err.Error(), expectedErr.Error())
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.False(t, restrictedClient.clusterScopedListAttempted)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStatusWaitMixedResources(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
objManifests []string
|
|
|
|
|
allowedNamespaces []string
|
|
|
|
|
expectErrs []error
|
|
|
|
|
testFunc func(statusWaiter, ResourceList, time.Duration) error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "wait succeeds with namespace-scoped resources only",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1", "namespace-2"},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "wait fails when cluster-scoped resource included",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, clusterRoleManifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "waitForDelete fails when cluster-scoped resource included",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, clusterRoleManifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.WaitForDelete(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "wait fails when namespace resource included",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, namespaceManifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have cluster-wide LIST permissions for cluster-scoped resources")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "error when accessing disallowed namespace",
|
|
|
|
|
objManifests: []string{podNamespace1Manifest, podNamespace2Manifest},
|
|
|
|
|
allowedNamespaces: []string{"namespace-1"},
|
|
|
|
|
expectErrs: []error{fmt.Errorf("user does not have LIST permissions in namespace %q", "namespace-2")},
|
|
|
|
|
testFunc: func(sw statusWaiter, rl ResourceList, timeout time.Duration) error {
|
|
|
|
|
return sw.Wait(rl, timeout)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
t.Parallel()
|
|
|
|
|
c := newTestClient(t)
|
|
|
|
|
baseFakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
|
|
|
|
|
fakeMapper := testutil.NewFakeRESTMapper(
|
|
|
|
|
v1.SchemeGroupVersion.WithKind("Pod"),
|
|
|
|
|
batchv1.SchemeGroupVersion.WithKind("Job"),
|
|
|
|
|
schema.GroupVersion{Group: "rbac.authorization.k8s.io", Version: "v1"}.WithKind("ClusterRole"),
|
|
|
|
|
v1.SchemeGroupVersion.WithKind("Namespace"),
|
|
|
|
|
)
|
|
|
|
|
restrictedClient := newRestrictedDynamicClient(baseFakeClient, tt.allowedNamespaces)
|
|
|
|
|
sw := statusWaiter{
|
|
|
|
|
client: restrictedClient,
|
|
|
|
|
restMapper: fakeMapper,
|
|
|
|
|
}
|
|
|
|
|
objs := getRuntimeObjFromManifests(t, tt.objManifests)
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
|
u := obj.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
err := baseFakeClient.Tracker().Create(gvr, u, u.GetNamespace())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strings.Contains(tt.name, "delet") {
|
|
|
|
|
timeUntilDelete := time.Millisecond * 500
|
|
|
|
|
for _, obj := range objs {
|
|
|
|
|
u := obj.(*unstructured.Unstructured)
|
|
|
|
|
gvr := getGVR(t, fakeMapper, u)
|
|
|
|
|
go func(gvr schema.GroupVersionResource, u *unstructured.Unstructured) {
|
|
|
|
|
time.Sleep(timeUntilDelete)
|
|
|
|
|
err := baseFakeClient.Tracker().Delete(gvr, u.GetNamespace(), u.GetName())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}(gvr, u)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resourceList := getResourceListFromRuntimeObjs(t, c, objs)
|
|
|
|
|
err := tt.testFunc(sw, resourceList, time.Second*3)
|
|
|
|
|
if tt.expectErrs != nil {
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
for _, expectedErr := range tt.expectErrs {
|
|
|
|
|
assert.Contains(t, err.Error(), expectedErr.Error())
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.False(t, restrictedClient.clusterScopedListAttempted)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|