refactor: update to the latest version of kubebuilder and add support for hydra v2 (#127)

This commit is contained in:
Damian Peckett
2023-08-08 10:30:24 +02:00
committed by GitHub
parent 8bd24af9ce
commit 2a6cef5006
26 changed files with 448 additions and 951 deletions

View File

@ -9,13 +9,13 @@ import (
"sync"
"github.com/go-logr/logr"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
"github.com/ory/hydra-maester/hydra"
@ -166,7 +166,7 @@ func (r *OAuth2ClientReconciler) Reconcile(ctx context.Context, req ctrl.Request
var secret apiv1.Secret
if err := r.Get(ctx, types.NamespacedName{Name: oauth2client.Spec.SecretName, Namespace: req.Namespace}, &secret); err != nil {
if apierrs.IsNotFound(err) {
if registerErr := r.registerOAuth2Client(ctx, &oauth2client, nil); registerErr != nil {
if registerErr := r.registerOAuth2Client(ctx, &oauth2client); registerErr != nil {
return ctrl.Result{}, registerErr
}
return ctrl.Result{}, nil
@ -200,7 +200,8 @@ func (r *OAuth2ClientReconciler) Reconcile(ctx context.Context, req ctrl.Request
fetched, found, err := hydraClient.GetOAuth2Client(string(credentials.ID))
if err != nil {
return ctrl.Result{}, err
} else if !found {
return ctrl.Result{}, fmt.Errorf("oauth2 client %s not found", credentials.ID)
}
if found {
@ -210,7 +211,7 @@ func (r *OAuth2ClientReconciler) Reconcile(ctx context.Context, req ctrl.Request
}
if fetched.Owner != fmt.Sprintf("%s/%s", oauth2client.Name, oauth2client.Namespace) {
conflictErr := errors.Errorf("ID provided in secret %s/%s is assigned to another resource", secret.Name, secret.Namespace)
conflictErr := fmt.Errorf("ID provided in secret %s/%s is assigned to another resource", secret.Name, secret.Namespace)
if updateErr := r.updateReconciliationStatusError(ctx, &oauth2client, hydrav1alpha1.StatusInvalidSecret, conflictErr); updateErr != nil {
return ctrl.Result{}, updateErr
}
@ -223,10 +224,6 @@ func (r *OAuth2ClientReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, nil
}
if registerErr := r.registerOAuth2Client(ctx, &oauth2client, credentials); registerErr != nil {
return ctrl.Result{}, registerErr
}
return ctrl.Result{}, nil
}
@ -236,7 +233,7 @@ func (r *OAuth2ClientReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r)
}
func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error {
func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client) error {
if err := r.unregisterOAuth2Clients(ctx, c); err != nil {
return err
}
@ -251,16 +248,8 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
}
return errors.WithStack(err)
}
if credentials != nil {
if _, err := hydraClient.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
}
}
return r.ensureEmptyStatusError(ctx, c)
return fmt.Errorf("failed to construct hydra client for object: %w", err)
}
created, err := hydraClient.PostOAuth2Client(oauth2client)
@ -311,7 +300,8 @@ func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Contex
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
return updateErr
}
return errors.WithStack(err)
return fmt.Errorf("failed to construct hydra client for object: %w", err)
}
if _, err := hydraClient.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
@ -353,50 +343,58 @@ func (r *OAuth2ClientReconciler) unregisterOAuth2Clients(ctx context.Context, c
func (r *OAuth2ClientReconciler) updateReconciliationStatusError(ctx context.Context, c *hydrav1alpha1.OAuth2Client, code hydrav1alpha1.StatusCode, err error) error {
r.Log.Error(err, fmt.Sprintf("error processing client %s/%s ", c.Name, c.Namespace), "oauth2client", "register")
c.Status.ReconciliationError = hydrav1alpha1.ReconciliationError{
Code: code,
Description: err.Error(),
}
c.Status.Conditions = []hydrav1alpha1.OAuth2ClientCondition{
{
Type: hydrav1alpha1.OAuth2ClientConditionReady,
Status: hydrav1alpha1.ConditionFalse,
},
_, err = controllerutil.CreateOrPatch(ctx, r.Client, c, func() error {
c.Status.ObservedGeneration = c.Generation
c.Status.ReconciliationError = hydrav1alpha1.ReconciliationError{
Code: code,
Description: err.Error(),
}
c.Status.Conditions = []hydrav1alpha1.OAuth2ClientCondition{
{
Type: hydrav1alpha1.OAuth2ClientConditionReady,
Status: hydrav1alpha1.ConditionFalse,
},
}
return nil
})
if err != nil {
r.Log.Error(err, fmt.Sprintf("status update failed for client %s/%s ", c.Name, c.Namespace), "oauth2client", "update status")
}
return r.updateClientStatus(ctx, c)
return err
}
func (r *OAuth2ClientReconciler) ensureEmptyStatusError(ctx context.Context, c *hydrav1alpha1.OAuth2Client) error {
c.Status.ReconciliationError = hydrav1alpha1.ReconciliationError{}
c.Status.Conditions = []hydrav1alpha1.OAuth2ClientCondition{
{
Type: hydrav1alpha1.OAuth2ClientConditionReady,
Status: hydrav1alpha1.ConditionTrue,
},
}
return r.updateClientStatus(ctx, c)
}
_, err := controllerutil.CreateOrPatch(ctx, r.Client, c, func() error {
c.Status.ObservedGeneration = c.Generation
c.Status.ReconciliationError = hydrav1alpha1.ReconciliationError{}
c.Status.Conditions = []hydrav1alpha1.OAuth2ClientCondition{
{
Type: hydrav1alpha1.OAuth2ClientConditionReady,
Status: hydrav1alpha1.ConditionTrue,
},
}
func (r *OAuth2ClientReconciler) updateClientStatus(ctx context.Context, c *hydrav1alpha1.OAuth2Client) error {
c.Status.ObservedGeneration = c.Generation
if err := r.Status().Update(ctx, c); err != nil {
return nil
})
if err != nil {
r.Log.Error(err, fmt.Sprintf("status update failed for client %s/%s ", c.Name, c.Namespace), "oauth2client", "update status")
return err
}
return nil
return err
}
func parseSecret(secret apiv1.Secret, authMethod hydrav1alpha1.TokenEndpointAuthMethod) (*hydra.Oauth2ClientCredentials, error) {
id, found := secret.Data[ClientIDKey]
if !found {
return nil, errors.New(`"client_id property missing"`)
return nil, fmt.Errorf("client_id property missing")
}
psw, found := secret.Data[ClientSecretKey]
if !found && authMethod != "none" {
return nil, errors.New(`"client_secret property missing"`)
return nil, fmt.Errorf("client_secret property missing")
}
return &hydra.Oauth2ClientCredentials{
@ -423,7 +421,7 @@ func (r *OAuth2ClientReconciler) getHydraClientForClient(
client, err := r.oauth2ClientFactory(spec, "", false)
if err != nil {
return nil, errors.Wrap(err, "cannot create oauth2 client from CRD")
return nil, fmt.Errorf("cannot create oauth2 client from CRD: %w", err)
}
r.oauth2Clients[key] = client
@ -431,9 +429,11 @@ func (r *OAuth2ClientReconciler) getHydraClientForClient(
}
if r.HydraClient == nil {
return nil, errors.New("Not default client or other clients configured")
return nil, fmt.Errorf("no default client configured")
}
r.Log.Info(fmt.Sprintf("using default client"))
r.Log.Info("Using default client")
return r.HydraClient, nil
}

View File

@ -9,16 +9,15 @@ import (
"fmt"
"time"
"k8s.io/utils/pointer"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/stretchr/testify/mock"
apiv1 "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/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
@ -50,7 +49,7 @@ var _ = Describe("OAuth2Client Controller", func() {
tstName, tstClientID, tstSecretName := "test", "testClientID", "my-secret-123"
expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}}
s := scheme.Scheme
s := runtime.NewScheme()
err := hydrav1alpha1.AddToScheme(s)
Expect(err).NotTo(HaveOccurred())
@ -70,7 +69,7 @@ var _ = Describe("OAuth2Client Controller", func() {
mch.On("PostOAuth2Client", AnythingOfType("*hydra.OAuth2ClientJSON")).Return(func(o *hydra.OAuth2ClientJSON) *hydra.OAuth2ClientJSON {
return &hydra.OAuth2ClientJSON{
ClientID: &tstClientID,
Secret: pointer.StringPtr(tstSecret),
Secret: ptr.To(tstSecret),
GrantTypes: o.GrantTypes,
ResponseTypes: o.ResponseTypes,
RedirectURIs: o.RedirectURIs,
@ -129,7 +128,7 @@ var _ = Describe("OAuth2Client Controller", func() {
tstName, tstSecretName := "test2", "my-secret-456"
expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}}
s := scheme.Scheme
s := runtime.NewScheme()
err := hydrav1alpha1.AddToScheme(s)
Expect(err).NotTo(HaveOccurred())
@ -196,7 +195,7 @@ var _ = Describe("OAuth2Client Controller", func() {
var postedClient *hydra.OAuth2ClientJSON
expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}}
s := scheme.Scheme
s := runtime.NewScheme()
err := hydrav1alpha1.AddToScheme(s)
Expect(err).NotTo(HaveOccurred())
@ -270,9 +269,6 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(retrieved.Status.ReconciliationError.Code).To(BeEmpty())
Expect(retrieved.Status.ReconciliationError.Description).To(BeEmpty())
Expect(*postedClient.ClientID).To(Equal(tstClientID))
Expect(*postedClient.Secret).To(Equal(tstSecret))
// Ensure that secret doesn't have OwnerReference set
ok = client.ObjectKey{Name: tstSecretName, Namespace: tstNamespace}
err = k8sClient.Get(context.TODO(), ok, &secret)
@ -291,7 +287,7 @@ var _ = Describe("OAuth2Client Controller", func() {
tstName, tstClientID, tstSecretName := "test4", "testClientID-4", "my-secret-000"
expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}}
s := scheme.Scheme
s := runtime.NewScheme()
err := hydrav1alpha1.AddToScheme(s)
Expect(err).NotTo(HaveOccurred())
@ -348,7 +344,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(err).NotTo(HaveOccurred())
Expect(retrieved.Status.ReconciliationError).NotTo(BeNil())
Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusInvalidSecret))
Expect(retrieved.Status.ReconciliationError.Description).To(Equal(`"client_secret property missing"`))
Expect(retrieved.Status.ReconciliationError.Description).To(Equal("client_secret property missing"))
//delete instance
c.Delete(context.TODO(), instance)
@ -361,7 +357,7 @@ var _ = Describe("OAuth2Client Controller", func() {
tstName, tstClientID, tstSecretName := "test5", "testClientID-5", "my-secret-without-client-secret"
expectedRequest := &reconcile.Request{NamespacedName: types.NamespacedName{Name: tstName, Namespace: tstNamespace}}
s := scheme.Scheme
s := runtime.NewScheme()
err := hydrav1alpha1.AddToScheme(s)
Expect(err).NotTo(HaveOccurred())
@ -457,7 +453,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
}
// Watch for changes to Api
err = c.Watch(&source.Kind{Type: &hydrav1alpha1.OAuth2Client{}}, &handler.EnqueueRequestForObject{})
err = c.Watch(source.Kind(mgr.GetCache(), &hydrav1alpha1.OAuth2Client{}), &handler.EnqueueRequestForObject{})
if err != nil {
return err
}

View File

@ -13,6 +13,7 @@ import (
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
@ -61,8 +62,15 @@ var _ = BeforeSuite(func(done Done) {
var _ = AfterSuite(func() {
By("tearing down the test environment")
err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred())
// Need to retry a bit if the first stop fails due to a bug:
// https://github.com/kubernetes-sigs/controller-runtime/issues/1571
err := retry.OnError(retry.DefaultBackoff, func(err error) bool {
return true
}, func() error {
return testEnv.Stop()
})
Expect(err).NotTo(HaveOccurred())
})
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and