Merge pull request #316 from enj/enj/i/always_set_owner_ref
Always set an owner ref back to our deployment
This commit is contained in:
commit
6a9976742c
@ -31,13 +31,13 @@ import (
|
|||||||
kubeinformers "k8s.io/client-go/informers"
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"go.pinniped.dev/internal/constable"
|
"go.pinniped.dev/internal/constable"
|
||||||
"go.pinniped.dev/internal/controller/apicerts"
|
"go.pinniped.dev/internal/controller/apicerts"
|
||||||
"go.pinniped.dev/internal/controllerlib"
|
"go.pinniped.dev/internal/controllerlib"
|
||||||
"go.pinniped.dev/internal/dynamiccert"
|
"go.pinniped.dev/internal/dynamiccert"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
"go.pinniped.dev/internal/plog"
|
"go.pinniped.dev/internal/plog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -279,21 +279,6 @@ func respondWithAuthenticated(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newK8sClient() (kubernetes.Interface, error) {
|
|
||||||
kubeConfig, err := restclient.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the core Kubernetes API.
|
|
||||||
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return kubeClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func startControllers(
|
func startControllers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
dynamicCertProvider dynamiccert.Provider,
|
dynamicCertProvider dynamiccert.Provider,
|
||||||
@ -359,20 +344,20 @@ func run() error {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, err := newK8sClient()
|
client, err := kubeclient.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create k8s client: %w", err)
|
return fmt.Errorf("cannot create k8s client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
|
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
|
||||||
kubeClient,
|
client.Kubernetes,
|
||||||
defaultResyncInterval,
|
defaultResyncInterval,
|
||||||
kubeinformers.WithNamespace(namespace),
|
kubeinformers.WithNamespace(namespace),
|
||||||
)
|
)
|
||||||
|
|
||||||
dynamicCertProvider := dynamiccert.New()
|
dynamicCertProvider := dynamiccert.New()
|
||||||
|
|
||||||
startControllers(ctx, dynamicCertProvider, kubeClient, kubeInformers)
|
startControllers(ctx, dynamicCertProvider, client.Kubernetes, kubeInformers)
|
||||||
plog.Debug("controllers are ready")
|
plog.Debug("controllers are ready")
|
||||||
|
|
||||||
//nolint: gosec // Intentionally binding to all network interfaces.
|
//nolint: gosec // Intentionally binding to all network interfaces.
|
||||||
|
@ -17,13 +17,11 @@ import (
|
|||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/clock"
|
"k8s.io/apimachinery/pkg/util/clock"
|
||||||
kubeinformers "k8s.io/client-go/informers"
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/pkg/version"
|
"k8s.io/client-go/pkg/version"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/component-base/logs"
|
"k8s.io/component-base/logs"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
"k8s.io/klog/v2/klogr"
|
"k8s.io/klog/v2/klogr"
|
||||||
@ -37,7 +35,9 @@ import (
|
|||||||
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatcher"
|
"go.pinniped.dev/internal/controller/supervisorconfig/upstreamwatcher"
|
||||||
"go.pinniped.dev/internal/controller/supervisorstorage"
|
"go.pinniped.dev/internal/controller/supervisorstorage"
|
||||||
"go.pinniped.dev/internal/controllerlib"
|
"go.pinniped.dev/internal/controllerlib"
|
||||||
|
"go.pinniped.dev/internal/deploymentref"
|
||||||
"go.pinniped.dev/internal/downward"
|
"go.pinniped.dev/internal/downward"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
"go.pinniped.dev/internal/oidc/jwks"
|
"go.pinniped.dev/internal/oidc/jwks"
|
||||||
"go.pinniped.dev/internal/oidc/provider"
|
"go.pinniped.dev/internal/oidc/provider"
|
||||||
"go.pinniped.dev/internal/oidc/provider/manager"
|
"go.pinniped.dev/internal/oidc/provider/manager"
|
||||||
@ -252,81 +252,31 @@ func startControllers(
|
|||||||
go controllerManager.Start(ctx)
|
go controllerManager.Start(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSupervisorDeployment(
|
|
||||||
ctx context.Context,
|
|
||||||
kubeClient kubernetes.Interface,
|
|
||||||
podInfo *downward.PodInfo,
|
|
||||||
) (*appsv1.Deployment, error) {
|
|
||||||
ns := podInfo.Namespace
|
|
||||||
|
|
||||||
pod, err := kubeClient.CoreV1().Pods(ns).Get(ctx, podInfo.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not get pod: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
podOwner := metav1.GetControllerOf(pod)
|
|
||||||
if podOwner == nil {
|
|
||||||
return nil, fmt.Errorf("pod %s/%s is missing owner", ns, podInfo.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
rs, err := kubeClient.AppsV1().ReplicaSets(ns).Get(ctx, podOwner.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not get replicaset: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rsOwner := metav1.GetControllerOf(rs)
|
|
||||||
if rsOwner == nil {
|
|
||||||
return nil, fmt.Errorf("replicaset %s/%s is missing owner", ns, podInfo.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := kubeClient.AppsV1().Deployments(ns).Get(ctx, rsOwner.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not get deployment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClients() (kubernetes.Interface, pinnipedclientset.Interface, error) {
|
|
||||||
kubeConfig, err := restclient.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the core Kubernetes API.
|
|
||||||
kubeClient, err := kubernetes.NewForConfig(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not create kube client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the Pinniped API.
|
|
||||||
pinnipedClient, err := pinnipedclientset.NewForConfig(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not create pinniped client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return kubeClient, pinnipedClient, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
||||||
serverInstallationNamespace := podInfo.Namespace
|
serverInstallationNamespace := podInfo.Namespace
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
kubeClient, pinnipedClient, err := newClients()
|
// TODO remove code that relies on supervisorDeployment directly
|
||||||
|
dref, supervisorDeployment, err := deploymentref.New(podInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create deployment ref: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := kubeclient.New(dref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot create k8s client: %w", err)
|
return fmt.Errorf("cannot create k8s client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
|
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(
|
||||||
kubeClient,
|
client.Kubernetes,
|
||||||
defaultResyncInterval,
|
defaultResyncInterval,
|
||||||
kubeinformers.WithNamespace(serverInstallationNamespace),
|
kubeinformers.WithNamespace(serverInstallationNamespace),
|
||||||
)
|
)
|
||||||
|
|
||||||
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
|
pinnipedInformers := pinnipedinformers.NewSharedInformerFactoryWithOptions(
|
||||||
pinnipedClient,
|
client.PinnipedSupervisor,
|
||||||
defaultResyncInterval,
|
defaultResyncInterval,
|
||||||
pinnipedinformers.WithNamespace(serverInstallationNamespace),
|
pinnipedinformers.WithNamespace(serverInstallationNamespace),
|
||||||
)
|
)
|
||||||
@ -348,14 +298,9 @@ func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
|||||||
dynamicJWKSProvider,
|
dynamicJWKSProvider,
|
||||||
dynamicUpstreamIDPProvider,
|
dynamicUpstreamIDPProvider,
|
||||||
&secretCache,
|
&secretCache,
|
||||||
kubeClient.CoreV1().Secrets(serverInstallationNamespace),
|
client.Kubernetes.CoreV1().Secrets(serverInstallationNamespace),
|
||||||
)
|
)
|
||||||
|
|
||||||
supervisorDeployment, err := getSupervisorDeployment(ctx, kubeClient, podInfo)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot get supervisor deployment: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
startControllers(
|
startControllers(
|
||||||
ctx,
|
ctx,
|
||||||
cfg,
|
cfg,
|
||||||
@ -365,8 +310,8 @@ func run(podInfo *downward.PodInfo, cfg *supervisor.Config) error {
|
|||||||
dynamicUpstreamIDPProvider,
|
dynamicUpstreamIDPProvider,
|
||||||
&secretCache,
|
&secretCache,
|
||||||
supervisorDeployment,
|
supervisorDeployment,
|
||||||
kubeClient,
|
client.Kubernetes,
|
||||||
pinnipedClient,
|
client.PinnipedSupervisor,
|
||||||
kubeInformers,
|
kubeInformers,
|
||||||
pinnipedInformers,
|
pinnipedInformers,
|
||||||
)
|
)
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
conciergev1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1"
|
conciergev1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/authentication/v1alpha1"
|
||||||
conciergeclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
conciergeclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
type kubeconfigDeps struct {
|
type kubeconfigDeps struct {
|
||||||
@ -41,7 +42,11 @@ func kubeconfigRealDeps() kubeconfigDeps {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return conciergeclientset.NewForConfig(restConfig)
|
client, err := kubeclient.New(kubeclient.WithConfig(restConfig))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client.PinnipedConcierge, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,9 @@ spec:
|
|||||||
- path: "labels"
|
- path: "labels"
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: metadata.labels
|
fieldPath: metadata.labels
|
||||||
|
- path: "name"
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
- path: "namespace"
|
- path: "namespace"
|
||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: metadata.namespace
|
fieldPath: metadata.namespace
|
||||||
|
@ -69,6 +69,9 @@ rules:
|
|||||||
- apiGroups: [ config.concierge.pinniped.dev, authentication.concierge.pinniped.dev ]
|
- apiGroups: [ config.concierge.pinniped.dev, authentication.concierge.pinniped.dev ]
|
||||||
resources: [ "*" ]
|
resources: [ "*" ]
|
||||||
verbs: [ create, get, list, update, watch ]
|
verbs: [ create, get, list, update, watch ]
|
||||||
|
- apiGroups: [apps]
|
||||||
|
resources: [replicasets,deployments]
|
||||||
|
verbs: [get]
|
||||||
---
|
---
|
||||||
kind: RoleBinding
|
kind: RoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"go.pinniped.dev/generated/1.19/apis/concierge/login/v1alpha1"
|
"go.pinniped.dev/generated/1.19/apis/concierge/login/v1alpha1"
|
||||||
"go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
"go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrLoginFailed is returned by ExchangeToken when the server rejects the login request.
|
// ErrLoginFailed is returned by ExchangeToken when the server rejects the login request.
|
||||||
@ -84,5 +85,9 @@ func getClient(apiEndpoint string, caBundle string) (versioned.Interface, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return versioned.NewForConfig(cfg)
|
client, err := kubeclient.New(kubeclient.WithConfig(cfg))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client.PinnipedConcierge, nil
|
||||||
}
|
}
|
||||||
|
@ -105,7 +105,6 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not read pod metadata: %w", err)
|
return fmt.Errorf("could not read pod metadata: %w", err)
|
||||||
}
|
}
|
||||||
serverInstallationNamespace := podInfo.Namespace
|
|
||||||
|
|
||||||
// Initialize the cache of active authenticators.
|
// Initialize the cache of active authenticators.
|
||||||
authenticators := authncache.New()
|
authenticators := authncache.New()
|
||||||
@ -125,16 +124,16 @@ func (a *App) runServer(ctx context.Context) error {
|
|||||||
// post start hook of the aggregated API server.
|
// post start hook of the aggregated API server.
|
||||||
startControllersFunc, err := controllermanager.PrepareControllers(
|
startControllersFunc, err := controllermanager.PrepareControllers(
|
||||||
&controllermanager.Config{
|
&controllermanager.Config{
|
||||||
ServerInstallationNamespace: serverInstallationNamespace,
|
ServerInstallationInfo: podInfo,
|
||||||
NamesConfig: &cfg.NamesConfig,
|
NamesConfig: &cfg.NamesConfig,
|
||||||
Labels: cfg.Labels,
|
Labels: cfg.Labels,
|
||||||
KubeCertAgentConfig: &cfg.KubeCertAgentConfig,
|
KubeCertAgentConfig: &cfg.KubeCertAgentConfig,
|
||||||
DiscoveryURLOverride: cfg.DiscoveryInfo.URL,
|
DiscoveryURLOverride: cfg.DiscoveryInfo.URL,
|
||||||
DynamicServingCertProvider: dynamicServingCertProvider,
|
DynamicServingCertProvider: dynamicServingCertProvider,
|
||||||
DynamicSigningCertProvider: dynamicSigningCertProvider,
|
DynamicSigningCertProvider: dynamicSigningCertProvider,
|
||||||
ServingCertDuration: time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds) * time.Second,
|
ServingCertDuration: time.Duration(*cfg.APIConfig.ServingCertificateConfig.DurationSeconds) * time.Second,
|
||||||
ServingCertRenewBefore: time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds) * time.Second,
|
ServingCertRenewBefore: time.Duration(*cfg.APIConfig.ServingCertificateConfig.RenewBeforeSeconds) * time.Second,
|
||||||
AuthenticatorCache: authenticators,
|
AuthenticatorCache: authenticators,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
@ -200,13 +199,7 @@ func generateSecret(namespace, name string, labels map[string]string, secretData
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
deploymentGVK := schema.GroupVersionKind{
|
deploymentGVK := appsv1.SchemeGroupVersion.WithKind("Deployment")
|
||||||
Group: appsv1.SchemeGroupVersion.Group,
|
|
||||||
Version: appsv1.SchemeGroupVersion.Version,
|
|
||||||
Kind: "Deployment",
|
|
||||||
}
|
|
||||||
|
|
||||||
isController := false
|
|
||||||
|
|
||||||
return &corev1.Secret{
|
return &corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@ -218,7 +211,6 @@ func generateSecret(namespace, name string, labels map[string]string, secretData
|
|||||||
Kind: deploymentGVK.Kind,
|
Kind: deploymentGVK.Kind,
|
||||||
Name: owner.GetName(),
|
Name: owner.GetName(),
|
||||||
UID: owner.GetUID(),
|
UID: owner.GetUID(),
|
||||||
Controller: &isController,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
|
@ -268,7 +268,6 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
|
|||||||
generatedSymmetricKey = []byte("some-neato-32-byte-generated-key")
|
generatedSymmetricKey = []byte("some-neato-32-byte-generated-key")
|
||||||
otherGeneratedSymmetricKey = []byte("some-funio-32-byte-generated-key")
|
otherGeneratedSymmetricKey = []byte("some-funio-32-byte-generated-key")
|
||||||
|
|
||||||
isController = false
|
|
||||||
generatedSecret = &corev1.Secret{
|
generatedSecret = &corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: generatedSecretName,
|
Name: generatedSecretName,
|
||||||
@ -279,7 +278,6 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
|
|||||||
Kind: ownerGVK.Kind,
|
Kind: ownerGVK.Kind,
|
||||||
Name: owner.GetName(),
|
Name: owner.GetName(),
|
||||||
UID: owner.GetUID(),
|
UID: owner.GetUID(),
|
||||||
Controller: &isController,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
@ -300,7 +298,6 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
|
|||||||
Kind: ownerGVK.Kind,
|
Kind: ownerGVK.Kind,
|
||||||
Name: owner.GetName(),
|
Name: owner.GetName(),
|
||||||
UID: owner.GetUID(),
|
UID: owner.GetUID(),
|
||||||
Controller: &isController,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
|
@ -10,14 +10,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/clock"
|
"k8s.io/apimachinery/pkg/util/clock"
|
||||||
k8sinformers "k8s.io/client-go/informers"
|
k8sinformers "k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/klog/v2/klogr"
|
"k8s.io/klog/v2/klogr"
|
||||||
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
|
||||||
|
|
||||||
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/login/v1alpha1"
|
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/login/v1alpha1"
|
||||||
pinnipedclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
pinnipedclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
||||||
@ -31,7 +27,10 @@ import (
|
|||||||
"go.pinniped.dev/internal/controller/issuerconfig"
|
"go.pinniped.dev/internal/controller/issuerconfig"
|
||||||
"go.pinniped.dev/internal/controller/kubecertagent"
|
"go.pinniped.dev/internal/controller/kubecertagent"
|
||||||
"go.pinniped.dev/internal/controllerlib"
|
"go.pinniped.dev/internal/controllerlib"
|
||||||
|
"go.pinniped.dev/internal/deploymentref"
|
||||||
|
"go.pinniped.dev/internal/downward"
|
||||||
"go.pinniped.dev/internal/dynamiccert"
|
"go.pinniped.dev/internal/dynamiccert"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -43,8 +42,8 @@ const (
|
|||||||
//
|
//
|
||||||
// It is used to inject parameters into PrepareControllers.
|
// It is used to inject parameters into PrepareControllers.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// ServerInstallationNamespace provides the namespace in which Pinniped is deployed.
|
// ServerInstallationInfo provides the name of the pod in which Pinniped is running and the namespace in which Pinniped is deployed.
|
||||||
ServerInstallationNamespace string
|
ServerInstallationInfo *downward.PodInfo
|
||||||
|
|
||||||
// NamesConfig comes from the Pinniped config API (see api.Config). It specifies how Kubernetes
|
// NamesConfig comes from the Pinniped config API (see api.Config). It specifies how Kubernetes
|
||||||
// objects should be named.
|
// objects should be named.
|
||||||
@ -81,29 +80,29 @@ type Config struct {
|
|||||||
// Prepare the controllers and their informers and return a function that will start them when called.
|
// Prepare the controllers and their informers and return a function that will start them when called.
|
||||||
//nolint:funlen // Eh, fair, it is a really long function...but it is wiring the world...so...
|
//nolint:funlen // Eh, fair, it is a really long function...but it is wiring the world...so...
|
||||||
func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
||||||
// Create k8s clients.
|
dref, _, err := deploymentref.New(c.ServerInstallationInfo)
|
||||||
kubeConfig, err := createConfig()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create config for the controllers: %w", err)
|
return nil, fmt.Errorf("cannot create deployment ref: %w", err)
|
||||||
}
|
}
|
||||||
k8sClient, aggregatorClient, pinnipedClient, err := createClients(kubeConfig)
|
|
||||||
|
client, err := kubeclient.New(dref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not create clients for the controllers: %w", err)
|
return nil, fmt.Errorf("could not create clients for the controllers: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create informers. Don't forget to make sure they get started in the function returned below.
|
// Create informers. Don't forget to make sure they get started in the function returned below.
|
||||||
informers := createInformers(c.ServerInstallationNamespace, k8sClient, pinnipedClient)
|
informers := createInformers(c.ServerInstallationInfo.Namespace, client.Kubernetes, client.PinnipedConcierge)
|
||||||
|
|
||||||
// Configuration for the kubecertagent controllers created below.
|
// Configuration for the kubecertagent controllers created below.
|
||||||
agentPodConfig := &kubecertagent.AgentPodConfig{
|
agentPodConfig := &kubecertagent.AgentPodConfig{
|
||||||
Namespace: c.ServerInstallationNamespace,
|
Namespace: c.ServerInstallationInfo.Namespace,
|
||||||
ContainerImage: *c.KubeCertAgentConfig.Image,
|
ContainerImage: *c.KubeCertAgentConfig.Image,
|
||||||
PodNamePrefix: *c.KubeCertAgentConfig.NamePrefix,
|
PodNamePrefix: *c.KubeCertAgentConfig.NamePrefix,
|
||||||
ContainerImagePullSecrets: c.KubeCertAgentConfig.ImagePullSecrets,
|
ContainerImagePullSecrets: c.KubeCertAgentConfig.ImagePullSecrets,
|
||||||
AdditionalLabels: c.Labels,
|
AdditionalLabels: c.Labels,
|
||||||
}
|
}
|
||||||
credentialIssuerLocationConfig := &kubecertagent.CredentialIssuerLocationConfig{
|
credentialIssuerLocationConfig := &kubecertagent.CredentialIssuerLocationConfig{
|
||||||
Namespace: c.ServerInstallationNamespace,
|
Namespace: c.ServerInstallationInfo.Namespace,
|
||||||
Name: c.NamesConfig.CredentialIssuer,
|
Name: c.NamesConfig.CredentialIssuer,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,11 +114,11 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
// CredentialIssuer resource and keeping that information up to date.
|
// CredentialIssuer resource and keeping that information up to date.
|
||||||
WithController(
|
WithController(
|
||||||
issuerconfig.NewKubeConfigInfoPublisherController(
|
issuerconfig.NewKubeConfigInfoPublisherController(
|
||||||
c.ServerInstallationNamespace,
|
c.ServerInstallationInfo.Namespace,
|
||||||
c.NamesConfig.CredentialIssuer,
|
c.NamesConfig.CredentialIssuer,
|
||||||
c.Labels,
|
c.Labels,
|
||||||
c.DiscoveryURLOverride,
|
c.DiscoveryURLOverride,
|
||||||
pinnipedClient,
|
client.PinnipedConcierge,
|
||||||
informers.kubePublicNamespaceK8s.Core().V1().ConfigMaps(),
|
informers.kubePublicNamespaceK8s.Core().V1().ConfigMaps(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
),
|
),
|
||||||
@ -129,10 +128,10 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
// API certs controllers are responsible for managing the TLS certificates used to serve Pinniped's API.
|
// API certs controllers are responsible for managing the TLS certificates used to serve Pinniped's API.
|
||||||
WithController(
|
WithController(
|
||||||
apicerts.NewCertsManagerController(
|
apicerts.NewCertsManagerController(
|
||||||
c.ServerInstallationNamespace,
|
c.ServerInstallationInfo.Namespace,
|
||||||
c.NamesConfig.ServingCertificateSecret,
|
c.NamesConfig.ServingCertificateSecret,
|
||||||
c.Labels,
|
c.Labels,
|
||||||
k8sClient,
|
client.Kubernetes,
|
||||||
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
controllerlib.WithInitialEvent,
|
controllerlib.WithInitialEvent,
|
||||||
@ -144,10 +143,10 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
).
|
).
|
||||||
WithController(
|
WithController(
|
||||||
apicerts.NewAPIServiceUpdaterController(
|
apicerts.NewAPIServiceUpdaterController(
|
||||||
c.ServerInstallationNamespace,
|
c.ServerInstallationInfo.Namespace,
|
||||||
c.NamesConfig.ServingCertificateSecret,
|
c.NamesConfig.ServingCertificateSecret,
|
||||||
loginv1alpha1.SchemeGroupVersion.Version+"."+loginv1alpha1.GroupName,
|
loginv1alpha1.SchemeGroupVersion.Version+"."+loginv1alpha1.GroupName,
|
||||||
aggregatorClient,
|
client.Aggregation,
|
||||||
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
),
|
),
|
||||||
@ -155,7 +154,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
).
|
).
|
||||||
WithController(
|
WithController(
|
||||||
apicerts.NewCertsObserverController(
|
apicerts.NewCertsObserverController(
|
||||||
c.ServerInstallationNamespace,
|
c.ServerInstallationInfo.Namespace,
|
||||||
c.NamesConfig.ServingCertificateSecret,
|
c.NamesConfig.ServingCertificateSecret,
|
||||||
c.DynamicServingCertProvider,
|
c.DynamicServingCertProvider,
|
||||||
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
||||||
@ -165,9 +164,9 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
).
|
).
|
||||||
WithController(
|
WithController(
|
||||||
apicerts.NewCertsExpirerController(
|
apicerts.NewCertsExpirerController(
|
||||||
c.ServerInstallationNamespace,
|
c.ServerInstallationInfo.Namespace,
|
||||||
c.NamesConfig.ServingCertificateSecret,
|
c.NamesConfig.ServingCertificateSecret,
|
||||||
k8sClient,
|
client.Kubernetes,
|
||||||
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
informers.installationNamespaceK8s.Core().V1().Secrets(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
c.ServingCertRenewBefore,
|
c.ServingCertRenewBefore,
|
||||||
@ -183,8 +182,8 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
credentialIssuerLocationConfig,
|
credentialIssuerLocationConfig,
|
||||||
c.Labels,
|
c.Labels,
|
||||||
clock.RealClock{},
|
clock.RealClock{},
|
||||||
k8sClient,
|
client.Kubernetes,
|
||||||
pinnipedClient,
|
client.PinnipedConcierge,
|
||||||
informers.kubeSystemNamespaceK8s.Core().V1().Pods(),
|
informers.kubeSystemNamespaceK8s.Core().V1().Pods(),
|
||||||
informers.installationNamespaceK8s.Core().V1().Pods(),
|
informers.installationNamespaceK8s.Core().V1().Pods(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
@ -197,8 +196,8 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
agentPodConfig,
|
agentPodConfig,
|
||||||
credentialIssuerLocationConfig,
|
credentialIssuerLocationConfig,
|
||||||
clock.RealClock{},
|
clock.RealClock{},
|
||||||
k8sClient,
|
client.Kubernetes,
|
||||||
pinnipedClient,
|
client.PinnipedConcierge,
|
||||||
informers.kubeSystemNamespaceK8s.Core().V1().Pods(),
|
informers.kubeSystemNamespaceK8s.Core().V1().Pods(),
|
||||||
informers.installationNamespaceK8s.Core().V1().Pods(),
|
informers.installationNamespaceK8s.Core().V1().Pods(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
@ -209,8 +208,8 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
kubecertagent.NewExecerController(
|
kubecertagent.NewExecerController(
|
||||||
credentialIssuerLocationConfig,
|
credentialIssuerLocationConfig,
|
||||||
c.DynamicSigningCertProvider,
|
c.DynamicSigningCertProvider,
|
||||||
kubecertagent.NewPodCommandExecutor(kubeConfig, k8sClient),
|
kubecertagent.NewPodCommandExecutor(client.JSONConfig, client.Kubernetes),
|
||||||
pinnipedClient,
|
client.PinnipedConcierge,
|
||||||
clock.RealClock{},
|
clock.RealClock{},
|
||||||
informers.installationNamespaceK8s.Core().V1().Pods(),
|
informers.installationNamespaceK8s.Core().V1().Pods(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
@ -220,7 +219,7 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
WithController(
|
WithController(
|
||||||
kubecertagent.NewDeleterController(
|
kubecertagent.NewDeleterController(
|
||||||
agentPodConfig,
|
agentPodConfig,
|
||||||
k8sClient,
|
client.Kubernetes,
|
||||||
informers.kubeSystemNamespaceK8s.Core().V1().Pods(),
|
informers.kubeSystemNamespaceK8s.Core().V1().Pods(),
|
||||||
informers.installationNamespaceK8s.Core().V1().Pods(),
|
informers.installationNamespaceK8s.Core().V1().Pods(),
|
||||||
controllerlib.WithInformer,
|
controllerlib.WithInformer,
|
||||||
@ -263,51 +262,6 @@ func PrepareControllers(c *Config) (func(ctx context.Context), error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the rest config that will be used by the clients for the controllers.
|
|
||||||
func createConfig() (*rest.Config, error) {
|
|
||||||
// Load the Kubernetes client configuration.
|
|
||||||
kubeConfig, err := restclient.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return kubeConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the k8s clients that will be used by the controllers.
|
|
||||||
func createClients(kubeConfig *rest.Config) (
|
|
||||||
k8sClient *kubernetes.Clientset,
|
|
||||||
aggregatorClient *aggregatorclient.Clientset,
|
|
||||||
pinnipedClient *pinnipedclientset.Clientset,
|
|
||||||
err error,
|
|
||||||
) {
|
|
||||||
// explicitly use protobuf when talking to built-in kube APIs
|
|
||||||
protoKubeConfig := createProtoKubeConfig(kubeConfig)
|
|
||||||
|
|
||||||
// Connect to the core Kubernetes API.
|
|
||||||
k8sClient, err = kubernetes.NewForConfig(protoKubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the Kubernetes aggregation API.
|
|
||||||
aggregatorClient, err = aggregatorclient.NewForConfig(protoKubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect to the pinniped API.
|
|
||||||
// I think we can't use protobuf encoding here because we are using CRDs
|
|
||||||
// (for which protobuf encoding is not supported).
|
|
||||||
pinnipedClient, err = pinnipedclientset.NewForConfig(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("could not initialize pinniped client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint: nakedret // Short function. Makes the order of return values more clear.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type informers struct {
|
type informers struct {
|
||||||
kubePublicNamespaceK8s k8sinformers.SharedInformerFactory
|
kubePublicNamespaceK8s k8sinformers.SharedInformerFactory
|
||||||
kubeSystemNamespaceK8s k8sinformers.SharedInformerFactory
|
kubeSystemNamespaceK8s k8sinformers.SharedInformerFactory
|
||||||
@ -318,8 +272,8 @@ type informers struct {
|
|||||||
// Create the informers that will be used by the controllers.
|
// Create the informers that will be used by the controllers.
|
||||||
func createInformers(
|
func createInformers(
|
||||||
serverInstallationNamespace string,
|
serverInstallationNamespace string,
|
||||||
k8sClient *kubernetes.Clientset,
|
k8sClient kubernetes.Interface,
|
||||||
pinnipedClient *pinnipedclientset.Clientset,
|
pinnipedClient pinnipedclientset.Interface,
|
||||||
) *informers {
|
) *informers {
|
||||||
return &informers{
|
return &informers{
|
||||||
kubePublicNamespaceK8s: k8sinformers.NewSharedInformerFactoryWithOptions(
|
kubePublicNamespaceK8s: k8sinformers.NewSharedInformerFactoryWithOptions(
|
||||||
@ -356,13 +310,3 @@ func (i *informers) startAndWaitForSync(ctx context.Context) {
|
|||||||
i.installationNamespaceK8s.WaitForCacheSync(ctx.Done())
|
i.installationNamespaceK8s.WaitForCacheSync(ctx.Done())
|
||||||
i.installationNamespacePinniped.WaitForCacheSync(ctx.Done())
|
i.installationNamespacePinniped.WaitForCacheSync(ctx.Done())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a copy of the input config with the ContentConfig set to use protobuf.
|
|
||||||
// Do not use this config to communicate with any CRD based APIs.
|
|
||||||
func createProtoKubeConfig(kubeConfig *restclient.Config) *restclient.Config {
|
|
||||||
protoKubeConfig := restclient.CopyConfig(kubeConfig)
|
|
||||||
const protoThenJSON = runtime.ContentTypeProtobuf + "," + runtime.ContentTypeJSON
|
|
||||||
protoKubeConfig.AcceptContentTypes = protoThenJSON
|
|
||||||
protoKubeConfig.ContentType = runtime.ContentTypeProtobuf
|
|
||||||
return protoKubeConfig
|
|
||||||
}
|
|
||||||
|
72
internal/deploymentref/deploymentref.go
Normal file
72
internal/deploymentref/deploymentref.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package deploymentref
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/downward"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
|
"go.pinniped.dev/internal/ownerref"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(podInfo *downward.PodInfo) (kubeclient.Option, *appsv1.Deployment, error) {
|
||||||
|
tempClient, err := kubeclient.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("cannot create temp client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployment, err := getDeployment(tempClient.Kubernetes, podInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("cannot get deployment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := metav1.OwnerReference{
|
||||||
|
Name: deployment.Name,
|
||||||
|
UID: deployment.UID,
|
||||||
|
}
|
||||||
|
ref.APIVersion, ref.Kind = appsv1.SchemeGroupVersion.WithKind("Deployment").ToAPIVersionAndKind()
|
||||||
|
|
||||||
|
return kubeclient.WithMiddleware(ownerref.New(ref)), deployment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeployment(kubeClient kubernetes.Interface, podInfo *downward.PodInfo) (*appsv1.Deployment, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ns := podInfo.Namespace
|
||||||
|
|
||||||
|
pod, err := kubeClient.CoreV1().Pods(ns).Get(ctx, podInfo.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get pod: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
podOwner := metav1.GetControllerOf(pod)
|
||||||
|
if podOwner == nil {
|
||||||
|
return nil, fmt.Errorf("pod %s/%s is missing owner", ns, podInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := kubeClient.AppsV1().ReplicaSets(ns).Get(ctx, podOwner.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get replicaset: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rsOwner := metav1.GetControllerOf(rs)
|
||||||
|
if rsOwner == nil {
|
||||||
|
return nil, fmt.Errorf("replicaset %s/%s is missing owner", ns, podInfo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := kubeClient.AppsV1().Deployments(ns).Get(ctx, rsOwner.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not get deployment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
117
internal/kubeclient/kubeclient.go
Normal file
117
internal/kubeclient/kubeclient.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package kubeclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
kubescheme "k8s.io/client-go/kubernetes/scheme"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
aggregatorclientscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||||
|
|
||||||
|
pinnipedconciergeclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
||||||
|
pinnipedconciergeclientsetscheme "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned/scheme"
|
||||||
|
pinnipedsupervisorclientset "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned"
|
||||||
|
pinnipedsupervisorclientsetscheme "go.pinniped.dev/generated/1.19/client/supervisor/clientset/versioned/scheme"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Kubernetes kubernetes.Interface
|
||||||
|
Aggregation aggregatorclient.Interface
|
||||||
|
PinnipedConcierge pinnipedconciergeclientset.Interface
|
||||||
|
PinnipedSupervisor pinnipedsupervisorclientset.Interface
|
||||||
|
|
||||||
|
JSONConfig, ProtoConfig *restclient.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO expand this interface to address more complex use cases.
|
||||||
|
type Middleware interface {
|
||||||
|
Handles(httpMethod string) bool
|
||||||
|
Mutate(obj metav1.Object) (mutated bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...Option) (*Client, error) {
|
||||||
|
c := &clientConfig{}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to assuming we are running in a pod with the service account token mounted
|
||||||
|
if c.config == nil {
|
||||||
|
inClusterConfig, err := restclient.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not load in-cluster configuration: %w", err)
|
||||||
|
}
|
||||||
|
WithConfig(inClusterConfig)(c) // make sure all writes to clientConfig flow through one code path
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicitly use json when talking to CRD APIs
|
||||||
|
jsonKubeConfig := createJSONKubeConfig(c.config)
|
||||||
|
|
||||||
|
// explicitly use protobuf when talking to built-in kube APIs
|
||||||
|
protoKubeConfig := createProtoKubeConfig(c.config)
|
||||||
|
|
||||||
|
// Connect to the core Kubernetes API.
|
||||||
|
k8sClient, err := kubernetes.NewForConfig(configWithWrapper(protoKubeConfig, kubescheme.Codecs, c.middlewares))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the Kubernetes aggregation API.
|
||||||
|
aggregatorClient, err := aggregatorclient.NewForConfig(configWithWrapper(protoKubeConfig, aggregatorclientscheme.Codecs, c.middlewares))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not initialize aggregation client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the pinniped concierge API.
|
||||||
|
// We cannot use protobuf encoding here because we are using CRDs
|
||||||
|
// (for which protobuf encoding is not yet supported).
|
||||||
|
// TODO we should try to add protobuf support to TokenCredentialRequests since it is an aggregated API
|
||||||
|
pinnipedConciergeClient, err := pinnipedconciergeclientset.NewForConfig(configWithWrapper(jsonKubeConfig, pinnipedconciergeclientsetscheme.Codecs, c.middlewares))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not initialize pinniped client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the pinniped supervisor API.
|
||||||
|
// We cannot use protobuf encoding here because we are using CRDs
|
||||||
|
// (for which protobuf encoding is not yet supported).
|
||||||
|
pinnipedSupervisorClient, err := pinnipedsupervisorclientset.NewForConfig(configWithWrapper(jsonKubeConfig, pinnipedsupervisorclientsetscheme.Codecs, c.middlewares))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not initialize pinniped client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
Kubernetes: k8sClient,
|
||||||
|
Aggregation: aggregatorClient,
|
||||||
|
PinnipedConcierge: pinnipedConciergeClient,
|
||||||
|
PinnipedSupervisor: pinnipedSupervisorClient,
|
||||||
|
|
||||||
|
JSONConfig: jsonKubeConfig,
|
||||||
|
ProtoConfig: protoKubeConfig,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a copy of the input config with the ContentConfig set to use json.
|
||||||
|
// Use this config to communicate with all CRD based APIs.
|
||||||
|
func createJSONKubeConfig(kubeConfig *restclient.Config) *restclient.Config {
|
||||||
|
jsonKubeConfig := restclient.CopyConfig(kubeConfig)
|
||||||
|
jsonKubeConfig.AcceptContentTypes = runtime.ContentTypeJSON
|
||||||
|
jsonKubeConfig.ContentType = runtime.ContentTypeJSON
|
||||||
|
return jsonKubeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a copy of the input config with the ContentConfig set to use protobuf.
|
||||||
|
// Do not use this config to communicate with any CRD based APIs.
|
||||||
|
func createProtoKubeConfig(kubeConfig *restclient.Config) *restclient.Config {
|
||||||
|
protoKubeConfig := restclient.CopyConfig(kubeConfig)
|
||||||
|
const protoThenJSON = runtime.ContentTypeProtobuf + "," + runtime.ContentTypeJSON
|
||||||
|
protoKubeConfig.AcceptContentTypes = protoThenJSON
|
||||||
|
protoKubeConfig.ContentType = runtime.ContentTypeProtobuf
|
||||||
|
return protoKubeConfig
|
||||||
|
}
|
25
internal/kubeclient/option.go
Normal file
25
internal/kubeclient/option.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package kubeclient
|
||||||
|
|
||||||
|
import restclient "k8s.io/client-go/rest"
|
||||||
|
|
||||||
|
type Option func(*clientConfig)
|
||||||
|
|
||||||
|
type clientConfig struct {
|
||||||
|
config *restclient.Config
|
||||||
|
middlewares []Middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithConfig(config *restclient.Config) Option {
|
||||||
|
return func(c *clientConfig) {
|
||||||
|
c.config = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMiddleware(middleware Middleware) Option {
|
||||||
|
return func(c *clientConfig) {
|
||||||
|
c.middlewares = append(c.middlewares, middleware)
|
||||||
|
}
|
||||||
|
}
|
122
internal/kubeclient/roundtrip.go
Normal file
122
internal/kubeclient/roundtrip.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package kubeclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO unit test
|
||||||
|
|
||||||
|
func configWithWrapper(config *restclient.Config, negotiatedSerializer runtime.NegotiatedSerializer, middlewares []Middleware) *restclient.Config {
|
||||||
|
// no need for any wrapping when we have no middleware to inject
|
||||||
|
if len(middlewares) == 0 {
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
info, ok := runtime.SerializerInfoForMediaType(negotiatedSerializer.SupportedMediaTypes(), config.ContentType)
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Errorf("unknown content type: %s ", config.ContentType)) // static input, programmer error
|
||||||
|
}
|
||||||
|
serializer := info.Serializer // should perform no conversion
|
||||||
|
|
||||||
|
f := func(rt http.RoundTripper) http.RoundTripper {
|
||||||
|
return roundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
// ignore everything that has an unreadable body
|
||||||
|
if req.GetBody == nil {
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reqMiddlewares []Middleware
|
||||||
|
for _, middleware := range middlewares {
|
||||||
|
middleware := middleware
|
||||||
|
if middleware.Handles(req.Method) {
|
||||||
|
reqMiddlewares = append(reqMiddlewares, middleware)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no middleware to handle this request
|
||||||
|
if len(reqMiddlewares) == 0 {
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := req.GetBody()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get body failed: %w", err)
|
||||||
|
}
|
||||||
|
defer body.Close()
|
||||||
|
data, err := ioutil.ReadAll(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read body failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to decode with no defaults or into specified, i.e. defer to the decoder
|
||||||
|
// this should result in the a straight decode with no conversion
|
||||||
|
obj, _, err := serializer.Decode(data, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("body decode failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return rt.RoundTrip(req) // ignore everything that has no object meta for now
|
||||||
|
}
|
||||||
|
|
||||||
|
// run all the mutating operations
|
||||||
|
var reqMutated bool
|
||||||
|
for _, reqMiddleware := range reqMiddlewares {
|
||||||
|
mutated := reqMiddleware.Mutate(accessor)
|
||||||
|
reqMutated = mutated || reqMutated
|
||||||
|
}
|
||||||
|
|
||||||
|
// no mutation occurred, keep the original request
|
||||||
|
if !reqMutated {
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we plan on making a new request so make sure to close the original request's body
|
||||||
|
_ = req.Body.Close()
|
||||||
|
|
||||||
|
newData, err := runtime.Encode(serializer, obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("new body encode failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO log newData at high loglevel similar to REST client
|
||||||
|
|
||||||
|
// simplest way to reuse the body creation logic
|
||||||
|
newReqForBody, err := http.NewRequest(req.Method, req.URL.String(), bytes.NewReader(newData))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create new req for body: %w", err) // this should never happen
|
||||||
|
}
|
||||||
|
|
||||||
|
// shallow copy because we want to preserve all the headers and such but not mutate the original request
|
||||||
|
newReq := req.WithContext(req.Context())
|
||||||
|
|
||||||
|
// replace the body with the new data
|
||||||
|
newReq.ContentLength = newReqForBody.ContentLength
|
||||||
|
newReq.Body = newReqForBody.Body
|
||||||
|
newReq.GetBody = newReqForBody.GetBody
|
||||||
|
|
||||||
|
return rt.RoundTrip(newReq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := restclient.CopyConfig(config)
|
||||||
|
cc.Wrap(f)
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
type roundTripperFunc func(req *http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return f(req)
|
||||||
|
}
|
39
internal/ownerref/ownerref.go
Normal file
39
internal/ownerref/ownerref.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package ownerref
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(ref metav1.OwnerReference) kubeclient.Middleware {
|
||||||
|
return ownerRefMiddleware(ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ kubeclient.Middleware = ownerRefMiddleware(metav1.OwnerReference{})
|
||||||
|
|
||||||
|
type ownerRefMiddleware metav1.OwnerReference
|
||||||
|
|
||||||
|
func (o ownerRefMiddleware) Handles(httpMethod string) bool {
|
||||||
|
return httpMethod == http.MethodPost // only handle create requests
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this func assumes all objects are namespace scoped and are in the same namespace.
|
||||||
|
// i.e. it assumes all objects are safe to set an owner ref on
|
||||||
|
// i.e. the owner could be namespace scoped and thus cannot own a cluster scoped object
|
||||||
|
// this could be fixed by using a rest mapper to confirm the REST scoping
|
||||||
|
// or we could always use an owner ref to a cluster scoped object
|
||||||
|
func (o ownerRefMiddleware) Mutate(obj metav1.Object) (mutated bool) {
|
||||||
|
// we only want to set the owner ref on create and when one is not already present
|
||||||
|
if len(obj.GetOwnerReferences()) != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.SetOwnerReferences([]metav1.OwnerReference{metav1.OwnerReference(o)})
|
||||||
|
return true
|
||||||
|
}
|
142
internal/ownerref/ownerref_test.go
Normal file
142
internal/ownerref/ownerref_test.go
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package ownerref
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOwnerReferenceMiddleware(t *testing.T) {
|
||||||
|
ref1 := metav1.OwnerReference{
|
||||||
|
Name: "earth",
|
||||||
|
UID: "0x11",
|
||||||
|
}
|
||||||
|
ref2 := metav1.OwnerReference{
|
||||||
|
Name: "mars",
|
||||||
|
UID: "0x12",
|
||||||
|
}
|
||||||
|
ref3 := metav1.OwnerReference{
|
||||||
|
Name: "sun",
|
||||||
|
UID: "0x13",
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "twizzlers"}}
|
||||||
|
configMap := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "pandas"}}
|
||||||
|
|
||||||
|
secretWithOwner := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "twizzlers", OwnerReferences: []metav1.OwnerReference{ref3}}}
|
||||||
|
configMapWithOwner := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "pandas", OwnerReferences: []metav1.OwnerReference{ref3}}}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
ref metav1.OwnerReference
|
||||||
|
httpMethod string
|
||||||
|
obj metav1.Object
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantHandles, wantMutates bool
|
||||||
|
wantObj metav1.Object
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "on update",
|
||||||
|
args: args{
|
||||||
|
ref: ref1,
|
||||||
|
httpMethod: http.MethodPut,
|
||||||
|
obj: secret.DeepCopy(),
|
||||||
|
},
|
||||||
|
wantHandles: false,
|
||||||
|
wantMutates: false,
|
||||||
|
wantObj: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "on create",
|
||||||
|
args: args{
|
||||||
|
ref: ref1,
|
||||||
|
httpMethod: http.MethodPost,
|
||||||
|
obj: secret.DeepCopy(),
|
||||||
|
},
|
||||||
|
wantHandles: true,
|
||||||
|
wantMutates: true,
|
||||||
|
wantObj: withOwnerRef(t, secret, ref1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "on create config map",
|
||||||
|
args: args{
|
||||||
|
ref: ref2,
|
||||||
|
httpMethod: http.MethodPost,
|
||||||
|
obj: configMap.DeepCopy(),
|
||||||
|
},
|
||||||
|
wantHandles: true,
|
||||||
|
wantMutates: true,
|
||||||
|
wantObj: withOwnerRef(t, configMap, ref2),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "on create with pre-existing ref",
|
||||||
|
args: args{
|
||||||
|
ref: ref1,
|
||||||
|
httpMethod: http.MethodPost,
|
||||||
|
obj: secretWithOwner.DeepCopy(),
|
||||||
|
},
|
||||||
|
wantHandles: true,
|
||||||
|
wantMutates: false,
|
||||||
|
wantObj: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "on create with pre-existing ref config map",
|
||||||
|
args: args{
|
||||||
|
ref: ref2,
|
||||||
|
httpMethod: http.MethodPost,
|
||||||
|
obj: configMapWithOwner.DeepCopy(),
|
||||||
|
},
|
||||||
|
wantHandles: true,
|
||||||
|
wantMutates: false,
|
||||||
|
wantObj: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
middleware := New(tt.args.ref)
|
||||||
|
|
||||||
|
handles := middleware.Handles(tt.args.httpMethod)
|
||||||
|
require.Equal(t, tt.wantHandles, handles)
|
||||||
|
|
||||||
|
if !handles {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orig := tt.args.obj.(runtime.Object).DeepCopyObject()
|
||||||
|
|
||||||
|
mutates := middleware.Mutate(tt.args.obj)
|
||||||
|
require.Equal(t, tt.wantMutates, mutates)
|
||||||
|
|
||||||
|
if mutates {
|
||||||
|
require.NotEqual(t, orig, tt.args.obj)
|
||||||
|
require.Equal(t, tt.wantObj, tt.args.obj)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, orig, tt.args.obj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withOwnerRef(t *testing.T, obj runtime.Object, ref metav1.OwnerReference) metav1.Object {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
obj = obj.DeepCopyObject()
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, accessor.GetOwnerReferences(), 0)
|
||||||
|
accessor.SetOwnerReferences([]metav1.OwnerReference{ref})
|
||||||
|
|
||||||
|
return accessor
|
||||||
|
}
|
@ -22,6 +22,7 @@ import (
|
|||||||
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/login/v1alpha1"
|
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/login/v1alpha1"
|
||||||
conciergeclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
conciergeclientset "go.pinniped.dev/generated/1.19/client/concierge/clientset/versioned"
|
||||||
"go.pinniped.dev/internal/constable"
|
"go.pinniped.dev/internal/constable"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrLoginFailed is returned by Client.ExchangeToken when the concierge server rejects the login request for any reason.
|
// ErrLoginFailed is returned by Client.ExchangeToken when the concierge server rejects the login request for any reason.
|
||||||
@ -150,7 +151,11 @@ func (c *Client) clientset() (conciergeclientset.Interface, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return conciergeclientset.NewForConfig(cfg)
|
client, err := kubeclient.New(kubeclient.WithConfig(cfg))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return client.PinnipedConcierge, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeToken performs a TokenCredentialRequest against the Pinniped concierge and returns the result as an ExecCredential.
|
// ExchangeToken performs a TokenCredentialRequest against the Pinniped concierge and returns the result as an ExecCredential.
|
||||||
|
259
test/integration/kubeclient_test.go
Normal file
259
test/integration/kubeclient_test.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
|
|
||||||
|
conciergeconfigv1alpha1 "go.pinniped.dev/generated/1.19/apis/concierge/config/v1alpha1"
|
||||||
|
supervisorconfigv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
|
||||||
|
"go.pinniped.dev/internal/kubeclient"
|
||||||
|
"go.pinniped.dev/internal/ownerref"
|
||||||
|
"go.pinniped.dev/test/library"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKubeClientOwnerRef(t *testing.T) {
|
||||||
|
env := library.IntegrationEnv(t)
|
||||||
|
|
||||||
|
regularClient := library.NewClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
namespaces := regularClient.CoreV1().Namespaces()
|
||||||
|
|
||||||
|
namespace, err := namespaces.Create(
|
||||||
|
ctx,
|
||||||
|
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{GenerateName: "test-owner-ref-"}},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := namespaces.Delete(ctx, namespace.Name, metav1.DeleteOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create something that we can point to
|
||||||
|
parentSecret, err := regularClient.CoreV1().Secrets(namespace.Name).Create(
|
||||||
|
ctx,
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "parent-",
|
||||||
|
OwnerReferences: nil, // no owner refs set
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{"A": []byte("B")},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, parentSecret.OwnerReferences, 0)
|
||||||
|
|
||||||
|
// create a client that should set an owner ref back to parent on create
|
||||||
|
ref := metav1.OwnerReference{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Secret",
|
||||||
|
Name: parentSecret.Name,
|
||||||
|
UID: parentSecret.UID,
|
||||||
|
}
|
||||||
|
ownerRefClient, err := kubeclient.New(
|
||||||
|
kubeclient.WithMiddleware(ownerref.New(ref)),
|
||||||
|
kubeclient.WithConfig(library.NewClientConfig(t)),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ownerRefSecrets := ownerRefClient.Kubernetes.CoreV1().Secrets(namespace.Name)
|
||||||
|
|
||||||
|
// we expect this secret to have the owner ref set even though we did not set it explicitly
|
||||||
|
childSecret, err := ownerRefSecrets.Create(
|
||||||
|
ctx,
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "child-",
|
||||||
|
OwnerReferences: nil, // no owner refs set
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{"C": []byte("D")},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hasOwnerRef(t, childSecret, ref)
|
||||||
|
|
||||||
|
preexistingRef := *ref.DeepCopy()
|
||||||
|
preexistingRef.Name = "different"
|
||||||
|
preexistingRef.UID = "different"
|
||||||
|
|
||||||
|
// we expect this secret to keep the owner ref that is was created with
|
||||||
|
otherSecret, err := ownerRefSecrets.Create(
|
||||||
|
ctx,
|
||||||
|
&corev1.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "child-",
|
||||||
|
OwnerReferences: []metav1.OwnerReference{preexistingRef}, // owner ref set explicitly
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{"C": []byte("D")},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hasOwnerRef(t, otherSecret, preexistingRef)
|
||||||
|
require.NotEqual(t, ref, preexistingRef)
|
||||||
|
// the secret has no owner so it should be immediately deleted
|
||||||
|
isEventuallyDeleted(t, func() error {
|
||||||
|
_, err := ownerRefSecrets.Get(ctx, otherSecret.Name, metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// we expect no owner ref to be set on update
|
||||||
|
parentSecretUpdate := parentSecret.DeepCopy()
|
||||||
|
parentSecretUpdate.Data = map[string][]byte{"E": []byte("F ")}
|
||||||
|
updatedParentSecret, err := ownerRefSecrets.Update(ctx, parentSecretUpdate, metav1.UpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, parentSecret.UID, updatedParentSecret.UID)
|
||||||
|
require.NotEqual(t, parentSecret.ResourceVersion, updatedParentSecret.ResourceVersion)
|
||||||
|
require.Len(t, updatedParentSecret.OwnerReferences, 0)
|
||||||
|
|
||||||
|
// delete the parent object
|
||||||
|
err = ownerRefSecrets.Delete(ctx, parentSecret.Name, metav1.DeleteOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// the child object should be cleaned up on its own
|
||||||
|
isEventuallyDeleted(t, func() error {
|
||||||
|
_, err := ownerRefSecrets.Get(ctx, childSecret.Name, metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// sanity check API service client
|
||||||
|
apiService, err := ownerRefClient.Aggregation.ApiregistrationV1().APIServices().Create(
|
||||||
|
ctx,
|
||||||
|
&apiregistrationv1.APIService{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "v1.pandas.dev",
|
||||||
|
OwnerReferences: nil, // no owner refs set
|
||||||
|
},
|
||||||
|
Spec: apiregistrationv1.APIServiceSpec{
|
||||||
|
Version: "v1",
|
||||||
|
Group: "pandas.dev",
|
||||||
|
GroupPriorityMinimum: 10_000,
|
||||||
|
VersionPriority: 500,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hasOwnerRef(t, apiService, ref)
|
||||||
|
// this owner ref is invalid for an API service so it should be immediately deleted
|
||||||
|
isEventuallyDeleted(t, func() error {
|
||||||
|
_, err := ownerRefClient.Aggregation.ApiregistrationV1().APIServices().Get(ctx, apiService.Name, metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// sanity check concierge client
|
||||||
|
credentialIssuer, err := ownerRefClient.PinnipedConcierge.ConfigV1alpha1().CredentialIssuers(namespace.Name).Create(
|
||||||
|
ctx,
|
||||||
|
&conciergeconfigv1alpha1.CredentialIssuer{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "owner-ref-test-",
|
||||||
|
OwnerReferences: nil, // no owner refs set
|
||||||
|
},
|
||||||
|
Status: conciergeconfigv1alpha1.CredentialIssuerStatus{
|
||||||
|
Strategies: []conciergeconfigv1alpha1.CredentialIssuerStrategy{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hasOwnerRef(t, credentialIssuer, ref)
|
||||||
|
// this owner has already been deleted so the cred issuer should be immediately deleted
|
||||||
|
isEventuallyDeleted(t, func() error {
|
||||||
|
_, err := ownerRefClient.PinnipedConcierge.ConfigV1alpha1().CredentialIssuers(namespace.Name).Get(ctx, credentialIssuer.Name, metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// sanity check supervisor client
|
||||||
|
federationDomain, err := ownerRefClient.PinnipedSupervisor.ConfigV1alpha1().FederationDomains(namespace.Name).Create(
|
||||||
|
ctx,
|
||||||
|
&supervisorconfigv1alpha1.FederationDomain{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "owner-ref-test-",
|
||||||
|
OwnerReferences: nil, // no owner refs set
|
||||||
|
},
|
||||||
|
Spec: supervisorconfigv1alpha1.FederationDomainSpec{
|
||||||
|
Issuer: "https://pandas.dev",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metav1.CreateOptions{},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
hasOwnerRef(t, federationDomain, ref)
|
||||||
|
// this owner has already been deleted so the federation domain should be immediately deleted
|
||||||
|
isEventuallyDeleted(t, func() error {
|
||||||
|
_, err := ownerRefClient.PinnipedSupervisor.ConfigV1alpha1().FederationDomains(namespace.Name).Get(ctx, federationDomain.Name, metav1.GetOptions{})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
// check some well-known, always created secrets to make sure they have an owner ref back to their deployment
|
||||||
|
|
||||||
|
dref := metav1.OwnerReference{}
|
||||||
|
dref.APIVersion, dref.Kind = appsv1.SchemeGroupVersion.WithKind("Deployment").ToAPIVersionAndKind()
|
||||||
|
|
||||||
|
supervisorDeployment, err := ownerRefClient.Kubernetes.AppsV1().Deployments(env.SupervisorNamespace).Get(ctx, env.SupervisorAppName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
supervisorKey, err := ownerRefClient.Kubernetes.CoreV1().Secrets(env.SupervisorNamespace).Get(ctx, env.SupervisorAppName+"-key", metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
supervisorDref := *dref.DeepCopy()
|
||||||
|
supervisorDref.Name = env.SupervisorAppName
|
||||||
|
supervisorDref.UID = supervisorDeployment.UID
|
||||||
|
hasOwnerRef(t, supervisorKey, supervisorDref)
|
||||||
|
|
||||||
|
conciergeDeployment, err := ownerRefClient.Kubernetes.AppsV1().Deployments(env.ConciergeNamespace).Get(ctx, env.ConciergeAppName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
conciergeCert, err := ownerRefClient.Kubernetes.CoreV1().Secrets(env.ConciergeNamespace).Get(ctx, env.ConciergeAppName+"-api-tls-serving-certificate", metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
conciergeDref := *dref.DeepCopy()
|
||||||
|
conciergeDref.Name = env.ConciergeAppName
|
||||||
|
conciergeDref.UID = conciergeDeployment.UID
|
||||||
|
hasOwnerRef(t, conciergeCert, conciergeDref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasOwnerRef(t *testing.T, obj metav1.Object, ref metav1.OwnerReference) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ownerReferences := obj.GetOwnerReferences()
|
||||||
|
require.Len(t, ownerReferences, 1)
|
||||||
|
require.Equal(t, ref, ownerReferences[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEventuallyDeleted(t *testing.T, f func() error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
err := f()
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return false
|
||||||
|
case errors.IsNotFound(err):
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
require.NoError(t, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}, time.Minute, time.Second)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user