mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 18:43:34 +00:00
vendor files
This commit is contained in:
85
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/BUILD
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/BUILD
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"serviceaccounts_controller.go",
|
||||
"tokengetter.go",
|
||||
"tokens_controller.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/serviceaccount",
|
||||
deps = [
|
||||
"//pkg/apis/core/v1:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/registry/core/secret:go_default_library",
|
||||
"//pkg/registry/core/secret/storage:go_default_library",
|
||||
"//pkg/registry/core/serviceaccount:go_default_library",
|
||||
"//pkg/registry/core/serviceaccount/storage:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//pkg/util/metrics:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/retry:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"serviceaccounts_controller_test.go",
|
||||
"tokens_controller_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/serviceaccount",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
7
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/OWNERS
generated
vendored
Executable file
7
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/OWNERS
generated
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
approvers:
|
||||
- liggitt
|
||||
- deads2k
|
||||
reviewers:
|
||||
- liggitt
|
||||
- deads2k
|
||||
- enj
|
19
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package serviceaccount provides implementations
|
||||
// to manage service accounts and service account tokens
|
||||
package serviceaccount // import "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
221
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller.go
generated
vendored
Normal file
221
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller.go
generated
vendored
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/util/metrics"
|
||||
)
|
||||
|
||||
// ServiceAccountsControllerOptions contains options for running a ServiceAccountsController
|
||||
type ServiceAccountsControllerOptions struct {
|
||||
// ServiceAccounts is the list of service accounts to ensure exist in every namespace
|
||||
ServiceAccounts []v1.ServiceAccount
|
||||
|
||||
// ServiceAccountResync is the interval between full resyncs of ServiceAccounts.
|
||||
// If non-zero, all service accounts will be re-listed this often.
|
||||
// Otherwise, re-list will be delayed as long as possible (until the watch is closed or times out).
|
||||
ServiceAccountResync time.Duration
|
||||
|
||||
// NamespaceResync is the interval between full resyncs of Namespaces.
|
||||
// If non-zero, all namespaces will be re-listed this often.
|
||||
// Otherwise, re-list will be delayed as long as possible (until the watch is closed or times out).
|
||||
NamespaceResync time.Duration
|
||||
}
|
||||
|
||||
func DefaultServiceAccountsControllerOptions() ServiceAccountsControllerOptions {
|
||||
return ServiceAccountsControllerOptions{
|
||||
ServiceAccounts: []v1.ServiceAccount{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: "default"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewServiceAccountsController returns a new *ServiceAccountsController.
|
||||
func NewServiceAccountsController(saInformer coreinformers.ServiceAccountInformer, nsInformer coreinformers.NamespaceInformer, cl clientset.Interface, options ServiceAccountsControllerOptions) (*ServiceAccountsController, error) {
|
||||
e := &ServiceAccountsController{
|
||||
client: cl,
|
||||
serviceAccountsToEnsure: options.ServiceAccounts,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "serviceaccount"),
|
||||
}
|
||||
if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil {
|
||||
if err := metrics.RegisterMetricAndTrackRateLimiterUsage("serviceaccount_controller", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
saInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: e.serviceAccountDeleted,
|
||||
})
|
||||
e.saLister = saInformer.Lister()
|
||||
e.saListerSynced = saInformer.Informer().HasSynced
|
||||
|
||||
nsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: e.namespaceAdded,
|
||||
UpdateFunc: e.namespaceUpdated,
|
||||
})
|
||||
e.nsLister = nsInformer.Lister()
|
||||
e.nsListerSynced = nsInformer.Informer().HasSynced
|
||||
|
||||
e.syncHandler = e.syncNamespace
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// ServiceAccountsController manages ServiceAccount objects inside Namespaces
|
||||
type ServiceAccountsController struct {
|
||||
client clientset.Interface
|
||||
serviceAccountsToEnsure []v1.ServiceAccount
|
||||
|
||||
// To allow injection for testing.
|
||||
syncHandler func(key string) error
|
||||
|
||||
saLister corelisters.ServiceAccountLister
|
||||
saListerSynced cache.InformerSynced
|
||||
|
||||
nsLister corelisters.NamespaceLister
|
||||
nsListerSynced cache.InformerSynced
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
}
|
||||
|
||||
func (c *ServiceAccountsController) Run(workers int, stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
glog.Infof("Starting service account controller")
|
||||
defer glog.Infof("Shutting down service account controller")
|
||||
|
||||
if !controller.WaitForCacheSync("service account", stopCh, c.saListerSynced, c.nsListerSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(c.runWorker, time.Second, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
// serviceAccountDeleted reacts to a ServiceAccount deletion by recreating a default ServiceAccount in the namespace if needed
|
||||
func (c *ServiceAccountsController) serviceAccountDeleted(obj interface{}) {
|
||||
sa, ok := obj.(*v1.ServiceAccount)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj))
|
||||
return
|
||||
}
|
||||
sa, ok = tombstone.Obj.(*v1.ServiceAccount)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a ServiceAccount %#v", obj))
|
||||
return
|
||||
}
|
||||
}
|
||||
c.queue.Add(sa.Namespace)
|
||||
}
|
||||
|
||||
// namespaceAdded reacts to a Namespace creation by creating a default ServiceAccount object
|
||||
func (c *ServiceAccountsController) namespaceAdded(obj interface{}) {
|
||||
namespace := obj.(*v1.Namespace)
|
||||
c.queue.Add(namespace.Name)
|
||||
}
|
||||
|
||||
// namespaceUpdated reacts to a Namespace update (or re-list) by creating a default ServiceAccount in the namespace if needed
|
||||
func (c *ServiceAccountsController) namespaceUpdated(oldObj interface{}, newObj interface{}) {
|
||||
newNamespace := newObj.(*v1.Namespace)
|
||||
c.queue.Add(newNamespace.Name)
|
||||
}
|
||||
|
||||
func (c *ServiceAccountsController) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
|
||||
func (c *ServiceAccountsController) processNextWorkItem() bool {
|
||||
key, quit := c.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer c.queue.Done(key)
|
||||
|
||||
err := c.syncHandler(key.(string))
|
||||
if err == nil {
|
||||
c.queue.Forget(key)
|
||||
return true
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err))
|
||||
c.queue.AddRateLimited(key)
|
||||
|
||||
return true
|
||||
}
|
||||
func (c *ServiceAccountsController) syncNamespace(key string) error {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
glog.V(4).Infof("Finished syncing namespace %q (%v)", key, time.Now().Sub(startTime))
|
||||
}()
|
||||
|
||||
ns, err := c.nsLister.Get(key)
|
||||
if apierrs.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ns.Status.Phase != v1.NamespaceActive {
|
||||
// If namespace is not active, we shouldn't try to create anything
|
||||
return nil
|
||||
}
|
||||
|
||||
createFailures := []error{}
|
||||
for i := range c.serviceAccountsToEnsure {
|
||||
sa := c.serviceAccountsToEnsure[i]
|
||||
switch _, err := c.saLister.ServiceAccounts(ns.Name).Get(sa.Name); {
|
||||
case err == nil:
|
||||
continue
|
||||
case apierrs.IsNotFound(err):
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
// this is only safe because we never read it and we always write it
|
||||
// TODO eliminate this once the fake client can handle creation without NS
|
||||
sa.Namespace = ns.Name
|
||||
|
||||
if _, err := c.client.CoreV1().ServiceAccounts(ns.Name).Create(&sa); err != nil && !apierrs.IsAlreadyExists(err) {
|
||||
createFailures = append(createFailures, err)
|
||||
}
|
||||
}
|
||||
|
||||
return utilerrors.Flatten(utilerrors.NewAggregate(createFailures))
|
||||
}
|
242
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller_test.go
generated
vendored
Normal file
242
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/serviceaccounts_controller_test.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
type serverResponse struct {
|
||||
statusCode int
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
func TestServiceAccountCreation(t *testing.T) {
|
||||
ns := metav1.NamespaceDefault
|
||||
|
||||
defaultName := "default"
|
||||
managedName := "managed"
|
||||
|
||||
activeNS := &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: ns},
|
||||
Status: v1.NamespaceStatus{
|
||||
Phase: v1.NamespaceActive,
|
||||
},
|
||||
}
|
||||
terminatingNS := &v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: ns},
|
||||
Status: v1.NamespaceStatus{
|
||||
Phase: v1.NamespaceTerminating,
|
||||
},
|
||||
}
|
||||
defaultServiceAccount := &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: defaultName,
|
||||
Namespace: ns,
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
}
|
||||
managedServiceAccount := &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: managedName,
|
||||
Namespace: ns,
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
}
|
||||
unmanagedServiceAccount := &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "other-unmanaged",
|
||||
Namespace: ns,
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
}
|
||||
|
||||
testcases := map[string]struct {
|
||||
ExistingNamespace *v1.Namespace
|
||||
ExistingServiceAccounts []*v1.ServiceAccount
|
||||
|
||||
AddedNamespace *v1.Namespace
|
||||
UpdatedNamespace *v1.Namespace
|
||||
DeletedServiceAccount *v1.ServiceAccount
|
||||
|
||||
ExpectCreatedServiceAccounts []string
|
||||
}{
|
||||
"new active namespace missing serviceaccounts": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{},
|
||||
AddedNamespace: activeNS,
|
||||
ExpectCreatedServiceAccounts: sets.NewString(defaultName, managedName).List(),
|
||||
},
|
||||
"new active namespace missing serviceaccount": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{managedServiceAccount},
|
||||
AddedNamespace: activeNS,
|
||||
ExpectCreatedServiceAccounts: []string{defaultName},
|
||||
},
|
||||
"new active namespace with serviceaccounts": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{defaultServiceAccount, managedServiceAccount},
|
||||
AddedNamespace: activeNS,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
|
||||
"new terminating namespace": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{},
|
||||
AddedNamespace: terminatingNS,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
|
||||
"updated active namespace missing serviceaccounts": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{},
|
||||
UpdatedNamespace: activeNS,
|
||||
ExpectCreatedServiceAccounts: sets.NewString(defaultName, managedName).List(),
|
||||
},
|
||||
"updated active namespace missing serviceaccount": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{defaultServiceAccount},
|
||||
UpdatedNamespace: activeNS,
|
||||
ExpectCreatedServiceAccounts: []string{managedName},
|
||||
},
|
||||
"updated active namespace with serviceaccounts": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{defaultServiceAccount, managedServiceAccount},
|
||||
UpdatedNamespace: activeNS,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
"updated terminating namespace": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{},
|
||||
UpdatedNamespace: terminatingNS,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
|
||||
"deleted serviceaccount without namespace": {
|
||||
DeletedServiceAccount: defaultServiceAccount,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
"deleted serviceaccount with active namespace": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{managedServiceAccount},
|
||||
ExistingNamespace: activeNS,
|
||||
DeletedServiceAccount: defaultServiceAccount,
|
||||
ExpectCreatedServiceAccounts: []string{defaultName},
|
||||
},
|
||||
"deleted serviceaccount with terminating namespace": {
|
||||
ExistingNamespace: terminatingNS,
|
||||
DeletedServiceAccount: defaultServiceAccount,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
"deleted unmanaged serviceaccount with active namespace": {
|
||||
ExistingServiceAccounts: []*v1.ServiceAccount{defaultServiceAccount, managedServiceAccount},
|
||||
ExistingNamespace: activeNS,
|
||||
DeletedServiceAccount: unmanagedServiceAccount,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
"deleted unmanaged serviceaccount with terminating namespace": {
|
||||
ExistingNamespace: terminatingNS,
|
||||
DeletedServiceAccount: unmanagedServiceAccount,
|
||||
ExpectCreatedServiceAccounts: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
client := fake.NewSimpleClientset(defaultServiceAccount, managedServiceAccount)
|
||||
informers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(), controller.NoResyncPeriodFunc())
|
||||
options := DefaultServiceAccountsControllerOptions()
|
||||
options.ServiceAccounts = []v1.ServiceAccount{
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: defaultName}},
|
||||
{ObjectMeta: metav1.ObjectMeta{Name: managedName}},
|
||||
}
|
||||
saInformer := informers.Core().V1().ServiceAccounts()
|
||||
nsInformer := informers.Core().V1().Namespaces()
|
||||
controller, err := NewServiceAccountsController(
|
||||
saInformer,
|
||||
nsInformer,
|
||||
client,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating ServiceAccounts controller: %v", err)
|
||||
}
|
||||
controller.saListerSynced = alwaysReady
|
||||
controller.nsListerSynced = alwaysReady
|
||||
|
||||
saStore := saInformer.Informer().GetStore()
|
||||
nsStore := nsInformer.Informer().GetStore()
|
||||
|
||||
syncCalls := make(chan struct{})
|
||||
controller.syncHandler = func(key string) error {
|
||||
err := controller.syncNamespace(key)
|
||||
if err != nil {
|
||||
t.Logf("%s: %v", k, err)
|
||||
}
|
||||
|
||||
syncCalls <- struct{}{}
|
||||
return err
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
go controller.Run(1, stopCh)
|
||||
|
||||
if tc.ExistingNamespace != nil {
|
||||
nsStore.Add(tc.ExistingNamespace)
|
||||
}
|
||||
for _, s := range tc.ExistingServiceAccounts {
|
||||
saStore.Add(s)
|
||||
}
|
||||
|
||||
if tc.AddedNamespace != nil {
|
||||
nsStore.Add(tc.AddedNamespace)
|
||||
controller.namespaceAdded(tc.AddedNamespace)
|
||||
}
|
||||
if tc.UpdatedNamespace != nil {
|
||||
nsStore.Add(tc.UpdatedNamespace)
|
||||
controller.namespaceUpdated(nil, tc.UpdatedNamespace)
|
||||
}
|
||||
if tc.DeletedServiceAccount != nil {
|
||||
controller.serviceAccountDeleted(tc.DeletedServiceAccount)
|
||||
}
|
||||
|
||||
// wait to be called
|
||||
select {
|
||||
case <-syncCalls:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Errorf("%s: took too long", k)
|
||||
}
|
||||
|
||||
actions := client.Actions()
|
||||
if len(tc.ExpectCreatedServiceAccounts) != len(actions) {
|
||||
t.Errorf("%s: Expected to create accounts %#v. Actual actions were: %#v", k, tc.ExpectCreatedServiceAccounts, actions)
|
||||
continue
|
||||
}
|
||||
for i, expectedName := range tc.ExpectCreatedServiceAccounts {
|
||||
action := actions[i]
|
||||
if !action.Matches("create", "serviceaccounts") {
|
||||
t.Errorf("%s: Unexpected action %s", k, action)
|
||||
break
|
||||
}
|
||||
createdAccount := action.(core.CreateAction).GetObject().(*v1.ServiceAccount)
|
||||
if createdAccount.Name != expectedName {
|
||||
t.Errorf("%s: Expected %s to be created, got %s", k, expectedName, createdAccount.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var alwaysReady = func() bool { return true }
|
101
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokengetter.go
generated
vendored
Normal file
101
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokengetter.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
apiv1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
"k8s.io/kubernetes/pkg/registry/core/secret"
|
||||
secretstore "k8s.io/kubernetes/pkg/registry/core/secret/storage"
|
||||
serviceaccountregistry "k8s.io/kubernetes/pkg/registry/core/serviceaccount"
|
||||
serviceaccountstore "k8s.io/kubernetes/pkg/registry/core/serviceaccount/storage"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
)
|
||||
|
||||
// clientGetter implements ServiceAccountTokenGetter using a clientset.Interface
|
||||
type clientGetter struct {
|
||||
client clientset.Interface
|
||||
}
|
||||
|
||||
// NewGetterFromClient returns a ServiceAccountTokenGetter that
|
||||
// uses the specified client to retrieve service accounts and secrets.
|
||||
// The client should NOT authenticate using a service account token
|
||||
// the returned getter will be used to retrieve, or recursion will result.
|
||||
func NewGetterFromClient(c clientset.Interface) serviceaccount.ServiceAccountTokenGetter {
|
||||
return clientGetter{c}
|
||||
}
|
||||
func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) {
|
||||
return c.client.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) {
|
||||
return c.client.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
// registryGetter implements ServiceAccountTokenGetter using a service account and secret registry
|
||||
type registryGetter struct {
|
||||
serviceAccounts serviceaccountregistry.Registry
|
||||
secrets secret.Registry
|
||||
}
|
||||
|
||||
// NewGetterFromRegistries returns a ServiceAccountTokenGetter that
|
||||
// uses the specified registries to retrieve service accounts and secrets.
|
||||
func NewGetterFromRegistries(serviceAccounts serviceaccountregistry.Registry, secrets secret.Registry) serviceaccount.ServiceAccountTokenGetter {
|
||||
return ®istryGetter{serviceAccounts, secrets}
|
||||
}
|
||||
func (r *registryGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
|
||||
internalServiceAccount, err := r.serviceAccounts.GetServiceAccount(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v1ServiceAccount := v1.ServiceAccount{}
|
||||
err = apiv1.Convert_core_ServiceAccount_To_v1_ServiceAccount(internalServiceAccount, &v1ServiceAccount, nil)
|
||||
return &v1ServiceAccount, err
|
||||
|
||||
}
|
||||
func (r *registryGetter) GetSecret(namespace, name string) (*v1.Secret, error) {
|
||||
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
|
||||
internalSecret, err := r.secrets.GetSecret(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v1Secret := v1.Secret{}
|
||||
err = apiv1.Convert_core_Secret_To_v1_Secret(internalSecret, &v1Secret, nil)
|
||||
return &v1Secret, err
|
||||
|
||||
}
|
||||
|
||||
// NewGetterFromStorageInterface returns a ServiceAccountTokenGetter that
|
||||
// uses the specified storage to retrieve service accounts and secrets.
|
||||
func NewGetterFromStorageInterface(
|
||||
saConfig *storagebackend.Config,
|
||||
saPrefix string,
|
||||
secretConfig *storagebackend.Config,
|
||||
secretPrefix string) serviceaccount.ServiceAccountTokenGetter {
|
||||
|
||||
saOpts := generic.RESTOptions{StorageConfig: saConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: saPrefix}
|
||||
secretOpts := generic.RESTOptions{StorageConfig: secretConfig, Decorator: generic.UndecoratedStorage, ResourcePrefix: secretPrefix}
|
||||
return NewGetterFromRegistries(
|
||||
serviceaccountregistry.NewRegistry(serviceaccountstore.NewREST(saOpts)),
|
||||
secret.NewRegistry(secretstore.NewREST(secretOpts)),
|
||||
)
|
||||
}
|
781
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller.go
generated
vendored
Normal file
781
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller.go
generated
vendored
Normal file
@ -0,0 +1,781 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
informers "k8s.io/client-go/informers/core/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
clientretry "k8s.io/client-go/util/retry"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/registry/core/secret"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/util/metrics"
|
||||
)
|
||||
|
||||
// RemoveTokenBackoff is the recommended (empirical) retry interval for removing
|
||||
// a secret reference from a service account when the secret is deleted. It is
|
||||
// exported for use by custom secret controllers.
|
||||
var RemoveTokenBackoff = wait.Backoff{
|
||||
Steps: 10,
|
||||
Duration: 100 * time.Millisecond,
|
||||
Jitter: 1.0,
|
||||
}
|
||||
|
||||
// TokensControllerOptions contains options for the TokensController
|
||||
type TokensControllerOptions struct {
|
||||
// TokenGenerator is the generator to use to create new tokens
|
||||
TokenGenerator serviceaccount.TokenGenerator
|
||||
// ServiceAccountResync is the time.Duration at which to fully re-list service accounts.
|
||||
// If zero, re-list will be delayed as long as possible
|
||||
ServiceAccountResync time.Duration
|
||||
// SecretResync is the time.Duration at which to fully re-list secrets.
|
||||
// If zero, re-list will be delayed as long as possible
|
||||
SecretResync time.Duration
|
||||
// This CA will be added in the secrets of service accounts
|
||||
RootCA []byte
|
||||
|
||||
// MaxRetries controls the maximum number of times a particular key is retried before giving up
|
||||
// If zero, a default max is used
|
||||
MaxRetries int
|
||||
}
|
||||
|
||||
// NewTokensController returns a new *TokensController.
|
||||
func NewTokensController(serviceAccounts informers.ServiceAccountInformer, secrets informers.SecretInformer, cl clientset.Interface, options TokensControllerOptions) (*TokensController, error) {
|
||||
maxRetries := options.MaxRetries
|
||||
if maxRetries == 0 {
|
||||
maxRetries = 10
|
||||
}
|
||||
|
||||
e := &TokensController{
|
||||
client: cl,
|
||||
token: options.TokenGenerator,
|
||||
rootCA: options.RootCA,
|
||||
|
||||
syncServiceAccountQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "serviceaccount_tokens_service"),
|
||||
syncSecretQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "serviceaccount_tokens_secret"),
|
||||
|
||||
maxRetries: maxRetries,
|
||||
}
|
||||
if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil {
|
||||
if err := metrics.RegisterMetricAndTrackRateLimiterUsage("serviceaccount_tokens_controller", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
e.serviceAccounts = serviceAccounts.Lister()
|
||||
e.serviceAccountSynced = serviceAccounts.Informer().HasSynced
|
||||
serviceAccounts.Informer().AddEventHandlerWithResyncPeriod(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: e.queueServiceAccountSync,
|
||||
UpdateFunc: e.queueServiceAccountUpdateSync,
|
||||
DeleteFunc: e.queueServiceAccountSync,
|
||||
},
|
||||
options.ServiceAccountResync,
|
||||
)
|
||||
|
||||
secretCache := secrets.Informer().GetIndexer()
|
||||
e.updatedSecrets = cache.NewIntegerResourceVersionMutationCache(secretCache, secretCache, 60*time.Second, true)
|
||||
e.secretSynced = secrets.Informer().HasSynced
|
||||
secrets.Informer().AddEventHandlerWithResyncPeriod(
|
||||
cache.FilteringResourceEventHandler{
|
||||
FilterFunc: func(obj interface{}) bool {
|
||||
switch t := obj.(type) {
|
||||
case *v1.Secret:
|
||||
return t.Type == v1.SecretTypeServiceAccountToken
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("object passed to %T that is not expected: %T", e, obj))
|
||||
return false
|
||||
}
|
||||
},
|
||||
Handler: cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: e.queueSecretSync,
|
||||
UpdateFunc: e.queueSecretUpdateSync,
|
||||
DeleteFunc: e.queueSecretSync,
|
||||
},
|
||||
},
|
||||
options.SecretResync,
|
||||
)
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// TokensController manages ServiceAccountToken secrets for ServiceAccount objects
|
||||
type TokensController struct {
|
||||
client clientset.Interface
|
||||
token serviceaccount.TokenGenerator
|
||||
|
||||
rootCA []byte
|
||||
|
||||
serviceAccounts listersv1.ServiceAccountLister
|
||||
// updatedSecrets is a wrapper around the shared cache which allows us to record
|
||||
// and return our local mutations (since we're very likely to act on an updated
|
||||
// secret before the watch reports it).
|
||||
updatedSecrets cache.MutationCache
|
||||
|
||||
// Since we join two objects, we'll watch both of them with controllers.
|
||||
serviceAccountSynced cache.InformerSynced
|
||||
secretSynced cache.InformerSynced
|
||||
|
||||
// syncServiceAccountQueue handles service account events:
|
||||
// * ensures a referenced token exists for service accounts which still exist
|
||||
// * ensures tokens are removed for service accounts which no longer exist
|
||||
// key is "<namespace>/<name>/<uid>"
|
||||
syncServiceAccountQueue workqueue.RateLimitingInterface
|
||||
|
||||
// syncSecretQueue handles secret events:
|
||||
// * deletes tokens whose service account no longer exists
|
||||
// * updates tokens with missing token or namespace data, or mismatched ca data
|
||||
// * ensures service account secret references are removed for tokens which are deleted
|
||||
// key is a secretQueueKey{}
|
||||
syncSecretQueue workqueue.RateLimitingInterface
|
||||
|
||||
maxRetries int
|
||||
}
|
||||
|
||||
// Runs controller blocks until stopCh is closed
|
||||
func (e *TokensController) Run(workers int, stopCh <-chan struct{}) {
|
||||
// Shut down queues
|
||||
defer utilruntime.HandleCrash()
|
||||
defer e.syncServiceAccountQueue.ShutDown()
|
||||
defer e.syncSecretQueue.ShutDown()
|
||||
|
||||
if !controller.WaitForCacheSync("tokens", stopCh, e.serviceAccountSynced, e.secretSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(5).Infof("Starting workers")
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(e.syncServiceAccount, 0, stopCh)
|
||||
go wait.Until(e.syncSecret, 0, stopCh)
|
||||
}
|
||||
<-stopCh
|
||||
glog.V(1).Infof("Shutting down")
|
||||
}
|
||||
|
||||
func (e *TokensController) queueServiceAccountSync(obj interface{}) {
|
||||
if serviceAccount, ok := obj.(*v1.ServiceAccount); ok {
|
||||
e.syncServiceAccountQueue.Add(makeServiceAccountKey(serviceAccount))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TokensController) queueServiceAccountUpdateSync(oldObj interface{}, newObj interface{}) {
|
||||
if serviceAccount, ok := newObj.(*v1.ServiceAccount); ok {
|
||||
e.syncServiceAccountQueue.Add(makeServiceAccountKey(serviceAccount))
|
||||
}
|
||||
}
|
||||
|
||||
// complete optionally requeues key, then calls queue.Done(key)
|
||||
func (e *TokensController) retryOrForget(queue workqueue.RateLimitingInterface, key interface{}, requeue bool) {
|
||||
if !requeue {
|
||||
queue.Forget(key)
|
||||
return
|
||||
}
|
||||
|
||||
requeueCount := queue.NumRequeues(key)
|
||||
if requeueCount < e.maxRetries {
|
||||
queue.AddRateLimited(key)
|
||||
return
|
||||
}
|
||||
|
||||
glog.V(4).Infof("retried %d times: %#v", requeueCount, key)
|
||||
queue.Forget(key)
|
||||
}
|
||||
|
||||
func (e *TokensController) queueSecretSync(obj interface{}) {
|
||||
if secret, ok := obj.(*v1.Secret); ok {
|
||||
e.syncSecretQueue.Add(makeSecretQueueKey(secret))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TokensController) queueSecretUpdateSync(oldObj interface{}, newObj interface{}) {
|
||||
if secret, ok := newObj.(*v1.Secret); ok {
|
||||
e.syncSecretQueue.Add(makeSecretQueueKey(secret))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TokensController) syncServiceAccount() {
|
||||
key, quit := e.syncServiceAccountQueue.Get()
|
||||
if quit {
|
||||
return
|
||||
}
|
||||
defer e.syncServiceAccountQueue.Done(key)
|
||||
|
||||
retry := false
|
||||
defer func() {
|
||||
e.retryOrForget(e.syncServiceAccountQueue, key, retry)
|
||||
}()
|
||||
|
||||
saInfo, err := parseServiceAccountKey(key)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
sa, err := e.getServiceAccount(saInfo.namespace, saInfo.name, saInfo.uid, false)
|
||||
switch {
|
||||
case err != nil:
|
||||
glog.Error(err)
|
||||
retry = true
|
||||
case sa == nil:
|
||||
// service account no longer exists, so delete related tokens
|
||||
glog.V(4).Infof("syncServiceAccount(%s/%s), service account deleted, removing tokens", saInfo.namespace, saInfo.name)
|
||||
sa = &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: saInfo.namespace, Name: saInfo.name, UID: saInfo.uid}}
|
||||
retry, err = e.deleteTokens(sa)
|
||||
if err != nil {
|
||||
glog.Errorf("error deleting serviceaccount tokens for %s/%s: %v", saInfo.namespace, saInfo.name, err)
|
||||
}
|
||||
default:
|
||||
// ensure a token exists and is referenced by this service account
|
||||
retry, err = e.ensureReferencedToken(sa)
|
||||
if err != nil {
|
||||
glog.Errorf("error synchronizing serviceaccount %s/%s: %v", saInfo.namespace, saInfo.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TokensController) syncSecret() {
|
||||
key, quit := e.syncSecretQueue.Get()
|
||||
if quit {
|
||||
return
|
||||
}
|
||||
defer e.syncSecretQueue.Done(key)
|
||||
|
||||
// Track whether or not we should retry this sync
|
||||
retry := false
|
||||
defer func() {
|
||||
e.retryOrForget(e.syncSecretQueue, key, retry)
|
||||
}()
|
||||
|
||||
secretInfo, err := parseSecretQueueKey(key)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
secret, err := e.getSecret(secretInfo.namespace, secretInfo.name, secretInfo.uid, false)
|
||||
switch {
|
||||
case err != nil:
|
||||
glog.Error(err)
|
||||
retry = true
|
||||
case secret == nil:
|
||||
// If the service account exists
|
||||
if sa, saErr := e.getServiceAccount(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, false); saErr == nil && sa != nil {
|
||||
// secret no longer exists, so delete references to this secret from the service account
|
||||
if err := clientretry.RetryOnConflict(RemoveTokenBackoff, func() error {
|
||||
return e.removeSecretReference(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, secretInfo.name)
|
||||
}); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Ensure service account exists
|
||||
sa, saErr := e.getServiceAccount(secretInfo.namespace, secretInfo.saName, secretInfo.saUID, true)
|
||||
switch {
|
||||
case saErr != nil:
|
||||
glog.Error(saErr)
|
||||
retry = true
|
||||
case sa == nil:
|
||||
// Delete token
|
||||
glog.V(4).Infof("syncSecret(%s/%s), service account does not exist, deleting token", secretInfo.namespace, secretInfo.name)
|
||||
if retriable, err := e.deleteToken(secretInfo.namespace, secretInfo.name, secretInfo.uid); err != nil {
|
||||
glog.Errorf("error deleting serviceaccount token %s/%s for service account %s: %v", secretInfo.namespace, secretInfo.name, secretInfo.saName, err)
|
||||
retry = retriable
|
||||
}
|
||||
default:
|
||||
// Update token if needed
|
||||
if retriable, err := e.generateTokenIfNeeded(sa, secret); err != nil {
|
||||
glog.Errorf("error populating serviceaccount token %s/%s for service account %s: %v", secretInfo.namespace, secretInfo.name, secretInfo.saName, err)
|
||||
retry = retriable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TokensController) deleteTokens(serviceAccount *v1.ServiceAccount) ( /*retry*/ bool, error) {
|
||||
tokens, err := e.listTokenSecrets(serviceAccount)
|
||||
if err != nil {
|
||||
// don't retry on cache lookup errors
|
||||
return false, err
|
||||
}
|
||||
retry := false
|
||||
errs := []error{}
|
||||
for _, token := range tokens {
|
||||
r, err := e.deleteToken(token.Namespace, token.Name, token.UID)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if r {
|
||||
retry = true
|
||||
}
|
||||
}
|
||||
return retry, utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (e *TokensController) deleteToken(ns, name string, uid types.UID) ( /*retry*/ bool, error) {
|
||||
var opts *metav1.DeleteOptions
|
||||
if len(uid) > 0 {
|
||||
opts = &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &uid}}
|
||||
}
|
||||
err := e.client.CoreV1().Secrets(ns).Delete(name, opts)
|
||||
// NotFound doesn't need a retry (it's already been deleted)
|
||||
// Conflict doesn't need a retry (the UID precondition failed)
|
||||
if err == nil || apierrors.IsNotFound(err) || apierrors.IsConflict(err) {
|
||||
return false, nil
|
||||
}
|
||||
// Retry for any other error
|
||||
return true, err
|
||||
}
|
||||
|
||||
// ensureReferencedToken makes sure at least one ServiceAccountToken secret exists, and is included in the serviceAccount's Secrets list
|
||||
func (e *TokensController) ensureReferencedToken(serviceAccount *v1.ServiceAccount) ( /* retry */ bool, error) {
|
||||
if hasToken, err := e.hasReferencedToken(serviceAccount); err != nil {
|
||||
// Don't retry cache lookup errors
|
||||
return false, err
|
||||
} else if hasToken {
|
||||
// A service account token already exists, and is referenced, short-circuit
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// We don't want to update the cache's copy of the service account
|
||||
// so add the secret to a freshly retrieved copy of the service account
|
||||
serviceAccounts := e.client.CoreV1().ServiceAccounts(serviceAccount.Namespace)
|
||||
liveServiceAccount, err := serviceAccounts.Get(serviceAccount.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
// Retry if we cannot fetch the live service account (for a NotFound error, either the live lookup or our cache are stale)
|
||||
return true, err
|
||||
}
|
||||
if liveServiceAccount.ResourceVersion != serviceAccount.ResourceVersion {
|
||||
// Retry if our liveServiceAccount doesn't match our cache's resourceVersion (either the live lookup or our cache are stale)
|
||||
glog.V(4).Infof("liveServiceAccount.ResourceVersion (%s) does not match cache (%s), retrying", liveServiceAccount.ResourceVersion, serviceAccount.ResourceVersion)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Build the secret
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secret.Strategy.GenerateName(fmt.Sprintf("%s-token-", serviceAccount.Name)),
|
||||
Namespace: serviceAccount.Namespace,
|
||||
Annotations: map[string]string{
|
||||
v1.ServiceAccountNameKey: serviceAccount.Name,
|
||||
v1.ServiceAccountUIDKey: string(serviceAccount.UID),
|
||||
},
|
||||
},
|
||||
Type: v1.SecretTypeServiceAccountToken,
|
||||
Data: map[string][]byte{},
|
||||
}
|
||||
|
||||
// Generate the token
|
||||
token, err := e.token.GenerateToken(*serviceAccount, *secret)
|
||||
if err != nil {
|
||||
// retriable error
|
||||
return true, err
|
||||
}
|
||||
secret.Data[v1.ServiceAccountTokenKey] = []byte(token)
|
||||
secret.Data[v1.ServiceAccountNamespaceKey] = []byte(serviceAccount.Namespace)
|
||||
if e.rootCA != nil && len(e.rootCA) > 0 {
|
||||
secret.Data[v1.ServiceAccountRootCAKey] = e.rootCA
|
||||
}
|
||||
|
||||
// Save the secret
|
||||
createdToken, err := e.client.CoreV1().Secrets(serviceAccount.Namespace).Create(secret)
|
||||
if err != nil {
|
||||
// retriable error
|
||||
return true, err
|
||||
}
|
||||
// Manually add the new token to the cache store.
|
||||
// This prevents the service account update (below) triggering another token creation, if the referenced token couldn't be found in the store
|
||||
e.updatedSecrets.Mutation(createdToken)
|
||||
|
||||
// Try to add a reference to the newly created token to the service account
|
||||
addedReference := false
|
||||
err = clientretry.RetryOnConflict(clientretry.DefaultRetry, func() error {
|
||||
// refresh liveServiceAccount on every retry
|
||||
defer func() { liveServiceAccount = nil }()
|
||||
|
||||
// fetch the live service account if needed, and verify the UID matches and that we still need a token
|
||||
if liveServiceAccount == nil {
|
||||
liveServiceAccount, err = serviceAccounts.Get(serviceAccount.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if liveServiceAccount.UID != serviceAccount.UID {
|
||||
// If we don't have the same service account, stop trying to add a reference to the token made for the old service account.
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasToken, err := e.hasReferencedToken(liveServiceAccount); err != nil {
|
||||
// Don't retry cache lookup errors
|
||||
return nil
|
||||
} else if hasToken {
|
||||
// A service account token already exists, and is referenced, short-circuit
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Try to add a reference to the token
|
||||
liveServiceAccount.Secrets = append(liveServiceAccount.Secrets, v1.ObjectReference{Name: secret.Name})
|
||||
if _, err := serviceAccounts.Update(liveServiceAccount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addedReference = true
|
||||
return nil
|
||||
})
|
||||
|
||||
if !addedReference {
|
||||
// we weren't able to use the token, try to clean it up.
|
||||
glog.V(2).Infof("deleting secret %s/%s because reference couldn't be added (%v)", secret.Namespace, secret.Name, err)
|
||||
deleteOpts := &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &createdToken.UID}}
|
||||
if deleteErr := e.client.CoreV1().Secrets(createdToken.Namespace).Delete(createdToken.Name, deleteOpts); deleteErr != nil {
|
||||
glog.Error(deleteErr) // if we fail, just log it
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if apierrors.IsConflict(err) || apierrors.IsNotFound(err) {
|
||||
// if we got a Conflict error, the service account was updated by someone else, and we'll get an update notification later
|
||||
// if we got a NotFound error, the service account no longer exists, and we don't need to create a token for it
|
||||
return false, nil
|
||||
}
|
||||
// retry in all other cases
|
||||
return true, err
|
||||
}
|
||||
|
||||
// success!
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// hasReferencedToken returns true if the serviceAccount references a service account token secret
|
||||
func (e *TokensController) hasReferencedToken(serviceAccount *v1.ServiceAccount) (bool, error) {
|
||||
if len(serviceAccount.Secrets) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
allSecrets, err := e.listTokenSecrets(serviceAccount)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
referencedSecrets := getSecretReferences(serviceAccount)
|
||||
for _, secret := range allSecrets {
|
||||
if referencedSecrets.Has(secret.Name) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (e *TokensController) secretUpdateNeeded(secret *v1.Secret) (bool, bool, bool) {
|
||||
caData := secret.Data[v1.ServiceAccountRootCAKey]
|
||||
needsCA := len(e.rootCA) > 0 && bytes.Compare(caData, e.rootCA) != 0
|
||||
|
||||
needsNamespace := len(secret.Data[v1.ServiceAccountNamespaceKey]) == 0
|
||||
|
||||
tokenData := secret.Data[v1.ServiceAccountTokenKey]
|
||||
needsToken := len(tokenData) == 0
|
||||
|
||||
return needsCA, needsNamespace, needsToken
|
||||
}
|
||||
|
||||
// generateTokenIfNeeded populates the token data for the given Secret if not already set
|
||||
func (e *TokensController) generateTokenIfNeeded(serviceAccount *v1.ServiceAccount, cachedSecret *v1.Secret) ( /* retry */ bool, error) {
|
||||
// Check the cached secret to see if changes are needed
|
||||
if needsCA, needsNamespace, needsToken := e.secretUpdateNeeded(cachedSecret); !needsCA && !needsToken && !needsNamespace {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// We don't want to update the cache's copy of the secret
|
||||
// so add the token to a freshly retrieved copy of the secret
|
||||
secrets := e.client.CoreV1().Secrets(cachedSecret.Namespace)
|
||||
liveSecret, err := secrets.Get(cachedSecret.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
// Retry for any error other than a NotFound
|
||||
return !apierrors.IsNotFound(err), err
|
||||
}
|
||||
if liveSecret.ResourceVersion != cachedSecret.ResourceVersion {
|
||||
// our view of the secret is not up to date
|
||||
// we'll get notified of an update event later and get to try again
|
||||
glog.V(2).Infof("secret %s/%s is not up to date, skipping token population", liveSecret.Namespace, liveSecret.Name)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
needsCA, needsNamespace, needsToken := e.secretUpdateNeeded(liveSecret)
|
||||
if !needsCA && !needsToken && !needsNamespace {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if liveSecret.Annotations == nil {
|
||||
liveSecret.Annotations = map[string]string{}
|
||||
}
|
||||
if liveSecret.Data == nil {
|
||||
liveSecret.Data = map[string][]byte{}
|
||||
}
|
||||
|
||||
// Set the CA
|
||||
if needsCA {
|
||||
liveSecret.Data[v1.ServiceAccountRootCAKey] = e.rootCA
|
||||
}
|
||||
// Set the namespace
|
||||
if needsNamespace {
|
||||
liveSecret.Data[v1.ServiceAccountNamespaceKey] = []byte(liveSecret.Namespace)
|
||||
}
|
||||
|
||||
// Generate the token
|
||||
if needsToken {
|
||||
token, err := e.token.GenerateToken(*serviceAccount, *liveSecret)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
liveSecret.Data[v1.ServiceAccountTokenKey] = []byte(token)
|
||||
}
|
||||
|
||||
// Set annotations
|
||||
liveSecret.Annotations[v1.ServiceAccountNameKey] = serviceAccount.Name
|
||||
liveSecret.Annotations[v1.ServiceAccountUIDKey] = string(serviceAccount.UID)
|
||||
|
||||
// Save the secret
|
||||
_, err = secrets.Update(liveSecret)
|
||||
if apierrors.IsConflict(err) || apierrors.IsNotFound(err) {
|
||||
// if we got a Conflict error, the secret was updated by someone else, and we'll get an update notification later
|
||||
// if we got a NotFound error, the secret no longer exists, and we don't need to populate a token
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// removeSecretReference updates the given ServiceAccount to remove a reference to the given secretName if needed.
|
||||
func (e *TokensController) removeSecretReference(saNamespace string, saName string, saUID types.UID, secretName string) error {
|
||||
// We don't want to update the cache's copy of the service account
|
||||
// so remove the secret from a freshly retrieved copy of the service account
|
||||
serviceAccounts := e.client.CoreV1().ServiceAccounts(saNamespace)
|
||||
serviceAccount, err := serviceAccounts.Get(saName, metav1.GetOptions{})
|
||||
// Ignore NotFound errors when attempting to remove a reference
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Short-circuit if the UID doesn't match
|
||||
if len(saUID) > 0 && saUID != serviceAccount.UID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Short-circuit if the secret is no longer referenced
|
||||
if !getSecretReferences(serviceAccount).Has(secretName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the secret
|
||||
secrets := []v1.ObjectReference{}
|
||||
for _, s := range serviceAccount.Secrets {
|
||||
if s.Name != secretName {
|
||||
secrets = append(secrets, s)
|
||||
}
|
||||
}
|
||||
serviceAccount.Secrets = secrets
|
||||
_, err = serviceAccounts.Update(serviceAccount)
|
||||
// Ignore NotFound errors when attempting to remove a reference
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *TokensController) getServiceAccount(ns string, name string, uid types.UID, fetchOnCacheMiss bool) (*v1.ServiceAccount, error) {
|
||||
// Look up in cache
|
||||
sa, err := e.serviceAccounts.ServiceAccounts(ns).Get(name)
|
||||
if err != nil && !apierrors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if sa != nil {
|
||||
// Ensure UID matches if given
|
||||
if len(uid) == 0 || uid == sa.UID {
|
||||
return sa, nil
|
||||
}
|
||||
}
|
||||
|
||||
if !fetchOnCacheMiss {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Live lookup
|
||||
sa, err = e.client.CoreV1().ServiceAccounts(ns).Get(name, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Ensure UID matches if given
|
||||
if len(uid) == 0 || uid == sa.UID {
|
||||
return sa, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e *TokensController) getSecret(ns string, name string, uid types.UID, fetchOnCacheMiss bool) (*v1.Secret, error) {
|
||||
// Look up in cache
|
||||
obj, exists, err := e.updatedSecrets.GetByKey(makeCacheKey(ns, name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
secret, ok := obj.(*v1.Secret)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *v1.Secret, got %#v", secret)
|
||||
}
|
||||
// Ensure UID matches if given
|
||||
if len(uid) == 0 || uid == secret.UID {
|
||||
return secret, nil
|
||||
}
|
||||
}
|
||||
|
||||
if !fetchOnCacheMiss {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Live lookup
|
||||
secret, err := e.client.CoreV1().Secrets(ns).Get(name, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Ensure UID matches if given
|
||||
if len(uid) == 0 || uid == secret.UID {
|
||||
return secret, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// listTokenSecrets returns a list of all of the ServiceAccountToken secrets that
|
||||
// reference the given service account's name and uid
|
||||
func (e *TokensController) listTokenSecrets(serviceAccount *v1.ServiceAccount) ([]*v1.Secret, error) {
|
||||
namespaceSecrets, err := e.updatedSecrets.ByIndex("namespace", serviceAccount.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := []*v1.Secret{}
|
||||
for _, obj := range namespaceSecrets {
|
||||
secret := obj.(*v1.Secret)
|
||||
|
||||
if serviceaccount.IsServiceAccountToken(secret, serviceAccount) {
|
||||
items = append(items, secret)
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// serviceAccountNameAndUID is a helper method to get the ServiceAccount Name and UID from the given secret
|
||||
// Returns "","" if the secret is not a ServiceAccountToken secret
|
||||
// If the name or uid annotation is missing, "" is returned instead
|
||||
func serviceAccountNameAndUID(secret *v1.Secret) (string, string) {
|
||||
if secret.Type != v1.SecretTypeServiceAccountToken {
|
||||
return "", ""
|
||||
}
|
||||
return secret.Annotations[v1.ServiceAccountNameKey], secret.Annotations[v1.ServiceAccountUIDKey]
|
||||
}
|
||||
|
||||
func getSecretReferences(serviceAccount *v1.ServiceAccount) sets.String {
|
||||
references := sets.NewString()
|
||||
for _, secret := range serviceAccount.Secrets {
|
||||
references.Insert(secret.Name)
|
||||
}
|
||||
return references
|
||||
}
|
||||
|
||||
// serviceAccountQueueKey holds information we need to sync a service account.
|
||||
// It contains enough information to look up the cached service account,
|
||||
// or delete owned tokens if the service account no longer exists.
|
||||
type serviceAccountQueueKey struct {
|
||||
namespace string
|
||||
name string
|
||||
uid types.UID
|
||||
}
|
||||
|
||||
func makeServiceAccountKey(sa *v1.ServiceAccount) interface{} {
|
||||
return serviceAccountQueueKey{
|
||||
namespace: sa.Namespace,
|
||||
name: sa.Name,
|
||||
uid: sa.UID,
|
||||
}
|
||||
}
|
||||
|
||||
func parseServiceAccountKey(key interface{}) (serviceAccountQueueKey, error) {
|
||||
queueKey, ok := key.(serviceAccountQueueKey)
|
||||
if !ok || len(queueKey.namespace) == 0 || len(queueKey.name) == 0 || len(queueKey.uid) == 0 {
|
||||
return serviceAccountQueueKey{}, fmt.Errorf("invalid serviceaccount key: %#v", key)
|
||||
}
|
||||
return queueKey, nil
|
||||
}
|
||||
|
||||
// secretQueueKey holds information we need to sync a service account token secret.
|
||||
// It contains enough information to look up the cached service account,
|
||||
// or delete the secret reference if the secret no longer exists.
|
||||
type secretQueueKey struct {
|
||||
namespace string
|
||||
name string
|
||||
uid types.UID
|
||||
saName string
|
||||
// optional, will be blank when syncing tokens missing the service account uid annotation
|
||||
saUID types.UID
|
||||
}
|
||||
|
||||
func makeSecretQueueKey(secret *v1.Secret) interface{} {
|
||||
return secretQueueKey{
|
||||
namespace: secret.Namespace,
|
||||
name: secret.Name,
|
||||
uid: secret.UID,
|
||||
saName: secret.Annotations[v1.ServiceAccountNameKey],
|
||||
saUID: types.UID(secret.Annotations[v1.ServiceAccountUIDKey]),
|
||||
}
|
||||
}
|
||||
|
||||
func parseSecretQueueKey(key interface{}) (secretQueueKey, error) {
|
||||
queueKey, ok := key.(secretQueueKey)
|
||||
if !ok || len(queueKey.namespace) == 0 || len(queueKey.name) == 0 || len(queueKey.uid) == 0 || len(queueKey.saName) == 0 {
|
||||
return secretQueueKey{}, fmt.Errorf("invalid secret key: %#v", key)
|
||||
}
|
||||
return queueKey, nil
|
||||
}
|
||||
|
||||
// produce the same key format as cache.MetaNamespaceKeyFunc
|
||||
func makeCacheKey(namespace, name string) string {
|
||||
return namespace + "/" + name
|
||||
}
|
694
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller_test.go
generated
vendored
Normal file
694
vendor/k8s.io/kubernetes/pkg/controller/serviceaccount/tokens_controller_test.go
generated
vendored
Normal file
@ -0,0 +1,694 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilrand "k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
type testGenerator struct {
|
||||
GeneratedServiceAccounts []v1.ServiceAccount
|
||||
GeneratedSecrets []v1.Secret
|
||||
Token string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (t *testGenerator) GenerateToken(serviceAccount v1.ServiceAccount, secret v1.Secret) (string, error) {
|
||||
t.GeneratedSecrets = append(t.GeneratedSecrets, secret)
|
||||
t.GeneratedServiceAccounts = append(t.GeneratedServiceAccounts, serviceAccount)
|
||||
return t.Token, t.Err
|
||||
}
|
||||
|
||||
// emptySecretReferences is used by a service account without any secrets
|
||||
func emptySecretReferences() []v1.ObjectReference {
|
||||
return []v1.ObjectReference{}
|
||||
}
|
||||
|
||||
// missingSecretReferences is used by a service account that references secrets which do no exist
|
||||
func missingSecretReferences() []v1.ObjectReference {
|
||||
return []v1.ObjectReference{{Name: "missing-secret-1"}}
|
||||
}
|
||||
|
||||
// regularSecretReferences is used by a service account that references secrets which are not ServiceAccountTokens
|
||||
func regularSecretReferences() []v1.ObjectReference {
|
||||
return []v1.ObjectReference{{Name: "regular-secret-1"}}
|
||||
}
|
||||
|
||||
// tokenSecretReferences is used by a service account that references a ServiceAccountToken secret
|
||||
func tokenSecretReferences() []v1.ObjectReference {
|
||||
return []v1.ObjectReference{{Name: "token-secret-1"}}
|
||||
}
|
||||
|
||||
// addTokenSecretReference adds a reference to the ServiceAccountToken that will be created
|
||||
func addTokenSecretReference(refs []v1.ObjectReference) []v1.ObjectReference {
|
||||
return addNamedTokenSecretReference(refs, "default-token-xn8fg")
|
||||
}
|
||||
|
||||
// addNamedTokenSecretReference adds a reference to the named ServiceAccountToken
|
||||
func addNamedTokenSecretReference(refs []v1.ObjectReference, name string) []v1.ObjectReference {
|
||||
return append(refs, v1.ObjectReference{Name: name})
|
||||
}
|
||||
|
||||
// serviceAccount returns a service account with the given secret refs
|
||||
func serviceAccount(secretRefs []v1.ObjectReference) *v1.ServiceAccount {
|
||||
return &v1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
UID: "12345",
|
||||
Namespace: "default",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Secrets: secretRefs,
|
||||
}
|
||||
}
|
||||
|
||||
// updatedServiceAccount returns a service account with the resource version modified
|
||||
func updatedServiceAccount(secretRefs []v1.ObjectReference) *v1.ServiceAccount {
|
||||
sa := serviceAccount(secretRefs)
|
||||
sa.ResourceVersion = "2"
|
||||
return sa
|
||||
}
|
||||
|
||||
// opaqueSecret returns a persisted non-ServiceAccountToken secret named "regular-secret-1"
|
||||
func opaqueSecret() *v1.Secret {
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "regular-secret-1",
|
||||
Namespace: "default",
|
||||
UID: "23456",
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
Type: "Opaque",
|
||||
Data: map[string][]byte{
|
||||
"mykey": []byte("mydata"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// createdTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret.
|
||||
// Named "default-token-xn8fg", since that is the first generated name after rand.Seed(1)
|
||||
func createdTokenSecret(overrideName ...string) *v1.Secret {
|
||||
return namedCreatedTokenSecret("default-token-xn8fg")
|
||||
}
|
||||
|
||||
// namedTokenSecret returns the ServiceAccountToken secret posted when creating a new token secret with the given name.
|
||||
func namedCreatedTokenSecret(name string) *v1.Secret {
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
v1.ServiceAccountNameKey: "default",
|
||||
v1.ServiceAccountUIDKey: "12345",
|
||||
},
|
||||
},
|
||||
Type: v1.SecretTypeServiceAccountToken,
|
||||
Data: map[string][]byte{
|
||||
"token": []byte("ABC"),
|
||||
"ca.crt": []byte("CA Data"),
|
||||
"namespace": []byte("default"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// serviceAccountTokenSecret returns an existing ServiceAccountToken secret named "token-secret-1"
|
||||
func serviceAccountTokenSecret() *v1.Secret {
|
||||
return &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "token-secret-1",
|
||||
Namespace: "default",
|
||||
UID: "23456",
|
||||
ResourceVersion: "1",
|
||||
Annotations: map[string]string{
|
||||
v1.ServiceAccountNameKey: "default",
|
||||
v1.ServiceAccountUIDKey: "12345",
|
||||
},
|
||||
},
|
||||
Type: v1.SecretTypeServiceAccountToken,
|
||||
Data: map[string][]byte{
|
||||
"token": []byte("ABC"),
|
||||
"ca.crt": []byte("CA Data"),
|
||||
"namespace": []byte("default"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// serviceAccountTokenSecretWithoutTokenData returns an existing ServiceAccountToken secret that lacks token data
|
||||
func serviceAccountTokenSecretWithoutTokenData() *v1.Secret {
|
||||
secret := serviceAccountTokenSecret()
|
||||
delete(secret.Data, v1.ServiceAccountTokenKey)
|
||||
return secret
|
||||
}
|
||||
|
||||
// serviceAccountTokenSecretWithoutCAData returns an existing ServiceAccountToken secret that lacks ca data
|
||||
func serviceAccountTokenSecretWithoutCAData() *v1.Secret {
|
||||
secret := serviceAccountTokenSecret()
|
||||
delete(secret.Data, v1.ServiceAccountRootCAKey)
|
||||
return secret
|
||||
}
|
||||
|
||||
// serviceAccountTokenSecretWithCAData returns an existing ServiceAccountToken secret with the specified ca data
|
||||
func serviceAccountTokenSecretWithCAData(data []byte) *v1.Secret {
|
||||
secret := serviceAccountTokenSecret()
|
||||
secret.Data[v1.ServiceAccountRootCAKey] = data
|
||||
return secret
|
||||
}
|
||||
|
||||
// serviceAccountTokenSecretWithoutNamespaceData returns an existing ServiceAccountToken secret that lacks namespace data
|
||||
func serviceAccountTokenSecretWithoutNamespaceData() *v1.Secret {
|
||||
secret := serviceAccountTokenSecret()
|
||||
delete(secret.Data, v1.ServiceAccountNamespaceKey)
|
||||
return secret
|
||||
}
|
||||
|
||||
// serviceAccountTokenSecretWithNamespaceData returns an existing ServiceAccountToken secret with the specified namespace data
|
||||
func serviceAccountTokenSecretWithNamespaceData(data []byte) *v1.Secret {
|
||||
secret := serviceAccountTokenSecret()
|
||||
secret.Data[v1.ServiceAccountNamespaceKey] = data
|
||||
return secret
|
||||
}
|
||||
|
||||
type reaction struct {
|
||||
verb string
|
||||
resource string
|
||||
reactor func(t *testing.T) core.ReactionFunc
|
||||
}
|
||||
|
||||
func TestTokenCreation(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
ClientObjects []runtime.Object
|
||||
|
||||
IsAsync bool
|
||||
MaxRetries int
|
||||
|
||||
Reactors []reaction
|
||||
|
||||
ExistingServiceAccount *v1.ServiceAccount
|
||||
ExistingSecrets []*v1.Secret
|
||||
|
||||
AddedServiceAccount *v1.ServiceAccount
|
||||
UpdatedServiceAccount *v1.ServiceAccount
|
||||
DeletedServiceAccount *v1.ServiceAccount
|
||||
AddedSecret *v1.Secret
|
||||
AddedSecretLocal *v1.Secret
|
||||
UpdatedSecret *v1.Secret
|
||||
DeletedSecret *v1.Secret
|
||||
|
||||
ExpectedActions []core.Action
|
||||
}{
|
||||
"new serviceaccount with no secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())},
|
||||
|
||||
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with no secrets encountering create error": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())},
|
||||
MaxRetries: 10,
|
||||
IsAsync: true,
|
||||
Reactors: []reaction{{
|
||||
verb: "create",
|
||||
resource: "secrets",
|
||||
reactor: func(t *testing.T) core.ReactionFunc {
|
||||
i := 0
|
||||
return func(core.Action) (bool, runtime.Object, error) {
|
||||
i++
|
||||
if i < 3 {
|
||||
return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do"))
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
},
|
||||
}},
|
||||
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
// Attempt 1
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
|
||||
// Attempt 2
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-txhzt")),
|
||||
|
||||
// Attempt 3
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-vnmz7")),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addNamedTokenSecretReference(emptySecretReferences(), "default-token-vnmz7"))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with no secrets encountering unending create error": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||
MaxRetries: 2,
|
||||
IsAsync: true,
|
||||
Reactors: []reaction{{
|
||||
verb: "create",
|
||||
resource: "secrets",
|
||||
reactor: func(t *testing.T) core.ReactionFunc {
|
||||
return func(core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewForbidden(api.Resource("secrets"), "foo", errors.New("No can do"))
|
||||
}
|
||||
},
|
||||
}},
|
||||
|
||||
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
// Attempt
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
// Retry 1
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-txhzt")),
|
||||
// Retry 2
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, namedCreatedTokenSecret("default-token-vnmz7")),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with missing secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())},
|
||||
|
||||
AddedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with missing secrets and a local secret in the cache": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())},
|
||||
|
||||
AddedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
AddedSecretLocal: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"new serviceaccount with non-token secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), opaqueSecret()},
|
||||
|
||||
AddedServiceAccount: serviceAccount(regularSecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))),
|
||||
},
|
||||
},
|
||||
"new serviceaccount with token secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences()), serviceAccountTokenSecret()},
|
||||
ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()},
|
||||
|
||||
AddedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"new serviceaccount with no secrets with resource conflict": {
|
||||
ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences()), createdTokenSecret()},
|
||||
IsAsync: true,
|
||||
MaxRetries: 1,
|
||||
|
||||
AddedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with no secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(emptySecretReferences())},
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(emptySecretReferences()))),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with missing secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(missingSecretReferences())},
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(missingSecretReferences()))),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with non-token secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(regularSecretReferences()), opaqueSecret()},
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(regularSecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewCreateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, createdTokenSecret()),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(addTokenSecretReference(regularSecretReferences()))),
|
||||
},
|
||||
},
|
||||
"updated serviceaccount with token secrets": {
|
||||
ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()},
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"updated serviceaccount with no secrets with resource conflict": {
|
||||
ClientObjects: []runtime.Object{updatedServiceAccount(emptySecretReferences())},
|
||||
IsAsync: true,
|
||||
MaxRetries: 1,
|
||||
|
||||
UpdatedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
},
|
||||
},
|
||||
|
||||
"deleted serviceaccount with no secrets": {
|
||||
DeletedServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"deleted serviceaccount with missing secrets": {
|
||||
DeletedServiceAccount: serviceAccount(missingSecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"deleted serviceaccount with non-token secrets": {
|
||||
ClientObjects: []runtime.Object{opaqueSecret()},
|
||||
|
||||
DeletedServiceAccount: serviceAccount(regularSecretReferences()),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"deleted serviceaccount with token secrets": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
|
||||
ExistingSecrets: []*v1.Secret{serviceAccountTokenSecret()},
|
||||
|
||||
DeletedServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
},
|
||||
},
|
||||
|
||||
"added secret without serviceaccount": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
|
||||
|
||||
AddedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
},
|
||||
},
|
||||
"added secret with serviceaccount": {
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
AddedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"added token secret without token data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithoutTokenData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"added token secret without ca data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithoutCAData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"added token secret with mismatched ca data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"added token secret without namespace data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"added token secret with custom namespace data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
AddedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")),
|
||||
ExpectedActions: []core.Action{
|
||||
// no update is performed... the custom namespace is preserved
|
||||
},
|
||||
},
|
||||
|
||||
"updated secret without serviceaccount": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewDeleteAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
},
|
||||
},
|
||||
"updated secret with serviceaccount": {
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"updated token secret without token data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutTokenData()},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithoutTokenData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"updated token secret without ca data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutCAData()},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithoutCAData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"updated token secret with mismatched ca data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithCAData([]byte("mismatched"))},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithCAData([]byte("mismatched")),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"updated token secret without namespace data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, "token-secret-1"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "secrets"}, metav1.NamespaceDefault, serviceAccountTokenSecret()),
|
||||
},
|
||||
},
|
||||
"updated token secret with custom namespace data": {
|
||||
ClientObjects: []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
UpdatedSecret: serviceAccountTokenSecretWithNamespaceData([]byte("custom")),
|
||||
ExpectedActions: []core.Action{
|
||||
// no update is performed... the custom namespace is preserved
|
||||
},
|
||||
},
|
||||
|
||||
"deleted secret without serviceaccount": {
|
||||
DeletedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{},
|
||||
},
|
||||
"deleted secret with serviceaccount with reference": {
|
||||
ClientObjects: []runtime.Object{serviceAccount(tokenSecretReferences())},
|
||||
ExistingServiceAccount: serviceAccount(tokenSecretReferences()),
|
||||
|
||||
DeletedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
core.NewUpdateAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, serviceAccount(emptySecretReferences())),
|
||||
},
|
||||
},
|
||||
"deleted secret with serviceaccount without reference": {
|
||||
ExistingServiceAccount: serviceAccount(emptySecretReferences()),
|
||||
|
||||
DeletedSecret: serviceAccountTokenSecret(),
|
||||
ExpectedActions: []core.Action{
|
||||
core.NewGetAction(schema.GroupVersionResource{Version: "v1", Resource: "serviceaccounts"}, metav1.NamespaceDefault, "default"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
glog.Infof(k)
|
||||
|
||||
// Re-seed to reset name generation
|
||||
utilrand.Seed(1)
|
||||
|
||||
generator := &testGenerator{Token: "ABC"}
|
||||
|
||||
client := fake.NewSimpleClientset(tc.ClientObjects...)
|
||||
for _, reactor := range tc.Reactors {
|
||||
client.Fake.PrependReactor(reactor.verb, reactor.resource, reactor.reactor(t))
|
||||
}
|
||||
informers := informers.NewSharedInformerFactory(client, controller.NoResyncPeriodFunc())
|
||||
secretInformer := informers.Core().V1().Secrets().Informer()
|
||||
secrets := secretInformer.GetStore()
|
||||
serviceAccounts := informers.Core().V1().ServiceAccounts().Informer().GetStore()
|
||||
controller, err := NewTokensController(informers.Core().V1().ServiceAccounts(), informers.Core().V1().Secrets(), client, TokensControllerOptions{TokenGenerator: generator, RootCA: []byte("CA Data"), MaxRetries: tc.MaxRetries})
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Tokens controller: %v", err)
|
||||
}
|
||||
|
||||
if tc.ExistingServiceAccount != nil {
|
||||
serviceAccounts.Add(tc.ExistingServiceAccount)
|
||||
}
|
||||
for _, s := range tc.ExistingSecrets {
|
||||
secrets.Add(s)
|
||||
}
|
||||
|
||||
if tc.AddedServiceAccount != nil {
|
||||
serviceAccounts.Add(tc.AddedServiceAccount)
|
||||
controller.queueServiceAccountSync(tc.AddedServiceAccount)
|
||||
}
|
||||
if tc.UpdatedServiceAccount != nil {
|
||||
serviceAccounts.Add(tc.UpdatedServiceAccount)
|
||||
controller.queueServiceAccountUpdateSync(nil, tc.UpdatedServiceAccount)
|
||||
}
|
||||
if tc.DeletedServiceAccount != nil {
|
||||
serviceAccounts.Delete(tc.DeletedServiceAccount)
|
||||
controller.queueServiceAccountSync(tc.DeletedServiceAccount)
|
||||
}
|
||||
if tc.AddedSecret != nil {
|
||||
secrets.Add(tc.AddedSecret)
|
||||
controller.queueSecretSync(tc.AddedSecret)
|
||||
}
|
||||
if tc.AddedSecretLocal != nil {
|
||||
controller.updatedSecrets.Mutation(tc.AddedSecretLocal)
|
||||
}
|
||||
if tc.UpdatedSecret != nil {
|
||||
secrets.Add(tc.UpdatedSecret)
|
||||
controller.queueSecretUpdateSync(nil, tc.UpdatedSecret)
|
||||
}
|
||||
if tc.DeletedSecret != nil {
|
||||
secrets.Delete(tc.DeletedSecret)
|
||||
controller.queueSecretSync(tc.DeletedSecret)
|
||||
}
|
||||
|
||||
// This is the longest we'll wait for async tests
|
||||
timeout := time.Now().Add(30 * time.Second)
|
||||
waitedForAdditionalActions := false
|
||||
|
||||
for {
|
||||
if controller.syncServiceAccountQueue.Len() > 0 {
|
||||
controller.syncServiceAccount()
|
||||
}
|
||||
if controller.syncSecretQueue.Len() > 0 {
|
||||
controller.syncSecret()
|
||||
}
|
||||
|
||||
// The queues still have things to work on
|
||||
if controller.syncServiceAccountQueue.Len() > 0 || controller.syncSecretQueue.Len() > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we expect this test to work asynchronously...
|
||||
if tc.IsAsync {
|
||||
// if we're still missing expected actions within our test timeout
|
||||
if len(client.Actions()) < len(tc.ExpectedActions) && time.Now().Before(timeout) {
|
||||
// wait for the expected actions (without hotlooping)
|
||||
time.Sleep(time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
// if we exactly match our expected actions, wait a bit to make sure no other additional actions show up
|
||||
if len(client.Actions()) == len(tc.ExpectedActions) && !waitedForAdditionalActions {
|
||||
time.Sleep(time.Second)
|
||||
waitedForAdditionalActions = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if controller.syncServiceAccountQueue.Len() > 0 {
|
||||
t.Errorf("%s: unexpected items in service account queue: %d", k, controller.syncServiceAccountQueue.Len())
|
||||
}
|
||||
if controller.syncSecretQueue.Len() > 0 {
|
||||
t.Errorf("%s: unexpected items in secret queue: %d", k, controller.syncSecretQueue.Len())
|
||||
}
|
||||
|
||||
actions := client.Actions()
|
||||
for i, action := range actions {
|
||||
if len(tc.ExpectedActions) < i+1 {
|
||||
t.Errorf("%s: %d unexpected actions: %+v", k, len(actions)-len(tc.ExpectedActions), actions[i:])
|
||||
break
|
||||
}
|
||||
|
||||
expectedAction := tc.ExpectedActions[i]
|
||||
if !reflect.DeepEqual(expectedAction, action) {
|
||||
t.Errorf("%s:\nExpected:\n%s\ngot:\n%s", k, spew.Sdump(expectedAction), spew.Sdump(action))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(tc.ExpectedActions) > len(actions) {
|
||||
t.Errorf("%s: %d additional expected actions", k, len(tc.ExpectedActions)-len(actions))
|
||||
for _, a := range tc.ExpectedActions[len(actions):] {
|
||||
t.Logf(" %+v", a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user