fix(controller): Ensure that OAuth2Client reconciliation creates hydra client for specs (#83)

This commit is contained in:
Alexandre Mclean
2021-09-14 08:07:06 -04:00
committed by GitHub
parent 273922d2e2
commit 028c3df4c2
8 changed files with 242 additions and 156 deletions

View File

@ -2,16 +2,18 @@
package mocks
import hydra "github.com/ory/hydra-maester/hydra"
import mock "github.com/stretchr/testify/mock"
import (
hydra "github.com/ory/hydra-maester/hydra"
mock "github.com/stretchr/testify/mock"
)
// HydraClientInterface is an autogenerated mock type for the HydraClientInterface type
type HydraClientInterface struct {
// Client is an autogenerated mock type for the Client type
type Client struct {
mock.Mock
}
// DeleteOAuth2Client provides a mock function with given fields: id
func (_m *HydraClientInterface) DeleteOAuth2Client(id string) error {
func (_m *Client) DeleteOAuth2Client(id string) error {
ret := _m.Called(id)
var r0 error
@ -25,7 +27,7 @@ func (_m *HydraClientInterface) DeleteOAuth2Client(id string) error {
}
// GetOAuth2Client provides a mock function with given fields: id
func (_m *HydraClientInterface) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) {
func (_m *Client) GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error) {
ret := _m.Called(id)
var r0 *hydra.OAuth2ClientJSON
@ -55,7 +57,7 @@ func (_m *HydraClientInterface) GetOAuth2Client(id string) (*hydra.OAuth2ClientJ
}
// ListOAuth2Client provides a mock function with given fields:
func (_m *HydraClientInterface) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) {
func (_m *Client) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error) {
ret := _m.Called()
var r0 []*hydra.OAuth2ClientJSON
@ -78,7 +80,7 @@ func (_m *HydraClientInterface) ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, e
}
// PostOAuth2Client provides a mock function with given fields: o
func (_m *HydraClientInterface) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
func (_m *Client) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
ret := _m.Called(o)
var r0 *hydra.OAuth2ClientJSON
@ -101,7 +103,7 @@ func (_m *HydraClientInterface) PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hy
}
// PutOAuth2Client provides a mock function with given fields: o
func (_m *HydraClientInterface) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
func (_m *Client) PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error) {
ret := _m.Called(o)
var r0 *hydra.OAuth2ClientJSON

View File

@ -18,9 +18,9 @@ package controllers
import (
"context"
"fmt"
"sync"
"github.com/go-logr/logr"
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
"github.com/ory/hydra-maester/hydra"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
@ -28,36 +28,89 @@ import (
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
"github.com/ory/hydra-maester/hydra"
)
const (
ClientIDKey = "client_id"
ClientSecretKey = "client_secret"
FinalizerName = "finalizer.ory.hydra.sh"
DefaultNamespace = "default"
)
type clientMapKey struct {
type clientKey struct {
url string
port int
endpoint string
forwardedProto string
}
type HydraClientInterface interface {
GetOAuth2Client(id string) (*hydra.OAuth2ClientJSON, bool, error)
ListOAuth2Client() ([]*hydra.OAuth2ClientJSON, error)
PostOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error)
PutOAuth2Client(o *hydra.OAuth2ClientJSON) (*hydra.OAuth2ClientJSON, error)
DeleteOAuth2Client(id string) error
// OAuth2ClientFactory is a function that creates oauth2 client.
// The OAuth2ClientReconciler defaults to use hydra.New and the factory allows
// to override this behavior for mocks during tests.
type OAuth2ClientFactory func(
spec hydrav1alpha1.OAuth2ClientSpec,
tlsTrustStore string,
insecureSkipVerify bool,
) (hydra.Client, error)
// OAuth2ClientReconciler reconciles a OAuth2Client object.
type OAuth2ClientReconciler struct {
client.Client
HydraClient hydra.Client
Log logr.Logger
ControllerNamespace string
oauth2Clients map[clientKey]hydra.Client
oauth2ClientFactory OAuth2ClientFactory
mu sync.Mutex
}
// OAuth2ClientReconciler reconciles a OAuth2Client object
type OAuth2ClientReconciler struct {
HydraClient HydraClientInterface
Log logr.Logger
otherClients map[clientMapKey]HydraClientInterface
client.Client
ControllerNamespace string
// Options represent options to pass to the oauth2 client reconciler.
type Options struct {
Namespace string
OAuth2ClientFactory OAuth2ClientFactory
}
// Option is a functional option.
type Option func(*Options)
// WithNamespace sets the kubernetes namespace for the controller.
// The default is "default".
func WithNamespace(ns string) Option {
return func(o *Options) {
o.Namespace = ns
}
}
// WithClientFactory sets a function to create new oauth2 clients during the reconciliation logic.
func WithClientFactory(factory OAuth2ClientFactory) Option {
return func(o *Options) {
o.OAuth2ClientFactory = factory
}
}
// New returns a new Oauth2ClientReconciler.
func New(c client.Client, hydraClient hydra.Client, log logr.Logger, opts ...Option) *OAuth2ClientReconciler {
options := &Options{
Namespace: DefaultNamespace,
OAuth2ClientFactory: hydra.New,
}
for _, opt := range opts {
opt(options)
}
return &OAuth2ClientReconciler{
Client: c,
HydraClient: hydraClient,
Log: log,
ControllerNamespace: options.Namespace,
oauth2Clients: make(map[clientKey]hydra.Client, 0),
oauth2ClientFactory: options.OAuth2ClientFactory,
}
}
// +kubebuilder:rbac:groups=hydra.ory.sh,resources=oauth2clients,verbs=get;list;watch;create;update;patch;delete
@ -200,12 +253,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
return err
}
hydra, err := r.getHydraClientForClient(*c)
hydraClient, err := r.getHydraClientForClient(*c)
if err != nil {
return err
}
oauth2client, err := c.ToOAuth2ClientJSON()
oauth2client, err := hydra.FromOAuth2Client(c)
if err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
@ -214,7 +267,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
}
if credentials != nil {
if _, err := hydra.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
if _, err := hydraClient.PostOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
}
@ -222,7 +275,7 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
return r.ensureEmptyStatusError(ctx, c)
}
created, err := hydra.PostOAuth2Client(oauth2client)
created, err := hydraClient.PostOAuth2Client(oauth2client)
if err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusRegistrationFailed, err); updateErr != nil {
return updateErr
@ -260,12 +313,12 @@ func (r *OAuth2ClientReconciler) registerOAuth2Client(ctx context.Context, c *hy
}
func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Context, c *hydrav1alpha1.OAuth2Client, credentials *hydra.Oauth2ClientCredentials) error {
hydra, err := r.getHydraClientForClient(*c)
hydraClient, err := r.getHydraClientForClient(*c)
if err != nil {
return err
}
oauth2client, err := c.ToOAuth2ClientJSON()
oauth2client, err := hydra.FromOAuth2Client(c)
if err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
return updateErr
@ -273,7 +326,7 @@ func (r *OAuth2ClientReconciler) updateRegisteredOAuth2Client(ctx context.Contex
return errors.WithStack(err)
}
if _, err := hydra.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
if _, err := hydraClient.PutOAuth2Client(oauth2client.WithCredentials(credentials)); err != nil {
if updateErr := r.updateReconciliationStatusError(ctx, c, hydrav1alpha1.StatusUpdateFailed, err); updateErr != nil {
return updateErr
}
@ -352,17 +405,31 @@ func parseSecret(secret apiv1.Secret, authMethod hydrav1alpha1.TokenEndpointAuth
}, nil
}
func (r *OAuth2ClientReconciler) getHydraClientForClient(oauth2client hydrav1alpha1.OAuth2Client) (HydraClientInterface, error) {
func (r *OAuth2ClientReconciler) getHydraClientForClient(
oauth2client hydrav1alpha1.OAuth2Client) (hydra.Client, error) {
spec := oauth2client.Spec
key := clientMapKey{
url: spec.HydraAdmin.URL,
port: spec.HydraAdmin.Port,
endpoint: spec.HydraAdmin.Endpoint,
forwardedProto: spec.HydraAdmin.ForwardedProto,
}
if c, ok := r.otherClients[key]; ok {
return c, nil
if spec.HydraAdmin.URL != "" {
key := clientKey{
url: spec.HydraAdmin.URL,
port: spec.HydraAdmin.Port,
endpoint: spec.HydraAdmin.Endpoint,
forwardedProto: spec.HydraAdmin.ForwardedProto,
}
r.mu.Lock()
defer r.mu.Unlock()
if c, ok := r.oauth2Clients[key]; ok {
return c, nil
}
client, err := r.oauth2ClientFactory(spec, "", false)
if err != nil {
return nil, errors.Wrap(err, "cannot create oauth2 client from CRD")
}
r.oauth2Clients[key] = client
return client, nil
}
if r.HydraClient == nil {
return nil, errors.New("Not default client or other clients configured")
}

View File

@ -10,10 +10,6 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
"github.com/ory/hydra-maester/controllers"
"github.com/ory/hydra-maester/controllers/mocks"
"github.com/ory/hydra-maester/hydra"
. "github.com/stretchr/testify/mock"
apiv1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -27,6 +23,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1"
"github.com/ory/hydra-maester/controllers"
mocks "github.com/ory/hydra-maester/controllers/mocks/hydra"
"github.com/ory/hydra-maester/hydra"
)
const (
@ -59,7 +60,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(err).NotTo(HaveOccurred())
c := mgr.GetClient()
mch := &mocks.HydraClientInterface{}
mch := &mocks.Client{}
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
mch.On("DeleteOAuth2Client", Anything).Return(nil)
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
@ -138,7 +139,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(err).NotTo(HaveOccurred())
c := mgr.GetClient()
mch := &mocks.HydraClientInterface{}
mch := &mocks.Client{}
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
mch.On("PostOAuth2Client", Anything).Return(nil, errors.New("error"))
mch.On("DeleteOAuth2Client", Anything).Return(nil)
@ -168,6 +169,7 @@ var _ = Describe("OAuth2Client Controller", func() {
err = c.Get(context.TODO(), ok, &retrieved)
Expect(err).NotTo(HaveOccurred())
Expect(retrieved.Status.ReconciliationError).NotTo(BeNil())
Expect(retrieved.Status.ReconciliationError.Code).To(Equal(hydrav1alpha1.StatusRegistrationFailed))
Expect(retrieved.Status.ReconciliationError.Description).To(Equal("error"))
@ -204,7 +206,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(err).NotTo(HaveOccurred())
c := mgr.GetClient()
mch := mocks.HydraClientInterface{}
mch := mocks.Client{}
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
mch.On("DeleteOAuth2Client", Anything).Return(nil)
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
@ -299,7 +301,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(err).NotTo(HaveOccurred())
c := mgr.GetClient()
mch := mocks.HydraClientInterface{}
mch := mocks.Client{}
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
mch.On("DeleteOAuth2Client", Anything).Return(nil)
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
@ -369,7 +371,7 @@ var _ = Describe("OAuth2Client Controller", func() {
Expect(err).NotTo(HaveOccurred())
c := mgr.GetClient()
mch := &mocks.HydraClientInterface{}
mch := &mocks.Client{}
mch.On("GetOAuth2Client", Anything).Return(nil, false, nil)
mch.On("DeleteOAuth2Client", Anything).Return(nil)
mch.On("ListOAuth2Client", Anything).Return(nil, nil)
@ -460,12 +462,17 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
return nil
}
func getAPIReconciler(mgr ctrl.Manager, mock controllers.HydraClientInterface) reconcile.Reconciler {
return &controllers.OAuth2ClientReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
HydraClient: mock,
func getAPIReconciler(mgr ctrl.Manager, mock hydra.Client) reconcile.Reconciler {
clientMocker := func(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkipVerify bool) (hydra.Client, error) {
return mock, nil
}
return controllers.New(
mgr.GetClient(),
mock,
ctrl.Log.WithName("controllers").WithName("OAuth2Client"),
controllers.WithClientFactory(clientMocker),
)
}
func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client {