fix(controller): Ensure that OAuth2Client reconciliation creates hydra client for specs (#83)
This commit is contained in:
@ -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
|
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user