2020-09-16 14:19:51 +00:00
|
|
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
2020-07-30 00:22:25 +00:00
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
package issuerconfig
|
2020-07-30 00:22:25 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-30 14:39:15 +00:00
|
|
|
"errors"
|
2020-07-30 00:22:25 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/sclevine/spec"
|
|
|
|
"github.com/sclevine/spec/report"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2020-07-30 14:39:15 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
2020-07-30 00:22:25 +00:00
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
2020-07-30 21:34:13 +00:00
|
|
|
kubeinformers "k8s.io/client-go/informers"
|
2020-07-30 00:22:25 +00:00
|
|
|
kubernetesfake "k8s.io/client-go/kubernetes/fake"
|
|
|
|
coretesting "k8s.io/client-go/testing"
|
|
|
|
|
2020-09-18 21:38:45 +00:00
|
|
|
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
|
2020-09-18 19:56:24 +00:00
|
|
|
pinnipedfake "go.pinniped.dev/generated/1.19/client/clientset/versioned/fake"
|
|
|
|
pinnipedinformers "go.pinniped.dev/generated/1.19/client/informers/externalversions"
|
|
|
|
"go.pinniped.dev/internal/controllerlib"
|
|
|
|
"go.pinniped.dev/internal/here"
|
|
|
|
"go.pinniped.dev/internal/testutil"
|
2020-07-30 00:22:25 +00:00
|
|
|
)
|
|
|
|
|
2020-07-31 00:16:09 +00:00
|
|
|
func TestInformerFilters(t *testing.T) {
|
|
|
|
spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) {
|
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
|
|
|
const credentialIssuerConfigResourceName = "some-resource-name"
|
2020-07-31 00:16:09 +00:00
|
|
|
const installedInNamespace = "some-namespace"
|
|
|
|
|
|
|
|
var r *require.Assertions
|
2020-08-11 01:53:53 +00:00
|
|
|
var observableWithInformerOption *testutil.ObservableWithInformerOption
|
2020-08-28 15:59:09 +00:00
|
|
|
var configMapInformerFilter controllerlib.Filter
|
|
|
|
var credentialIssuerConfigInformerFilter controllerlib.Filter
|
2020-07-31 00:16:09 +00:00
|
|
|
|
|
|
|
it.Before(func() {
|
|
|
|
r = require.New(t)
|
2020-08-11 01:53:53 +00:00
|
|
|
observableWithInformerOption = testutil.NewObservableWithInformerOption()
|
2020-07-31 00:16:09 +00:00
|
|
|
configMapInformer := kubeinformers.NewSharedInformerFactory(nil, 0).Core().V1().ConfigMaps()
|
2020-09-18 21:38:45 +00:00
|
|
|
credentialIssuerConfigInformer := pinnipedinformers.NewSharedInformerFactory(nil, 0).Config().V1alpha1().CredentialIssuerConfigs()
|
2020-07-31 00:16:09 +00:00
|
|
|
_ = NewPublisherController(
|
|
|
|
installedInNamespace,
|
2020-09-18 23:39:58 +00:00
|
|
|
credentialIssuerConfigResourceName,
|
2020-07-31 00:16:09 +00:00
|
|
|
nil,
|
2020-08-03 14:17:11 +00:00
|
|
|
nil,
|
2020-07-31 00:16:09 +00:00
|
|
|
configMapInformer,
|
2020-08-21 16:55:44 +00:00
|
|
|
credentialIssuerConfigInformer,
|
2020-07-31 00:16:09 +00:00
|
|
|
observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters
|
|
|
|
)
|
2020-08-11 01:53:53 +00:00
|
|
|
configMapInformerFilter = observableWithInformerOption.GetFilterForInformer(configMapInformer)
|
2020-08-21 16:55:44 +00:00
|
|
|
credentialIssuerConfigInformerFilter = observableWithInformerOption.GetFilterForInformer(credentialIssuerConfigInformer)
|
2020-07-31 00:16:09 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
when("watching ConfigMap objects", func() {
|
2020-08-28 15:59:09 +00:00
|
|
|
var subject controllerlib.Filter
|
2020-07-31 00:16:09 +00:00
|
|
|
var target, wrongNamespace, wrongName, unrelated *corev1.ConfigMap
|
|
|
|
|
|
|
|
it.Before(func() {
|
|
|
|
subject = configMapInformerFilter
|
|
|
|
target = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"}}
|
|
|
|
wrongNamespace = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "wrong-namespace"}}
|
|
|
|
wrongName = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "kube-public"}}
|
|
|
|
unrelated = &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"}}
|
|
|
|
})
|
|
|
|
|
|
|
|
when("the target ConfigMap changes", func() {
|
|
|
|
it("returns true to trigger the sync method", func() {
|
|
|
|
r.True(subject.Add(target))
|
|
|
|
r.True(subject.Update(target, unrelated))
|
|
|
|
r.True(subject.Update(unrelated, target))
|
|
|
|
r.True(subject.Delete(target))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
when("a ConfigMap from another namespace changes", func() {
|
|
|
|
it("returns false to avoid triggering the sync method", func() {
|
|
|
|
r.False(subject.Add(wrongNamespace))
|
|
|
|
r.False(subject.Update(wrongNamespace, unrelated))
|
|
|
|
r.False(subject.Update(unrelated, wrongNamespace))
|
|
|
|
r.False(subject.Delete(wrongNamespace))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
when("a ConfigMap with a different name changes", func() {
|
|
|
|
it("returns false to avoid triggering the sync method", func() {
|
|
|
|
r.False(subject.Add(wrongName))
|
|
|
|
r.False(subject.Update(wrongName, unrelated))
|
|
|
|
r.False(subject.Update(unrelated, wrongName))
|
|
|
|
r.False(subject.Delete(wrongName))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
when("a ConfigMap with a different name and a different namespace changes", func() {
|
|
|
|
it("returns false to avoid triggering the sync method", func() {
|
|
|
|
r.False(subject.Add(unrelated))
|
|
|
|
r.False(subject.Update(unrelated, unrelated))
|
|
|
|
r.False(subject.Delete(unrelated))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("watching CredentialIssuerConfig objects", func() {
|
2020-08-28 15:59:09 +00:00
|
|
|
var subject controllerlib.Filter
|
2020-09-18 21:38:45 +00:00
|
|
|
var target, wrongNamespace, wrongName, unrelated *configv1alpha1.CredentialIssuerConfig
|
2020-07-31 00:16:09 +00:00
|
|
|
|
|
|
|
it.Before(func() {
|
2020-08-21 16:55:44 +00:00
|
|
|
subject = credentialIssuerConfigInformerFilter
|
2020-09-18 21:38:45 +00:00
|
|
|
target = &configv1alpha1.CredentialIssuerConfig{
|
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerConfigResourceName, Namespace: installedInNamespace},
|
2020-07-31 00:16:09 +00:00
|
|
|
}
|
2020-09-18 21:38:45 +00:00
|
|
|
wrongNamespace = &configv1alpha1.CredentialIssuerConfig{
|
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: credentialIssuerConfigResourceName, Namespace: "wrong-namespace"},
|
2020-07-31 00:16:09 +00:00
|
|
|
}
|
2020-09-18 21:38:45 +00:00
|
|
|
wrongName = &configv1alpha1.CredentialIssuerConfig{
|
2020-07-31 00:16:09 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: installedInNamespace},
|
|
|
|
}
|
2020-09-18 21:38:45 +00:00
|
|
|
unrelated = &configv1alpha1.CredentialIssuerConfig{
|
2020-07-31 00:16:09 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "wrong-name", Namespace: "wrong-namespace"},
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("the target CredentialIssuerConfig changes", func() {
|
2020-07-31 00:16:09 +00:00
|
|
|
it("returns true to trigger the sync method", func() {
|
|
|
|
r.True(subject.Add(target))
|
|
|
|
r.True(subject.Update(target, unrelated))
|
|
|
|
r.True(subject.Update(unrelated, target))
|
|
|
|
r.True(subject.Delete(target))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("a CredentialIssuerConfig from another namespace changes", func() {
|
2020-07-31 00:16:09 +00:00
|
|
|
it("returns false to avoid triggering the sync method", func() {
|
|
|
|
r.False(subject.Add(wrongNamespace))
|
|
|
|
r.False(subject.Update(wrongNamespace, unrelated))
|
|
|
|
r.False(subject.Update(unrelated, wrongNamespace))
|
|
|
|
r.False(subject.Delete(wrongNamespace))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("a CredentialIssuerConfig with a different name changes", func() {
|
2020-07-31 00:16:09 +00:00
|
|
|
it("returns false to avoid triggering the sync method", func() {
|
|
|
|
r.False(subject.Add(wrongName))
|
|
|
|
r.False(subject.Update(wrongName, unrelated))
|
|
|
|
r.False(subject.Update(unrelated, wrongName))
|
|
|
|
r.False(subject.Delete(wrongName))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("a CredentialIssuerConfig with a different name and a different namespace changes", func() {
|
2020-07-31 00:16:09 +00:00
|
|
|
it("returns false to avoid triggering the sync method", func() {
|
|
|
|
r.False(subject.Add(unrelated))
|
|
|
|
r.False(subject.Update(unrelated, unrelated))
|
|
|
|
r.False(subject.Delete(unrelated))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSync(t *testing.T) {
|
|
|
|
spec.Run(t, "Sync", func(t *testing.T, when spec.G, it spec.S) {
|
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
|
|
|
const credentialIssuerConfigResourceName = "some-resource-name"
|
2020-07-30 00:22:25 +00:00
|
|
|
const installedInNamespace = "some-namespace"
|
|
|
|
|
|
|
|
var r *require.Assertions
|
|
|
|
|
2020-08-28 15:59:09 +00:00
|
|
|
var subject controllerlib.Controller
|
2020-08-03 14:17:11 +00:00
|
|
|
var serverOverride *string
|
2020-07-30 21:34:13 +00:00
|
|
|
var kubeInformerClient *kubernetesfake.Clientset
|
2020-08-20 17:54:15 +00:00
|
|
|
var pinnipedInformerClient *pinnipedfake.Clientset
|
2020-07-30 21:34:13 +00:00
|
|
|
var kubeInformers kubeinformers.SharedInformerFactory
|
2020-08-20 17:54:15 +00:00
|
|
|
var pinnipedInformers pinnipedinformers.SharedInformerFactory
|
|
|
|
var pinnipedAPIClient *pinnipedfake.Clientset
|
2020-07-30 00:22:25 +00:00
|
|
|
var timeoutContext context.Context
|
|
|
|
var timeoutContextCancel context.CancelFunc
|
2020-08-28 15:59:09 +00:00
|
|
|
var syncContext *controllerlib.Context
|
2020-07-30 00:22:25 +00:00
|
|
|
|
2020-09-18 21:38:45 +00:00
|
|
|
var expectedCredentialIssuerConfig = func(expectedNamespace, expectedServerURL, expectedCAData string) (schema.GroupVersionResource, *configv1alpha1.CredentialIssuerConfig) {
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR := schema.GroupVersionResource{
|
2020-09-18 21:38:45 +00:00
|
|
|
Group: configv1alpha1.GroupName,
|
2020-07-30 00:22:25 +00:00
|
|
|
Version: "v1alpha1",
|
2020-08-21 16:55:44 +00:00
|
|
|
Resource: "credentialissuerconfigs",
|
2020-07-30 00:22:25 +00:00
|
|
|
}
|
2020-09-18 21:38:45 +00:00
|
|
|
expectedCredentialIssuerConfig := &configv1alpha1.CredentialIssuerConfig{
|
2020-07-30 00:22:25 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
|
|
|
Name: credentialIssuerConfigResourceName,
|
2020-07-30 00:22:25 +00:00
|
|
|
Namespace: expectedNamespace,
|
|
|
|
},
|
2020-09-18 21:38:45 +00:00
|
|
|
Status: configv1alpha1.CredentialIssuerConfigStatus{
|
|
|
|
Strategies: []configv1alpha1.CredentialIssuerConfigStrategy{},
|
|
|
|
KubeConfigInfo: &configv1alpha1.CredentialIssuerConfigKubeConfigInfo{
|
2020-08-22 00:00:42 +00:00
|
|
|
Server: expectedServerURL,
|
|
|
|
CertificateAuthorityData: expectedCAData,
|
|
|
|
},
|
2020-07-30 00:22:25 +00:00
|
|
|
},
|
|
|
|
}
|
2020-08-21 16:55:44 +00:00
|
|
|
return expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig
|
2020-07-30 00:22:25 +00:00
|
|
|
}
|
|
|
|
|
2020-07-30 21:34:13 +00:00
|
|
|
// Defer starting the informers until the last possible moment so that the
|
|
|
|
// nested Before's can keep adding things to the informer caches.
|
|
|
|
var startInformersAndController = func() {
|
2020-08-03 14:17:11 +00:00
|
|
|
// Set this at the last second to allow for injection of server override.
|
2020-07-30 21:34:13 +00:00
|
|
|
subject = NewPublisherController(
|
|
|
|
installedInNamespace,
|
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
|
|
|
credentialIssuerConfigResourceName,
|
2020-08-03 14:17:11 +00:00
|
|
|
serverOverride,
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedAPIClient,
|
2020-07-30 21:34:13 +00:00
|
|
|
kubeInformers.Core().V1().ConfigMaps(),
|
2020-09-18 21:38:45 +00:00
|
|
|
pinnipedInformers.Config().V1alpha1().CredentialIssuerConfigs(),
|
2020-08-28 15:59:09 +00:00
|
|
|
controllerlib.WithInformer,
|
2020-07-30 21:34:13 +00:00
|
|
|
)
|
|
|
|
|
2020-08-03 14:17:11 +00:00
|
|
|
// Set this at the last second to support calling subject.Name().
|
2020-08-28 15:59:09 +00:00
|
|
|
syncContext = &controllerlib.Context{
|
2020-07-30 00:22:25 +00:00
|
|
|
Context: timeoutContext,
|
|
|
|
Name: subject.Name(),
|
2020-08-28 15:59:09 +00:00
|
|
|
Key: controllerlib.Key{
|
2020-07-30 00:22:25 +00:00
|
|
|
Namespace: "kube-public",
|
|
|
|
Name: "cluster-info",
|
|
|
|
},
|
|
|
|
}
|
2020-08-03 14:17:11 +00:00
|
|
|
|
|
|
|
// Must start informers before calling TestRunSynchronously()
|
|
|
|
kubeInformers.Start(timeoutContext.Done())
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedInformers.Start(timeoutContext.Done())
|
2020-08-28 15:59:09 +00:00
|
|
|
controllerlib.TestRunSynchronously(t, subject)
|
2020-08-03 14:17:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
it.Before(func() {
|
|
|
|
r = require.New(t)
|
|
|
|
|
|
|
|
timeoutContext, timeoutContextCancel = context.WithTimeout(context.Background(), time.Second*3)
|
|
|
|
|
|
|
|
kubeInformerClient = kubernetesfake.NewSimpleClientset()
|
|
|
|
kubeInformers = kubeinformers.NewSharedInformerFactory(kubeInformerClient, 0)
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedAPIClient = pinnipedfake.NewSimpleClientset()
|
|
|
|
pinnipedInformerClient = pinnipedfake.NewSimpleClientset()
|
|
|
|
pinnipedInformers = pinnipedinformers.NewSharedInformerFactory(pinnipedInformerClient, 0)
|
2020-07-30 00:22:25 +00:00
|
|
|
})
|
|
|
|
|
2020-07-30 21:34:13 +00:00
|
|
|
it.After(func() {
|
|
|
|
timeoutContextCancel()
|
|
|
|
})
|
|
|
|
|
2020-07-30 01:18:42 +00:00
|
|
|
when("there is a cluster-info ConfigMap in the kube-public namespace", func() {
|
2020-07-30 00:22:25 +00:00
|
|
|
const caData = "c29tZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YQo=" // "some-certificate-authority-data" base64 encoded
|
|
|
|
const kubeServerURL = "https://some-server"
|
2020-07-30 01:18:42 +00:00
|
|
|
|
|
|
|
when("the ConfigMap has the expected `kubeconfig` top-level data key", func() {
|
|
|
|
it.Before(func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
clusterInfoConfigMap := &corev1.ConfigMap{
|
2020-07-30 01:18:42 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"},
|
|
|
|
// Note that go fmt puts tabs in our file, which we must remove from our configmap yaml below.
|
|
|
|
Data: map[string]string{
|
2020-09-12 01:15:24 +00:00
|
|
|
"kubeconfig": here.Docf(`
|
|
|
|
kind: Config
|
|
|
|
apiVersion: v1
|
|
|
|
clusters:
|
|
|
|
- name: ""
|
|
|
|
cluster:
|
|
|
|
certificate-authority-data: "%s"
|
|
|
|
server: "%s"`,
|
|
|
|
caData, kubeServerURL),
|
2020-07-30 01:18:42 +00:00
|
|
|
"uninteresting-key": "uninteresting-value",
|
|
|
|
},
|
|
|
|
}
|
2020-07-30 21:34:13 +00:00
|
|
|
err := kubeInformerClient.Tracker().Add(clusterInfoConfigMap)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
|
|
|
})
|
2020-07-30 00:22:25 +00:00
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("the CredentialIssuerConfig does not already exist", func() {
|
|
|
|
it("creates a CredentialIssuerConfig", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
2020-07-30 14:39:15 +00:00
|
|
|
installedInNamespace,
|
|
|
|
kubeServerURL,
|
|
|
|
caData,
|
|
|
|
)
|
2020-07-30 21:34:13 +00:00
|
|
|
|
|
|
|
r.Equal(
|
|
|
|
[]coretesting.Action{
|
|
|
|
coretesting.NewCreateAction(
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR,
|
2020-07-30 21:34:13 +00:00
|
|
|
installedInNamespace,
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfig,
|
2020-07-30 21:34:13 +00:00
|
|
|
),
|
|
|
|
},
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedAPIClient.Actions(),
|
2020-07-30 21:34:13 +00:00
|
|
|
)
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
2020-07-30 14:39:15 +00:00
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("creating the CredentialIssuerConfig fails", func() {
|
2020-07-30 14:39:15 +00:00
|
|
|
it.Before(func() {
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedAPIClient.PrependReactor(
|
2020-07-30 14:39:15 +00:00
|
|
|
"create",
|
2020-08-21 16:55:44 +00:00
|
|
|
"credentialissuerconfigs",
|
2020-07-30 14:39:15 +00:00
|
|
|
func(_ coretesting.Action) (bool, runtime.Object, error) {
|
|
|
|
return true, nil, errors.New("create failed")
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("returns the create error", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-08-28 00:11:10 +00:00
|
|
|
r.EqualError(err, "could not create or update credentialissuerconfig: create failed: create failed")
|
2020-07-30 14:39:15 +00:00
|
|
|
})
|
|
|
|
})
|
2020-08-03 14:17:11 +00:00
|
|
|
|
|
|
|
when("a server override is passed to the controller", func() {
|
|
|
|
it("uses the server override field", func() {
|
|
|
|
serverOverride = new(string)
|
|
|
|
*serverOverride = "https://some-server-override"
|
|
|
|
|
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-08-03 14:17:11 +00:00
|
|
|
r.NoError(err)
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
2020-08-03 14:17:11 +00:00
|
|
|
installedInNamespace,
|
|
|
|
kubeServerURL,
|
|
|
|
caData,
|
|
|
|
)
|
2020-08-22 00:00:42 +00:00
|
|
|
expectedCredentialIssuerConfig.Status.KubeConfigInfo.Server = "https://some-server-override"
|
2020-08-03 14:17:11 +00:00
|
|
|
|
|
|
|
r.Equal(
|
|
|
|
[]coretesting.Action{
|
|
|
|
coretesting.NewCreateAction(
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR,
|
2020-08-03 14:17:11 +00:00
|
|
|
installedInNamespace,
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfig,
|
2020-08-03 14:17:11 +00:00
|
|
|
),
|
|
|
|
},
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedAPIClient.Actions(),
|
2020-08-03 14:17:11 +00:00
|
|
|
)
|
|
|
|
})
|
|
|
|
})
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("the CredentialIssuerConfig already exists", func() {
|
|
|
|
when("the CredentialIssuerConfig is already up to date according to the data in the ConfigMap", func() {
|
2020-07-30 01:18:42 +00:00
|
|
|
it.Before(func() {
|
2020-08-21 16:55:44 +00:00
|
|
|
_, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
2020-07-30 14:39:15 +00:00
|
|
|
installedInNamespace,
|
|
|
|
kubeServerURL,
|
|
|
|
caData,
|
|
|
|
)
|
2020-08-21 16:55:44 +00:00
|
|
|
err := pinnipedInformerClient.Tracker().Add(expectedCredentialIssuerConfig)
|
2020-07-30 14:39:15 +00:00
|
|
|
r.NoError(err)
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
it("does not update the CredentialIssuerConfig to avoid unnecessary etcd writes/api calls", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
2020-07-30 14:39:15 +00:00
|
|
|
|
2020-08-20 17:54:15 +00:00
|
|
|
r.Empty(pinnipedAPIClient.Actions())
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("the CredentialIssuerConfig is stale compared to the data in the ConfigMap", func() {
|
2020-07-30 01:18:42 +00:00
|
|
|
it.Before(func() {
|
2020-08-21 16:55:44 +00:00
|
|
|
_, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
2020-07-30 14:39:15 +00:00
|
|
|
installedInNamespace,
|
|
|
|
kubeServerURL,
|
|
|
|
caData,
|
|
|
|
)
|
2020-08-22 00:00:42 +00:00
|
|
|
expectedCredentialIssuerConfig.Status.KubeConfigInfo.Server = "https://some-other-server"
|
2020-08-21 16:55:44 +00:00
|
|
|
r.NoError(pinnipedInformerClient.Tracker().Add(expectedCredentialIssuerConfig))
|
|
|
|
r.NoError(pinnipedAPIClient.Tracker().Add(expectedCredentialIssuerConfig))
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
it("updates the existing CredentialIssuerConfig", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-07-30 14:39:15 +00:00
|
|
|
r.NoError(err)
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR, expectedCredentialIssuerConfig := expectedCredentialIssuerConfig(
|
2020-07-30 14:39:15 +00:00
|
|
|
installedInNamespace,
|
|
|
|
kubeServerURL,
|
|
|
|
caData,
|
|
|
|
)
|
|
|
|
expectedActions := []coretesting.Action{
|
|
|
|
coretesting.NewUpdateAction(
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfigGVR,
|
2020-07-30 14:39:15 +00:00
|
|
|
installedInNamespace,
|
2020-08-21 16:55:44 +00:00
|
|
|
expectedCredentialIssuerConfig,
|
2020-07-30 14:39:15 +00:00
|
|
|
),
|
|
|
|
}
|
2020-08-20 17:54:15 +00:00
|
|
|
r.Equal(expectedActions, pinnipedAPIClient.Actions())
|
2020-07-30 14:39:15 +00:00
|
|
|
})
|
|
|
|
|
2020-08-21 16:55:44 +00:00
|
|
|
when("updating the CredentialIssuerConfig fails", func() {
|
2020-07-30 14:39:15 +00:00
|
|
|
it.Before(func() {
|
2020-08-20 17:54:15 +00:00
|
|
|
pinnipedAPIClient.PrependReactor(
|
2020-07-30 14:39:15 +00:00
|
|
|
"update",
|
2020-08-21 16:55:44 +00:00
|
|
|
"credentialissuerconfigs",
|
2020-07-30 14:39:15 +00:00
|
|
|
func(_ coretesting.Action) (bool, runtime.Object, error) {
|
|
|
|
return true, nil, errors.New("update failed")
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("returns the update error", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-08-28 00:11:10 +00:00
|
|
|
r.EqualError(err, "could not create or update credentialissuerconfig: update failed")
|
2020-07-30 14:39:15 +00:00
|
|
|
})
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
2020-07-30 00:22:25 +00:00
|
|
|
|
2020-07-30 01:18:42 +00:00
|
|
|
when("the ConfigMap is missing the expected `kubeconfig` top-level data key", func() {
|
|
|
|
it.Before(func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
clusterInfoConfigMap := &corev1.ConfigMap{
|
2020-07-30 01:18:42 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"},
|
|
|
|
Data: map[string]string{
|
|
|
|
"these are not the droids you're looking for": "uninteresting-value",
|
|
|
|
},
|
2020-07-30 00:22:25 +00:00
|
|
|
}
|
2020-07-30 21:34:13 +00:00
|
|
|
err := kubeInformerClient.Tracker().Add(clusterInfoConfigMap)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
|
|
|
})
|
2020-07-30 00:22:25 +00:00
|
|
|
|
2020-07-30 01:18:42 +00:00
|
|
|
it("keeps waiting for it to exist", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
2020-08-20 17:54:15 +00:00
|
|
|
r.Empty(pinnipedAPIClient.Actions())
|
2020-07-30 00:22:25 +00:00
|
|
|
})
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
2020-08-27 14:43:13 +00:00
|
|
|
|
|
|
|
when("the ConfigMap does not have a valid kubeconfig", func() {
|
|
|
|
it.Before(func() {
|
|
|
|
clusterInfoConfigMap := &corev1.ConfigMap{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{Name: "cluster-info", Namespace: "kube-public"},
|
|
|
|
Data: map[string]string{
|
|
|
|
"kubeconfig": "this is an invalid kubeconfig",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := kubeInformerClient.Tracker().Add(clusterInfoConfigMap)
|
|
|
|
r.NoError(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("keeps waiting for it to be properly formatted", func() {
|
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-08-27 14:43:13 +00:00
|
|
|
r.NoError(err)
|
|
|
|
r.Empty(pinnipedAPIClient.Actions())
|
|
|
|
})
|
|
|
|
})
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
when("there is not a cluster-info ConfigMap in the kube-public namespace", func() {
|
|
|
|
it.Before(func() {
|
|
|
|
unrelatedConfigMap := &corev1.ConfigMap{
|
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
|
|
Name: "oops this is not the cluster-info ConfigMap",
|
|
|
|
Namespace: "kube-public",
|
|
|
|
},
|
|
|
|
}
|
2020-07-30 21:34:13 +00:00
|
|
|
err := kubeInformerClient.Tracker().Add(unrelatedConfigMap)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("keeps waiting for one", func() {
|
2020-07-30 21:34:13 +00:00
|
|
|
startInformersAndController()
|
2020-08-28 15:59:09 +00:00
|
|
|
err := controllerlib.TestSync(t, subject, *syncContext)
|
2020-07-30 01:18:42 +00:00
|
|
|
r.NoError(err)
|
2020-08-20 17:54:15 +00:00
|
|
|
r.Empty(pinnipedAPIClient.Actions())
|
2020-07-30 01:18:42 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
2020-07-30 00:22:25 +00:00
|
|
|
}
|