From 547ba4eae8c4d5e4de5d411de25f37a33ad51775 Mon Sep 17 00:00:00 2001 From: Dipak Pawar Date: Mon, 17 Dec 2018 17:24:39 +0530 Subject: [PATCH 1/2] feat: call cluster service to create identity cluster relation while creating user --- application/service/services.go | 2 + cluster/cache.go | 40 +---- cluster/cluster.go | 59 +++++++ cluster/service/cluster.go | 70 ++++++++- cluster/service/cluster_blackbox_test.go | 146 +++++++++++++++--- controller/users.go | 14 +- controller/users_blackbox_test.go | 32 ++-- test/clusterservice.go | 6 + .../cluster/link_identity_to_cluster.yaml | 62 ++++++++ .../cluster/unlink_identity_to_cluster.yaml | 77 +++++++++ test/recorder/cluster.go | 96 ++++++++++++ test/recorder/notification.go | 64 ++++++++ test/recorder/recorder.go | 77 ++++----- 13 files changed, 625 insertions(+), 120 deletions(-) create mode 100644 test/data/cluster/link_identity_to_cluster.yaml create mode 100644 test/data/cluster/unlink_identity_to_cluster.yaml create mode 100644 test/recorder/cluster.go create mode 100644 test/recorder/notification.go diff --git a/application/service/services.go b/application/service/services.go index a54e3979..a38486bf 100644 --- a/application/service/services.go +++ b/application/service/services.go @@ -62,6 +62,8 @@ type ClusterService interface { Clusters(ctx context.Context, options ...rest.HTTPClientOption) ([]cluster.Cluster, error) ClusterByURL(ctx context.Context, url string, options ...rest.HTTPClientOption) (*cluster.Cluster, error) Status(ctx context.Context, options ...rest.HTTPClientOption) (bool, error) + RemoveIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error + AddIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error Stop() } diff --git a/cluster/cache.go b/cluster/cache.go index fe409a47..5473d631 100644 --- a/cluster/cache.go +++ b/cluster/cache.go @@ -3,7 +3,6 @@ package cluster import ( "context" "net/http" - "net/url" "sync" "time" @@ -13,7 +12,6 @@ import ( "github.com/fabric8-services/fabric8-auth/rest" "github.com/fabric8-services/fabric8-cluster-client/cluster" - goaclient "github.com/goadesign/goa/client" "github.com/pkg/errors" ) @@ -100,7 +98,8 @@ func (c *cache) refreshCache(ctx context.Context) error { // fetchClusters fetches a new list of clusters from Cluster Management Service func (c *cache) fetchClusters(ctx context.Context) (map[string]Cluster, error) { - cln, err := c.createClientWithServiceAccountSigner(ctx) + signer := NewJWTSASigner(ctx, c.config, c.options...) + cln, err := signer.CreateSignedClient() if err != nil { return nil, err } @@ -148,38 +147,3 @@ func (c *cache) fetchClusters(ctx context.Context) (map[string]Cluster, error) { } return clusterMap, nil } - -// createClientWithSASigner creates a client with a JWT signer which uses the Auth Service Account token -func (c *cache) createClientWithServiceAccountSigner(ctx context.Context) (*cluster.Client, error) { - cln, err := c.createClient(ctx) - if err != nil { - return nil, err - } - m, err := manager.DefaultManager(c.config) - if err != nil { - return nil, err - } - signer := m.AuthServiceAccountSigner() - cln.SetJWTSigner(signer) - return cln, nil -} - -func (c *cache) createClient(ctx context.Context) (*cluster.Client, error) { - u, err := url.Parse(c.config.GetClusterServiceURL()) - if err != nil { - return nil, err - } - - httpClient := http.DefaultClient - - if c.options != nil { - for _, opt := range c.options { - opt(httpClient) - } - } - cln := cluster.New(goaclient.HTTPClientDoer(httpClient)) - - cln.Host = u.Host - cln.Scheme = u.Scheme - return cln, nil -} diff --git a/cluster/cluster.go b/cluster/cluster.go index e8fa25c6..37b2ffc0 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -1,5 +1,15 @@ package cluster +import ( + "context" + "github.com/fabric8-services/fabric8-auth/authorization/token/manager" + "github.com/fabric8-services/fabric8-auth/rest" + "github.com/fabric8-services/fabric8-cluster-client/cluster" + goaclient "github.com/goadesign/goa/client" + "net/http" + "net/url" +) + // Cluster represents an OpenShift cluster configuration type Cluster struct { Name string `mapstructure:"name"` @@ -16,3 +26,52 @@ type Cluster struct { AuthClientDefaultScope string `mapstructure:"auth-client-default-scope"` CapacityExhausted bool `mapstructure:"capacity-exhausted"` // Optional in oso-clusters.conf ('false' by default) } + +type SASigner interface { + CreateSignedClient() (*cluster.Client, error) +} + +type JWTSASigner struct { + ctx context.Context + config clusterConfig + options []rest.HTTPClientOption +} + +func NewJWTSASigner(ctx context.Context, config clusterConfig, options ...rest.HTTPClientOption) SASigner { + return &JWTSASigner{ctx, config, options} +} + +// CreateSignedClient creates a client with a JWT signer which uses the Auth Service Account token +func (c JWTSASigner) CreateSignedClient() (*cluster.Client, error) { + cln, err := c.createClient(c.ctx) + if err != nil { + return nil, err + } + m, err := manager.DefaultManager(c.config) + if err != nil { + return nil, err + } + signer := m.AuthServiceAccountSigner() + cln.SetJWTSigner(signer) + return cln, nil +} + +func (c JWTSASigner) createClient(ctx context.Context) (*cluster.Client, error) { + u, err := url.Parse(c.config.GetClusterServiceURL()) + if err != nil { + return nil, err + } + + httpClient := http.DefaultClient + + if c.options != nil { + for _, opt := range c.options { + opt(httpClient) + } + } + cln := cluster.New(goaclient.HTTPClientDoer(httpClient)) + + cln.Host = u.Host + cln.Scheme = u.Scheme + return cln, nil +} diff --git a/cluster/service/cluster.go b/cluster/service/cluster.go index 38b1b460..359c0cf3 100644 --- a/cluster/service/cluster.go +++ b/cluster/service/cluster.go @@ -13,7 +13,13 @@ import ( "github.com/fabric8-services/fabric8-auth/application/service/base" servicecontext "github.com/fabric8-services/fabric8-auth/application/service/context" "github.com/fabric8-services/fabric8-auth/cluster" + "github.com/fabric8-services/fabric8-auth/goasupport" + "github.com/fabric8-services/fabric8-auth/log" "github.com/fabric8-services/fabric8-auth/rest" + clusterclient "github.com/fabric8-services/fabric8-cluster-client/cluster" + "github.com/pkg/errors" + "github.com/satori/go.uuid" + "net/http" ) type clusterServiceConfig interface { @@ -77,6 +83,62 @@ func (s *clusterService) Stop() { } } +func (s *clusterService) AddIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error { + signer := cluster.NewJWTSASigner(ctx, s.config, options...) + remoteClusterService, err := signer.CreateSignedClient() + if err != nil { + return err + } + identityToClusterData := &clusterclient.LinkIdentityToClusterData{ + ClusterURL: clusterURL, + IdentityID: identityID.String(), + } + res, err := remoteClusterService.LinkIdentityToClusterClusters(goasupport.ForwardContextRequestID(ctx), clusterclient.LinkIdentityToClusterClustersPath(), identityToClusterData) + if err != nil { + return err + } + defer rest.CloseResponse(res) + bodyString := rest.ReadBody(res.Body) // To prevent FDs leaks + if res.StatusCode != http.StatusNoContent { + log.Error(ctx, map[string]interface{}{ + "identity_id": identityID, + "cluster_url": clusterURL, + "response_status": res.Status, + "response_body": bodyString, + }, "unable to link identity to cluster in cluster management service") + return errors.Errorf("failed to link identity to cluster in cluster management service. Response status: %s. Response body: %s", res.Status, bodyString) + } + return nil +} + +func (s *clusterService) RemoveIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error { + signer := cluster.NewJWTSASigner(ctx, s.config, options...) + remoteClusterService, err := signer.CreateSignedClient() + if err != nil { + return err + } + identityToClusterData := &clusterclient.UnLinkIdentityToClusterdata{ + ClusterURL: clusterURL, + IdentityID: identityID.String(), + } + res, err := remoteClusterService.RemoveIdentityToClusterLinkClusters(goasupport.ForwardContextRequestID(ctx), clusterclient.RemoveIdentityToClusterLinkClustersPath(), identityToClusterData) + if err != nil { + return err + } + defer rest.CloseResponse(res) + bodyString := rest.ReadBody(res.Body) // To prevent FDs leaks + if res.StatusCode != http.StatusNoContent { + log.Error(ctx, map[string]interface{}{ + "identity_id": identityID, + "cluster_url": clusterURL, + "response_status": res.Status, + "response_body": bodyString, + }, "unable to remove identity cluster relationship in cluster management service") + return errors.Errorf("failed to unlink identity to cluster in cluster management service. Response status: %s. Response body: %s", res.Status, bodyString) + } + return nil +} + // Start initializes the default Cluster cache if it's not initialized already // Cache initialization loads the list of clusters from the cluster management service and starts regular cache refresher func Start(ctx context.Context, factory service.ClusterCacheFactory, options ...rest.HTTPClientOption) (bool, error) { @@ -93,10 +155,10 @@ func Start(ctx context.Context, factory service.ClusterCacheFactory, options ... } else { clusterCache = nil } - return (clusterCache != nil && started == uint32(1)), err + return clusterCache != nil && started == uint32(1), err } } - return (clusterCache != nil && started == uint32(1)), nil + return clusterCache != nil && started == uint32(1), nil } // Clusters converts the given cluster map to an array slice @@ -109,9 +171,9 @@ func Clusters(clusters map[string]cluster.Cluster) []cluster.Cluster { } func ClusterByURL(clusters map[string]cluster.Cluster, url string) *cluster.Cluster { - for apiURL, cluster := range clusters { + for apiURL, c := range clusters { if strings.HasPrefix(rest.AddTrailingSlashToURL(url), apiURL) { - return &cluster + return &c } } diff --git a/cluster/service/cluster_blackbox_test.go b/cluster/service/cluster_blackbox_test.go index e4a1174a..d0f0106c 100644 --- a/cluster/service/cluster_blackbox_test.go +++ b/cluster/service/cluster_blackbox_test.go @@ -16,6 +16,7 @@ import ( "github.com/dnaeon/go-vcr/cassette" vcrec "github.com/dnaeon/go-vcr/recorder" + "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -48,23 +49,140 @@ func (s *ClusterServiceTestSuite) TestClustersFail() { ctx, _, reqID := tokentestsupport.ContextWithTokenAndRequestID(s.T()) s.T().Run("clusters() fails if can't get clusters", func(t *testing.T) { - r, err := recorder.New("../../test/data/cluster/cluster_get_error", recorder.WithMatcher(clusterRequestMatcher(t, reqID, s.saToken))) + r, err := recorder.New("../../test/data/cluster/cluster_get_error", recorder.WithDefaultMatcher(reqID, s.saToken)) require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() _, err = s.Application.ClusterService().Clusters(ctx, rest.WithRoundTripper(r.Transport)) - require.EqualError(t, err, "unable to get clusters from Cluster Management Service. Response status: 500 Internal Server Error. Response body: oopsy woopsy", err.Error()) + require.EqualError(t, err, "unable to get clusters from Cluster Management Service. Response status: 500 Internal Server Error. Response body: oopsy woopsy") }) s.T().Run("clusters() fails if can't get clusters", func(t *testing.T) { - r, err := recorder.New("../../test/data/cluster/cluster_get_error", recorder.WithMatcher(clusterRequestMatcher(t, reqID, s.saToken))) + r, err := recorder.New("../../test/data/cluster/cluster_get_error", recorder.WithDefaultMatcher(reqID, s.saToken)) require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() _, err = s.Application.ClusterService().ClusterByURL(ctx, "https://api.starter-us-east-2.openshift.com/") - assert.EqualError(t, err, "unable to get clusters from Cluster Management Service. Response status: 500 Internal Server Error. Response body: oopsy woopsy", err.Error()) + assert.EqualError(t, err, "unable to get clusters from Cluster Management Service. Response status: 500 Internal Server Error. Response body: oopsy woopsy") + }) +} + +func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkOK() { + ctx, _, reqID := tokentestsupport.ContextWithTokenAndRequestID(s.T()) + identityID := createIdentityIDFromString(s.T()) + + s.T().Run("200", func(t *testing.T) { + clusterURL := "https://cluster.ok/" + r, err := recorder.New("../../test/data/cluster/unlink_identity_to_cluster", recorder.WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + assert.NoError(t, err) }) } +func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkFail() { + ctx, _, reqID := tokentestsupport.ContextWithTokenAndRequestID(s.T()) + identityID := createIdentityIDFromString(s.T()) + + s.T().Run("500", func(t *testing.T) { + clusterURL := "https://cluster.error/" + r, err := recorder.New("../../test/data/cluster/unlink_identity_to_cluster", recorder.WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 500 Internal Server Error. Response body: oopsy woopsy") + }) + + s.T().Run("400", func(t *testing.T) { + clusterURL := "https://cluster.bad/" + r, err := recorder.New("../../test/data/cluster/unlink_identity_to_cluster", recorder.WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 400 Bad Request. Response body: invalid identity-id") + }) + + s.T().Run("401", func(t *testing.T) { + clusterURL := "https://cluster.unauthorized/" + r, err := recorder.New("../../test/data/cluster/unlink_identity_to_cluster", recorder.WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 401 Unauthorized. Response body: unauthorized") + }) + + s.T().Run("404", func(t *testing.T) { + clusterURL := "https://cluster.notfound/" + r, err := recorder.New("../../test/data/cluster/unlink_identity_to_cluster", recorder.WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 404 Not Found. Response body: not found") + }) +} + +func (s *ClusterServiceTestSuite) TestAddIdentityToClusterLinkOK() { + ctx, _, reqID := tokentestsupport.ContextWithTokenAndRequestID(s.T()) + identityID := createIdentityIDFromString(s.T()) + + s.T().Run("200", func(t *testing.T) { + clusterURL := "https://cluster.ok/" + r, err := recorder.New("../../test/data/cluster/link_identity_to_cluster", recorder.WithLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + assert.NoError(t, err) + }) +} + +func (s *ClusterServiceTestSuite) TestAddIdentityToClusterLinkFail() { + ctx, _, reqID := tokentestsupport.ContextWithTokenAndRequestID(s.T()) + identityID := createIdentityIDFromString(s.T()) + + s.T().Run("500", func(t *testing.T) { + clusterURL := "https://cluster.error/" + r, err := recorder.New("../../test/data/cluster/link_identity_to_cluster", recorder.WithLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to link identity to cluster in cluster management service. Response status: 500 Internal Server Error. Response body: oopsy woopsy") + }) + + s.T().Run("400", func(t *testing.T) { + clusterURL := "https://cluster.bad/" + r, err := recorder.New("../../test/data/cluster/link_identity_to_cluster", recorder.WithLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to link identity to cluster in cluster management service. Response status: 400 Bad Request. Response body: invalid identity-id") + }) + + s.T().Run("401", func(t *testing.T) { + clusterURL := "https://cluster.unauthorized/" + r, err := recorder.New("../../test/data/cluster/link_identity_to_cluster", recorder.WithLinkIdentityToClusterRequestPayloadMatcher(clusterURL, reqID, s.saToken)) + require.NoError(t, err) + defer func() { require.NoError(t, stopRecorder(r)) }() + + err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + require.EqualError(t, err, "failed to link identity to cluster in cluster management service. Response status: 401 Unauthorized. Response body: unauthorized") + }) +} + +func createIdentityIDFromString(t *testing.T) uuid.UUID { + identityID, err := uuid.FromString("715eab6a-9818-4642-8002-1d0cf8939007") + require.NoError(t, err) + + return identityID +} + type dummyFactory struct { config factory.ClusterCacheFactoryConfiguration option rest.HTTPClientOption @@ -78,7 +196,7 @@ func (s *ClusterServiceTestSuite) TestStart() { ctx, _, reqID := tokentestsupport.ContextWithTokenAndRequestID(s.T()) s.T().Run("start fails if can't get clusters", func(t *testing.T) { - r, err := recorder.New("../../test/data/cluster/cluster_get_error", recorder.WithMatcher(clusterRequestMatcher(t, reqID, s.saToken))) + r, err := recorder.New("../../test/data/cluster/cluster_get_error", recorder.WithDefaultMatcher(reqID, s.saToken)) require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() @@ -94,7 +212,7 @@ func (s *ClusterServiceTestSuite) TestStart() { }) s.T().Run("start OK", func(t *testing.T) { - r, err := recorder.New("../../test/data/cluster/cluster_get_ok", recorder.WithMatcher(clusterRequestMatcher(t, reqID, s.saToken))) + r, err := recorder.New("../../test/data/cluster/cluster_get_ok", recorder.WithDefaultMatcher(reqID, s.saToken)) require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() @@ -155,19 +273,3 @@ func stopRecorder(r *vcrec.Recorder) error { }) return nil } - -func clusterRequestMatcher(t *testing.T, reqID, token string) cassette.Matcher { - return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { - authorization := httpRequest.Header.Get("Authorization") - assert.Equal(t, "Bearer "+token, authorization) - - rID := httpRequest.Header.Get("X-Request-Id") - assert.Equal(t, reqID, rID) - - assert.Equal(t, cassetteRequest.Method, httpRequest.Method) - require.NotNil(t, cassetteRequest.Method, httpRequest.URL) - assert.Equal(t, cassetteRequest.URL, httpRequest.URL.String()) - - return true - } -} diff --git a/controller/users.go b/controller/users.go index 112b5d79..0a074b1d 100644 --- a/controller/users.go +++ b/controller/users.go @@ -155,6 +155,18 @@ func (c *UsersController) Create(ctx *app.CreateUsersContext) error { return jsonapi.JSONErrorResponse(ctx, errors.NewInternalError(ctx, err)) } + // we create a identity cluster relationship in cluster management service. + clusterURL := ctx.Payload.Data.Attributes.Cluster + err = c.app.ClusterService().AddIdentityToClusterLink(ctx.Context, identityID, clusterURL) + if err != nil { + log.Error(ctx, map[string]interface{}{ + "err": err, + "identity_id": identityID, + "cluster_url": clusterURL, + }, "failed to link identity to cluster in cluster service") + // Not a blocker. Log the error and proceed. + } + // finally, if all works, we create a user in WIT too. err = c.app.WITService().CreateUser(ctx.Context, identity, identityID.String()) if err != nil { @@ -169,7 +181,7 @@ func (c *UsersController) Create(ctx *app.CreateUsersContext) error { } func (c *UsersController) checkPreviewUser(email string) (bool, error) { - // Any +preview*@redhat.com email matches + // Any +preview*@redhat.com email matches. Note that this is set only for production. not in prod-preview return regexp.MatchString(c.config.GetIgnoreEmailInProd(), strings.ToLower(email)) } diff --git a/controller/users_blackbox_test.go b/controller/users_blackbox_test.go index c8a7b065..557d79a8 100644 --- a/controller/users_blackbox_test.go +++ b/controller/users_blackbox_test.go @@ -38,19 +38,21 @@ func TestUsersController(t *testing.T) { type UsersControllerTestSuite struct { gormtestsupport.DBTestSuite - svc *goa.Service - controller *UsersController - userRepo accountrepo.UserRepository - identityRepo accountrepo.IdentityRepository - tenantService *dummyTenantService - witService *testservice.WITServiceMock + svc *goa.Service + controller *UsersController + userRepo accountrepo.UserRepository + identityRepo accountrepo.IdentityRepository + tenantService *dummyTenantService + witService *testservice.WITServiceMock + clusterServiceMock *testservice.ClusterServiceMock } func (s *UsersControllerTestSuite) SetupSuite() { s.DBTestSuite.SetupSuite() s.svc = goa.New("test") s.witService = testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") - s.Application = gormapplication.NewGormDB(s.DB, s.Configuration, s.Wrappers, factory.WithWITService(s.witService)) + s.clusterServiceMock = testsupport.NewClusterServiceMock(s.T()) + s.Application = gormapplication.NewGormDB(s.DB, s.Configuration, s.Wrappers, factory.WithWITService(s.witService), factory.WithClusterService(s.clusterServiceMock)) s.controller = NewUsersController(s.svc, s.Application, s.Configuration) s.userRepo = s.Application.Users() s.identityRepo = s.Application.Identities() @@ -1414,6 +1416,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountWithAllFieldsOK approved := false *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") + *s.clusterServiceMock = *testsupport.NewClusterServiceMock(s.T()) secureService, secureController := s.SecuredServiceAccountController(testsupport.TestOnlineRegistrationAppIdentity) @@ -1424,6 +1427,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountWithAllFieldsOK _, appUser := test.CreateUsersOK(s.T(), secureService.Context, secureService, secureController, createUserPayload) assertCreatedUser(s.T(), appUser.Data, user, identity) require.Equal(s.T(), uint64(1), s.witService.CreateUserCounter) + require.Equal(s.T(), uint64(1), s.clusterServiceMock.AddIdentityToClusterLinkCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountForExistingUserInDbFails() { @@ -1513,6 +1517,8 @@ func (s *UsersControllerTestSuite) checkCreateUserAsServiceAccountOK(email strin } *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") + *s.clusterServiceMock = *testsupport.NewClusterServiceMock(s.T()) + secureService, secureController := s.SecuredServiceAccountController(testsupport.TestOnlineRegistrationAppIdentity) createUserPayload := newCreateUsersPayload(&email, nil, nil, nil, nil, nil, &identity.Username, nil, user.ID.String(), &user.Cluster, nil, nil, nil) @@ -1521,6 +1527,7 @@ func (s *UsersControllerTestSuite) checkCreateUserAsServiceAccountOK(email strin _, appUser := test.CreateUsersOK(s.T(), secureService.Context, secureService, secureController, createUserPayload) assertCreatedUser(s.T(), appUser.Data, user, identity) require.Equal(s.T(), uint64(1), s.witService.CreateUserCounter) + require.Equal(s.T(), uint64(1), s.clusterServiceMock.AddIdentityToClusterLinkCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountWithMissingRequiredFieldsFails() { @@ -1549,6 +1556,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountUnauthorized() user := testsupport.TestUser identity := testsupport.TestIdentity *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") + *s.clusterServiceMock = *testsupport.NewClusterServiceMock(s.T()) secureService, secureController := s.SecuredServiceAccountController(testsupport.TestIdentity) @@ -1556,6 +1564,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountUnauthorized() createUserPayload := newCreateUsersPayload(&user.Email, &user.FullName, &user.Bio, &user.ImageURL, &user.URL, &user.Company, &identity.Username, nil, user.ID.String(), &user.Cluster, &identity.RegistrationCompleted, nil, user.ContextInformation) test.CreateUsersUnauthorized(s.T(), secureService.Context, secureService, secureController, createUserPayload) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsNonServiceAccountUnauthorized() { @@ -1563,9 +1572,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsNonServiceAccountUnauthorized user := testsupport.TestUser identity := testsupport.TestIdentity *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") - - //*s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") - *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") + *s.clusterServiceMock = *testsupport.NewClusterServiceMock(s.T()) secureService, secureController := s.SecuredController(testsupport.TestIdentity) @@ -1573,6 +1580,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsNonServiceAccountUnauthorized createUserPayload := newCreateUsersPayload(&user.Email, &user.FullName, &user.Bio, &user.ImageURL, &user.URL, &user.Company, &identity.Username, nil, user.ID.String(), &user.Cluster, &identity.RegistrationCompleted, nil, user.ContextInformation) test.CreateUsersUnauthorized(s.T(), secureService.Context, secureService, secureController, createUserPayload) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountForPreviewUserIgnored() { @@ -1588,6 +1596,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountForPreviewUserI func (s *UsersControllerTestSuite) checkCreateUserAsServiceAccountForPreviewUserIgnored(email string) { *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") + *s.clusterServiceMock = *testsupport.NewClusterServiceMock(s.T()) secureService, secureController := s.SecuredServiceAccountController(testsupport.TestOnlineRegistrationAppIdentity) username := "someuser" @@ -1599,6 +1608,7 @@ func (s *UsersControllerTestSuite) checkCreateUserAsServiceAccountForPreviewUser require.NotNil(s.T(), appUser) assertCreatedUser(s.T(), appUser.Data, accountrepo.User{Cluster: cluster, Email: email}, accountrepo.Identity{Username: username}) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) } func (s *UsersControllerTestSuite) TestCreateUserUnauthorized() { @@ -1606,11 +1616,13 @@ func (s *UsersControllerTestSuite) TestCreateUserUnauthorized() { user := testsupport.TestUser identity := testsupport.TestIdentity *s.witService = *testsupport.NewWITMock(s.T(), uuid.NewV4().String(), "test-space") + *s.clusterServiceMock = *testsupport.NewClusterServiceMock(s.T()) // then createUserPayload := newCreateUsersPayload(&user.Email, &user.FullName, &user.Bio, &user.ImageURL, &user.URL, &user.Company, &identity.Username, nil, user.ID.String(), &user.Cluster, &identity.RegistrationCompleted, nil, user.ContextInformation) test.CreateUsersUnauthorized(s.T(), context.Background(), nil, s.controller, createUserPayload) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) } func newCreateUsersPayload(email, fullName, bio, imageURL, profileURL, company, username, rhdUsername *string, rhdUserID string, cluster *string, registrationCompleted, approved *bool, contextInformation map[string]interface{}) *app.CreateUsersPayload { diff --git a/test/clusterservice.go b/test/clusterservice.go index c82b56ab..8bc8d7a4 100644 --- a/test/clusterservice.go +++ b/test/clusterservice.go @@ -33,6 +33,12 @@ func NewClusterServiceMock(t minimock.Tester) *testservice.ClusterServiceMock { clusterServiceMock.StatusFunc = func(ctx context.Context, options ...rest.HTTPClientOption) (bool, error) { return false, nil } + clusterServiceMock.AddIdentityToClusterLinkFunc = func(p context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) (r error) { + return nil + } + clusterServiceMock.RemoveIdentityToClusterLinkFunc = func(p context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) (r error) { + return nil + } return clusterServiceMock } diff --git a/test/data/cluster/link_identity_to_cluster.yaml b/test/data/cluster/link_identity_to_cluster.yaml new file mode 100644 index 00000000..d1076268 --- /dev/null +++ b/test/data/cluster/link_identity_to_cluster.yaml @@ -0,0 +1,62 @@ +--- +version: 1 +interactions: +- request: + body: | + { + "cluster-url": "https://cluster.ok/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: POST + response: + status: 204 No Content + code: 204 +- request: + body: | + { + "cluster-url": "https://cluster.error/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: POST + response: + body: "oopsy woopsy" + status: 500 Internal Server Error + code: 500 +- request: + body: | + { + "cluster-url": "https://cluster.bad/", + "identity-id": "Quis consequuntur ipsam qui." + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: POST + response: + status: 400 Bad Request + code: 400 + body: "invalid identity-id" +- request: + body: | + { + "cluster-url": "https://cluster.unauthorized/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: POST + response: + body: "unauthorized" + status: 401 Unauthorized + code: 401 diff --git a/test/data/cluster/unlink_identity_to_cluster.yaml b/test/data/cluster/unlink_identity_to_cluster.yaml new file mode 100644 index 00000000..9fa64ec5 --- /dev/null +++ b/test/data/cluster/unlink_identity_to_cluster.yaml @@ -0,0 +1,77 @@ +--- +version: 1 +interactions: +- request: + body: | + { + "cluster-url": "https://cluster.ok/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: DELETE + response: + status: 204 No Content + code: 204 +- request: + body: | + { + "cluster-url": "https://cluster.error/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: DELETE + response: + body: "oopsy woopsy" + status: 500 Internal Server Error + code: 500 +- request: + body: | + { + "cluster-url": "https://cluster.bad/", + "identity-id": "Quis consequuntur ipsam qui." + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: DELETE + response: + status: 400 Bad Request + code: 400 + body: "invalid identity-id" +- request: + body: | + { + "cluster-url": "https://cluster.unauthorized/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: DELETE + response: + body: "unauthorized" + status: 401 Unauthorized + code: 401 +- request: + body: | + { + "cluster-url": "https://cluster.notfound/", + "identity-id": "715eab6a-9818-4642-8002-1d0cf8939007" + } + headers: + Content-Type: + - application/json + url: http://f8cluster/api/clusters/identities + method: DELETE + response: + body: "not found" + status: 404 Not Found + code: 404 diff --git a/test/recorder/cluster.go b/test/recorder/cluster.go new file mode 100644 index 00000000..8b7fd35a --- /dev/null +++ b/test/recorder/cluster.go @@ -0,0 +1,96 @@ +package recorder + +import ( + "encoding/json" + "github.com/dnaeon/go-vcr/cassette" + "github.com/dnaeon/go-vcr/recorder" + "github.com/fabric8-services/fabric8-auth/log" + "github.com/fabric8-services/fabric8-cluster-client/cluster" + "net/http" + "strings" +) + +type RequestParamMatcher interface { + MatchClusterURL(r cassette.Request, url string) bool + RequestMatcher(clusterURL string) cassette.Matcher +} + +type LinkIdentityToClusterMatcher struct { + DefaultRequestMatcher +} + +func (m LinkIdentityToClusterMatcher) MatchClusterURL(r cassette.Request, url string) bool { + payload := cluster.LinkIdentityToClusterData{} + if err := json.NewDecoder(strings.NewReader(r.Body)).Decode(&payload); err != nil { + log.Error(nil, map[string]interface{}{"error": err.Error()}, "Cassette request payload doesn't match with LinkIdentityToClusterData payload") + return false + } + + if payload.ClusterURL != "" { + return payload.ClusterURL == url + } + return false +} + +func (m LinkIdentityToClusterMatcher) RequestMatcher(clusterURL string) cassette.Matcher { + return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + if ok := m.RequestMethodAndURLMatch(httpRequest, cassetteRequest); !ok { + return ok + } + + if m.MatchClusterURL(cassetteRequest, clusterURL) { + return m.HeaderMatch(httpRequest, cassetteRequest) + } + + return false + } +} + +type UnLinkIdentityToClusterMatcher struct { + DefaultRequestMatcher +} + +func (m UnLinkIdentityToClusterMatcher) MatchClusterURL(r cassette.Request, url string) bool { + payload := cluster.UnLinkIdentityToClusterdata{} + if err := json.NewDecoder(strings.NewReader(r.Body)).Decode(&payload); err != nil { + log.Error(nil, map[string]interface{}{"error": err.Error()}, "Cassette request payload doesn't match with UnLinkIdentityToClusterdata payload") + return false + } + + if payload.ClusterURL != "" { + return payload.ClusterURL == url + } + return false +} + +func (m UnLinkIdentityToClusterMatcher) RequestMatcher(clusterURL string) cassette.Matcher { + return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + if ok := m.RequestMethodAndURLMatch(httpRequest, cassetteRequest); !ok { + return ok + } + + if m.MatchClusterURL(cassetteRequest, clusterURL) { + return m.HeaderMatch(httpRequest, cassetteRequest) + } + + return false + } +} + +// WithLinkIdentityToClusterRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder +func WithLinkIdentityToClusterRequestPayloadMatcher(clusterURL, requestID, saToken string) Option { + linkIdentityToClusterMatcher := &LinkIdentityToClusterMatcher{DefaultRequestMatcher{requestID: requestID, saToken: saToken}} + + return func(r *recorder.Recorder) { + r.SetMatcher(linkIdentityToClusterMatcher.RequestMatcher(clusterURL)) + } +} + +// WithLinkIdentityToClusterRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder +func WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, requestID, saToken string) Option { + unLinkIdentityToClusterMatcher := &UnLinkIdentityToClusterMatcher{DefaultRequestMatcher{requestID: requestID, saToken: saToken}} + + return func(r *recorder.Recorder) { + r.SetMatcher(unLinkIdentityToClusterMatcher.RequestMatcher(clusterURL)) + } +} diff --git a/test/recorder/notification.go b/test/recorder/notification.go new file mode 100644 index 00000000..67124f4f --- /dev/null +++ b/test/recorder/notification.go @@ -0,0 +1,64 @@ +package recorder + +import ( + "encoding/json" + "github.com/dnaeon/go-vcr/cassette" + "github.com/dnaeon/go-vcr/recorder" + "github.com/fabric8-services/fabric8-auth/log" + "github.com/fabric8-services/fabric8-auth/notification/client" + goauuid "github.com/goadesign/goa/uuid" + "github.com/satori/go.uuid" + "net/http" + "strings" +) + +// JWTMatcher a cassette matcher that verifies the request method/URL and the subject of the token in the "Authorization" header. +func NotifyRequestPayloadMatcher(messageID *uuid.UUID) cassette.Matcher { + return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + // check the request URI and method + if httpRequest.Method != cassetteRequest.Method || + (httpRequest.URL != nil && httpRequest.URL.String() != cassetteRequest.URL) { + log.Debug(nil, map[string]interface{}{ + "httpRequest_method": httpRequest.Method, + "cassetteRequest_method": cassetteRequest.Method, + "httpRequest_url": httpRequest.URL, + "cassetteRequest_url": cassetteRequest.URL, + }, "Cassette method/url doesn't match with the current request") + return false + } + + payload := client.SendNotifyPayload{} + if err := json.NewDecoder(strings.NewReader(cassetteRequest.Body)).Decode(&payload); err != nil { + log.Error(nil, map[string]interface{}{"error": err.Error()}, "Cassette request payload doesn't match with notification payload") + return false + } + + if messageUUID, e := goauuid.FromString(messageID.String()); e == nil && payload.Data != nil { + return *payload.Data.ID == messageUUID + + } + + return false + } +} + +func NotifyRequestHeaderPayloadMatcher(messageID *uuid.UUID, requestID, saToken string) cassette.Matcher { + return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + + if NotifyRequestPayloadMatcher(messageID)(httpRequest, cassetteRequest) { + authorization := httpRequest.Header.Get("Authorization") + reqID := httpRequest.Header.Get("X-Request-Id") + + return "Bearer "+saToken == authorization && reqID == requestID + } + + return false + } +} + +// WithNotifyRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder +func WithNotifyRequestPayloadMatcher(messageID *uuid.UUID) Option { + return func(r *recorder.Recorder) { + r.SetMatcher(NotifyRequestPayloadMatcher(messageID)) + } +} diff --git a/test/recorder/recorder.go b/test/recorder/recorder.go index eb510808..b3b7dbe5 100644 --- a/test/recorder/recorder.go +++ b/test/recorder/recorder.go @@ -4,16 +4,11 @@ import ( "fmt" "os" - "encoding/json" "github.com/dnaeon/go-vcr/cassette" "github.com/dnaeon/go-vcr/recorder" "github.com/fabric8-services/fabric8-auth/log" - "github.com/fabric8-services/fabric8-auth/notification/client" - uuid2 "github.com/goadesign/goa/uuid" errs "github.com/pkg/errors" - "github.com/satori/go.uuid" "net/http" - "strings" ) // Option an option to customize the recorder to create @@ -26,13 +21,6 @@ func WithMatcher(matcher cassette.Matcher) Option { } } -// WithNotifyRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder -func WithNotifyRequestPayloadMatcher(messageID *uuid.UUID) Option { - return func(r *recorder.Recorder) { - r.SetMatcher(NotifyRequestPayloadMatcher(messageID)) - } -} - // New creates a new recorder func New(cassetteName string, options ...Option) (*recorder.Recorder, error) { _, err := os.Stat(fmt.Sprintf("%s.yaml", cassetteName)) @@ -50,46 +38,45 @@ func New(cassetteName string, options ...Option) (*recorder.Recorder, error) { return r, nil } -// JWTMatcher a cassette matcher that verifies the request method/URL and the subject of the token in the "Authorization" header. -func NotifyRequestPayloadMatcher(messageID *uuid.UUID) cassette.Matcher { - return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { - // check the request URI and method - if httpRequest.Method != cassetteRequest.Method || - (httpRequest.URL != nil && httpRequest.URL.String() != cassetteRequest.URL) { - log.Debug(nil, map[string]interface{}{ - "httpRequest_method": httpRequest.Method, - "cassetteRequest_method": cassetteRequest.Method, - "httpRequest_url": httpRequest.URL, - "cassetteRequest_url": cassetteRequest.URL, - }, "Cassette method/url doesn't match with the current request") - return false - } - - payload := client.SendNotifyPayload{} - if err := json.NewDecoder(strings.NewReader(cassetteRequest.Body)).Decode(&payload); err != nil { - log.Error(nil, map[string]interface{}{"error": err.Error()}, "Cassette request payload doesn't match with notification payload") - return false - } - - if messageUUID, e := uuid2.FromString(messageID.String()); e == nil && payload.Data != nil { - return *payload.Data.ID == messageUUID - - } +type DefaultRequestMatcher struct { + saToken string + requestID string +} +// Default Matcher is used when a custom matcher is not defined +// and compares only the method and URL. +func (r DefaultRequestMatcher) RequestMethodAndURLMatch(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + if httpRequest.Method != cassetteRequest.Method || + (httpRequest.URL != nil && httpRequest.URL.String() != cassetteRequest.URL) { + log.Debug(nil, map[string]interface{}{ + "httpRequest_method": httpRequest.Method, + "cassetteRequest_method": cassetteRequest.Method, + "httpRequest_url": httpRequest.URL, + "cassetteRequest_url": cassetteRequest.URL, + }, "Cassette method/url doesn't match with the current request") return false } + return true } -func NotifyRequestHeaderPayloadMatcher(messageID *uuid.UUID, requestID, saToken string) cassette.Matcher { - return func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { +func (r DefaultRequestMatcher) HeaderMatch(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + authorization := httpRequest.Header.Get("Authorization") + reqID := httpRequest.Header.Get("X-Request-Id") + + return "Bearer "+r.saToken == authorization && reqID == r.requestID +} - if NotifyRequestPayloadMatcher(messageID)(httpRequest, cassetteRequest) { - authorization := httpRequest.Header.Get("Authorization") - reqID := httpRequest.Header.Get("X-Request-Id") +// WithLinkIdentityToClusterRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder +func WithDefaultMatcher(requestID, saToken string) Option { + matcher := &DefaultRequestMatcher{saToken, requestID} - return "Bearer "+saToken == authorization && reqID == requestID - } + return func(r *recorder.Recorder) { + r.SetMatcher(func(httpRequest *http.Request, cassetteRequest cassette.Request) bool { + if ok := matcher.RequestMethodAndURLMatch(httpRequest, cassetteRequest); !ok { + return ok + } - return false + return matcher.HeaderMatch(httpRequest, cassetteRequest) + }) } } From ebaf3d2db191d6da65a092aac22204e4c9f6143d Mon Sep 17 00:00:00 2001 From: Dipak Pawar Date: Thu, 20 Dec 2018 18:24:35 +0530 Subject: [PATCH 2/2] address review comments --- application/service/services.go | 4 +- cluster/cluster.go | 63 +++------------------ cluster/factory/cluster_cache_factory.go | 3 +- cluster/{ => service}/cache.go | 31 ++++------- cluster/service/cluster.go | 71 +++++++++++++++++++++--- cluster/service/cluster_blackbox_test.go | 22 ++++---- controller/users.go | 3 +- controller/users_blackbox_test.go | 12 ++-- test/clusterservice.go | 4 +- test/recorder/cluster.go | 2 +- test/recorder/recorder.go | 7 ++- 11 files changed, 111 insertions(+), 111 deletions(-) rename cluster/{ => service}/cache.go (85%) diff --git a/application/service/services.go b/application/service/services.go index a38486bf..3eb898f7 100644 --- a/application/service/services.go +++ b/application/service/services.go @@ -62,8 +62,8 @@ type ClusterService interface { Clusters(ctx context.Context, options ...rest.HTTPClientOption) ([]cluster.Cluster, error) ClusterByURL(ctx context.Context, url string, options ...rest.HTTPClientOption) (*cluster.Cluster, error) Status(ctx context.Context, options ...rest.HTTPClientOption) (bool, error) - RemoveIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error - AddIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error + UnlinkIdentityFromCluster(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error + LinkIdentityToCluster(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error Stop() } diff --git a/cluster/cluster.go b/cluster/cluster.go index 37b2ffc0..1ee56dcd 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -1,14 +1,6 @@ package cluster -import ( - "context" - "github.com/fabric8-services/fabric8-auth/authorization/token/manager" - "github.com/fabric8-services/fabric8-auth/rest" - "github.com/fabric8-services/fabric8-cluster-client/cluster" - goaclient "github.com/goadesign/goa/client" - "net/http" - "net/url" -) +import "context" // Cluster represents an OpenShift cluster configuration type Cluster struct { @@ -27,51 +19,10 @@ type Cluster struct { CapacityExhausted bool `mapstructure:"capacity-exhausted"` // Optional in oso-clusters.conf ('false' by default) } -type SASigner interface { - CreateSignedClient() (*cluster.Client, error) -} - -type JWTSASigner struct { - ctx context.Context - config clusterConfig - options []rest.HTTPClientOption -} - -func NewJWTSASigner(ctx context.Context, config clusterConfig, options ...rest.HTTPClientOption) SASigner { - return &JWTSASigner{ctx, config, options} -} - -// CreateSignedClient creates a client with a JWT signer which uses the Auth Service Account token -func (c JWTSASigner) CreateSignedClient() (*cluster.Client, error) { - cln, err := c.createClient(c.ctx) - if err != nil { - return nil, err - } - m, err := manager.DefaultManager(c.config) - if err != nil { - return nil, err - } - signer := m.AuthServiceAccountSigner() - cln.SetJWTSigner(signer) - return cln, nil -} - -func (c JWTSASigner) createClient(ctx context.Context) (*cluster.Client, error) { - u, err := url.Parse(c.config.GetClusterServiceURL()) - if err != nil { - return nil, err - } - - httpClient := http.DefaultClient - - if c.options != nil { - for _, opt := range c.options { - opt(httpClient) - } - } - cln := cluster.New(goaclient.HTTPClientDoer(httpClient)) - - cln.Host = u.Host - cln.Scheme = u.Scheme - return cln, nil +type ClusterCache interface { + RLock() + RUnlock() + Clusters() map[string]Cluster + Start(ctx context.Context) error + Stop() } diff --git a/cluster/factory/cluster_cache_factory.go b/cluster/factory/cluster_cache_factory.go index 25fd17de..f9d785dd 100644 --- a/cluster/factory/cluster_cache_factory.go +++ b/cluster/factory/cluster_cache_factory.go @@ -9,6 +9,7 @@ import ( servicecontext "github.com/fabric8-services/fabric8-auth/application/service/context" "github.com/fabric8-services/fabric8-auth/authorization/token/manager" "github.com/fabric8-services/fabric8-auth/cluster" + clusterservice "github.com/fabric8-services/fabric8-auth/cluster/service" "github.com/fabric8-services/fabric8-auth/rest" ) @@ -34,5 +35,5 @@ type clusterCacheFactoryImpl struct { // NewClusterCache creates a new cluster cache func (f *clusterCacheFactoryImpl) NewClusterCache(ctx context.Context, options ...rest.HTTPClientOption) cluster.ClusterCache { - return cluster.NewCache(f.config, options...) + return clusterservice.NewCache(f.config, options...) } diff --git a/cluster/cache.go b/cluster/service/cache.go similarity index 85% rename from cluster/cache.go rename to cluster/service/cache.go index 5473d631..c9514a0c 100644 --- a/cluster/cache.go +++ b/cluster/service/cache.go @@ -1,4 +1,4 @@ -package cluster +package service import ( "context" @@ -10,8 +10,9 @@ import ( "github.com/fabric8-services/fabric8-auth/goasupport" "github.com/fabric8-services/fabric8-auth/log" "github.com/fabric8-services/fabric8-auth/rest" - "github.com/fabric8-services/fabric8-cluster-client/cluster" + clusterclient "github.com/fabric8-services/fabric8-cluster-client/cluster" + "github.com/fabric8-services/fabric8-auth/cluster" "github.com/pkg/errors" ) @@ -21,14 +22,6 @@ type clusterConfig interface { GetClusterCacheRefreshInterval() time.Duration } -type ClusterCache interface { - RLock() - RUnlock() - Clusters() map[string]Cluster - Start(ctx context.Context) error - Stop() -} - type cache struct { sync.RWMutex @@ -36,10 +29,10 @@ type cache struct { options []rest.HTTPClientOption refresher *time.Ticker stopCh chan bool - clusters map[string]Cluster + clusters map[string]cluster.Cluster } -func NewCache(config clusterConfig, options ...rest.HTTPClientOption) ClusterCache { +func NewCache(config clusterConfig, options ...rest.HTTPClientOption) cluster.ClusterCache { return &cache{ config: config, refresher: time.NewTicker(config.GetClusterCacheRefreshInterval()), @@ -79,7 +72,7 @@ func (c *cache) Stop() { } } -func (c *cache) Clusters() map[string]Cluster { +func (c *cache) Clusters() map[string]cluster.Cluster { return c.clusters } @@ -97,14 +90,14 @@ func (c *cache) refreshCache(ctx context.Context) error { } // fetchClusters fetches a new list of clusters from Cluster Management Service -func (c *cache) fetchClusters(ctx context.Context) (map[string]Cluster, error) { - signer := NewJWTSASigner(ctx, c.config, c.options...) - cln, err := signer.CreateSignedClient() +func (c *cache) fetchClusters(ctx context.Context) (map[string]cluster.Cluster, error) { + signer := newJWTSASigner(ctx, c.config, c.options...) + cln, err := signer.createSignedClient() if err != nil { return nil, err } - res, err := cln.ShowAuthClientClusters(goasupport.ForwardContextRequestID(ctx), cluster.ShowAuthClientClustersPath()) + res, err := cln.ShowAuthClientClusters(goasupport.ForwardContextRequestID(ctx), clusterclient.ShowAuthClientClustersPath()) if err != nil { return nil, err } @@ -124,10 +117,10 @@ func (c *cache) fetchClusters(ctx context.Context) (map[string]Cluster, error) { return nil, err } - clusterMap := map[string]Cluster{} + clusterMap := map[string]cluster.Cluster{} if clusters.Data != nil { for _, d := range clusters.Data { - cls := Cluster{ + cls := cluster.Cluster{ Name: d.Name, APIURL: d.APIURL, AppDNS: d.AppDNS, diff --git a/cluster/service/cluster.go b/cluster/service/cluster.go index 359c0cf3..fcf63d5e 100644 --- a/cluster/service/cluster.go +++ b/cluster/service/cluster.go @@ -17,9 +17,11 @@ import ( "github.com/fabric8-services/fabric8-auth/log" "github.com/fabric8-services/fabric8-auth/rest" clusterclient "github.com/fabric8-services/fabric8-cluster-client/cluster" + goaclient "github.com/goadesign/goa/client" "github.com/pkg/errors" "github.com/satori/go.uuid" "net/http" + "net/url" ) type clusterServiceConfig interface { @@ -83,11 +85,12 @@ func (s *clusterService) Stop() { } } -func (s *clusterService) AddIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error { - signer := cluster.NewJWTSASigner(ctx, s.config, options...) - remoteClusterService, err := signer.CreateSignedClient() +// LinkIdentityToCluster links Identity To Cluster using Cluster URL +func (s *clusterService) LinkIdentityToCluster(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error { + signer := newJWTSASigner(ctx, s.config, options...) + remoteClusterService, err := signer.createSignedClient() if err != nil { - return err + return errors.Wrapf(err, "failed to create JWT signer for cluster service") } identityToClusterData := &clusterclient.LinkIdentityToClusterData{ ClusterURL: clusterURL, @@ -95,7 +98,7 @@ func (s *clusterService) AddIdentityToClusterLink(ctx context.Context, identityI } res, err := remoteClusterService.LinkIdentityToClusterClusters(goasupport.ForwardContextRequestID(ctx), clusterclient.LinkIdentityToClusterClustersPath(), identityToClusterData) if err != nil { - return err + return errors.Wrapf(err, "failed to link identity %s to cluster having url %s", identityID, clusterURL) } defer rest.CloseResponse(res) bodyString := rest.ReadBody(res.Body) // To prevent FDs leaks @@ -111,9 +114,10 @@ func (s *clusterService) AddIdentityToClusterLink(ctx context.Context, identityI return nil } -func (s *clusterService) RemoveIdentityToClusterLink(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error { - signer := cluster.NewJWTSASigner(ctx, s.config, options...) - remoteClusterService, err := signer.CreateSignedClient() +// UnlinkIdentityFromCluster removes linked Identity from Cluster using Cluster URL +func (s *clusterService) UnlinkIdentityFromCluster(ctx context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) error { + signer := newJWTSASigner(ctx, s.config, options...) + remoteClusterService, err := signer.createSignedClient() if err != nil { return err } @@ -123,7 +127,7 @@ func (s *clusterService) RemoveIdentityToClusterLink(ctx context.Context, identi } res, err := remoteClusterService.RemoveIdentityToClusterLinkClusters(goasupport.ForwardContextRequestID(ctx), clusterclient.RemoveIdentityToClusterLinkClustersPath(), identityToClusterData) if err != nil { - return err + return errors.Wrapf(err, "failed to unlink identity %s from cluster having url %s", identityID, clusterURL) } defer rest.CloseResponse(res) bodyString := rest.ReadBody(res.Body) // To prevent FDs leaks @@ -179,3 +183,52 @@ func ClusterByURL(clusters map[string]cluster.Cluster, url string) *cluster.Clus return nil } + +type saSigner interface { + createSignedClient() (*clusterclient.Client, error) +} + +type jwtSASigner struct { + ctx context.Context + config clusterConfig + options []rest.HTTPClientOption +} + +func newJWTSASigner(ctx context.Context, config clusterConfig, options ...rest.HTTPClientOption) saSigner { + return &jwtSASigner{ctx, config, options} +} + +// CreateSignedClient creates a client with a JWT signer which uses the Auth Service Account token +func (c jwtSASigner) createSignedClient() (*clusterclient.Client, error) { + cln, err := c.createClient(c.ctx) + if err != nil { + return nil, err + } + m, err := manager.DefaultManager(c.config) + if err != nil { + return nil, err + } + signer := m.AuthServiceAccountSigner() + cln.SetJWTSigner(signer) + return cln, nil +} + +func (c jwtSASigner) createClient(ctx context.Context) (*clusterclient.Client, error) { + u, err := url.Parse(c.config.GetClusterServiceURL()) + if err != nil { + return nil, err + } + + httpClient := http.DefaultClient + + if c.options != nil { + for _, opt := range c.options { + opt(httpClient) + } + } + cln := clusterclient.New(goaclient.HTTPClientDoer(httpClient)) + + cln.Host = u.Host + cln.Scheme = u.Scheme + return cln, nil +} diff --git a/cluster/service/cluster_blackbox_test.go b/cluster/service/cluster_blackbox_test.go index d0f0106c..929ac8e8 100644 --- a/cluster/service/cluster_blackbox_test.go +++ b/cluster/service/cluster_blackbox_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/fabric8-services/fabric8-auth/authorization/token/manager" - "github.com/fabric8-services/fabric8-auth/cluster" "github.com/fabric8-services/fabric8-auth/cluster/factory" clusterservice "github.com/fabric8-services/fabric8-auth/cluster/service" "github.com/fabric8-services/fabric8-auth/gormtestsupport" @@ -16,6 +15,7 @@ import ( "github.com/dnaeon/go-vcr/cassette" vcrec "github.com/dnaeon/go-vcr/recorder" + "github.com/fabric8-services/fabric8-auth/cluster" "github.com/satori/go.uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -76,7 +76,7 @@ func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkOK() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().UnlinkIdentityFromCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) assert.NoError(t, err) }) } @@ -91,7 +91,7 @@ func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().UnlinkIdentityFromCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 500 Internal Server Error. Response body: oopsy woopsy") }) @@ -101,7 +101,7 @@ func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().UnlinkIdentityFromCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 400 Bad Request. Response body: invalid identity-id") }) @@ -111,7 +111,7 @@ func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().UnlinkIdentityFromCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 401 Unauthorized. Response body: unauthorized") }) @@ -121,7 +121,7 @@ func (s *ClusterServiceTestSuite) TestRemoveIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().RemoveIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().UnlinkIdentityFromCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to unlink identity to cluster in cluster management service. Response status: 404 Not Found. Response body: not found") }) } @@ -136,7 +136,7 @@ func (s *ClusterServiceTestSuite) TestAddIdentityToClusterLinkOK() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().LinkIdentityToCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) assert.NoError(t, err) }) } @@ -151,7 +151,7 @@ func (s *ClusterServiceTestSuite) TestAddIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().LinkIdentityToCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to link identity to cluster in cluster management service. Response status: 500 Internal Server Error. Response body: oopsy woopsy") }) @@ -161,7 +161,7 @@ func (s *ClusterServiceTestSuite) TestAddIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().LinkIdentityToCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to link identity to cluster in cluster management service. Response status: 400 Bad Request. Response body: invalid identity-id") }) @@ -171,7 +171,7 @@ func (s *ClusterServiceTestSuite) TestAddIdentityToClusterLinkFail() { require.NoError(t, err) defer func() { require.NoError(t, stopRecorder(r)) }() - err = s.Application.ClusterService().AddIdentityToClusterLink(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) + err = s.Application.ClusterService().LinkIdentityToCluster(ctx, identityID, clusterURL, rest.WithRoundTripper(r.Transport)) require.EqualError(t, err, "failed to link identity to cluster in cluster management service. Response status: 401 Unauthorized. Response body: unauthorized") }) } @@ -189,7 +189,7 @@ type dummyFactory struct { } func (f *dummyFactory) NewClusterCache(ctx context.Context, options ...rest.HTTPClientOption) cluster.ClusterCache { - return cluster.NewCache(f.config, f.option) + return clusterservice.NewCache(f.config, f.option) } func (s *ClusterServiceTestSuite) TestStart() { diff --git a/controller/users.go b/controller/users.go index 0a074b1d..ca40918a 100644 --- a/controller/users.go +++ b/controller/users.go @@ -157,7 +157,7 @@ func (c *UsersController) Create(ctx *app.CreateUsersContext) error { // we create a identity cluster relationship in cluster management service. clusterURL := ctx.Payload.Data.Attributes.Cluster - err = c.app.ClusterService().AddIdentityToClusterLink(ctx.Context, identityID, clusterURL) + err = c.app.ClusterService().LinkIdentityToCluster(ctx.Context, identityID, clusterURL) if err != nil { log.Error(ctx, map[string]interface{}{ "err": err, @@ -165,6 +165,7 @@ func (c *UsersController) Create(ctx *app.CreateUsersContext) error { "cluster_url": clusterURL, }, "failed to link identity to cluster in cluster service") // Not a blocker. Log the error and proceed. + // TODO roll back user creation here (hard delete user&identity from DB) if failed and return error See https://github.com/fabric8-services/fabric8-auth/issues/747 } // finally, if all works, we create a user in WIT too. diff --git a/controller/users_blackbox_test.go b/controller/users_blackbox_test.go index 557d79a8..02e5ad4c 100644 --- a/controller/users_blackbox_test.go +++ b/controller/users_blackbox_test.go @@ -1427,7 +1427,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountWithAllFieldsOK _, appUser := test.CreateUsersOK(s.T(), secureService.Context, secureService, secureController, createUserPayload) assertCreatedUser(s.T(), appUser.Data, user, identity) require.Equal(s.T(), uint64(1), s.witService.CreateUserCounter) - require.Equal(s.T(), uint64(1), s.clusterServiceMock.AddIdentityToClusterLinkCounter) + require.Equal(s.T(), uint64(1), s.clusterServiceMock.LinkIdentityToClusterCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountForExistingUserInDbFails() { @@ -1527,7 +1527,7 @@ func (s *UsersControllerTestSuite) checkCreateUserAsServiceAccountOK(email strin _, appUser := test.CreateUsersOK(s.T(), secureService.Context, secureService, secureController, createUserPayload) assertCreatedUser(s.T(), appUser.Data, user, identity) require.Equal(s.T(), uint64(1), s.witService.CreateUserCounter) - require.Equal(s.T(), uint64(1), s.clusterServiceMock.AddIdentityToClusterLinkCounter) + require.Equal(s.T(), uint64(1), s.clusterServiceMock.LinkIdentityToClusterCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountWithMissingRequiredFieldsFails() { @@ -1564,7 +1564,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountUnauthorized() createUserPayload := newCreateUsersPayload(&user.Email, &user.FullName, &user.Bio, &user.ImageURL, &user.URL, &user.Company, &identity.Username, nil, user.ID.String(), &user.Cluster, &identity.RegistrationCompleted, nil, user.ContextInformation) test.CreateUsersUnauthorized(s.T(), secureService.Context, secureService, secureController, createUserPayload) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) - require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.LinkIdentityToClusterCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsNonServiceAccountUnauthorized() { @@ -1580,7 +1580,7 @@ func (s *UsersControllerTestSuite) TestCreateUserAsNonServiceAccountUnauthorized createUserPayload := newCreateUsersPayload(&user.Email, &user.FullName, &user.Bio, &user.ImageURL, &user.URL, &user.Company, &identity.Username, nil, user.ID.String(), &user.Cluster, &identity.RegistrationCompleted, nil, user.ContextInformation) test.CreateUsersUnauthorized(s.T(), secureService.Context, secureService, secureController, createUserPayload) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) - require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.LinkIdentityToClusterCounter) } func (s *UsersControllerTestSuite) TestCreateUserAsServiceAccountForPreviewUserIgnored() { @@ -1608,7 +1608,7 @@ func (s *UsersControllerTestSuite) checkCreateUserAsServiceAccountForPreviewUser require.NotNil(s.T(), appUser) assertCreatedUser(s.T(), appUser.Data, accountrepo.User{Cluster: cluster, Email: email}, accountrepo.Identity{Username: username}) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) - require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.LinkIdentityToClusterCounter) } func (s *UsersControllerTestSuite) TestCreateUserUnauthorized() { @@ -1622,7 +1622,7 @@ func (s *UsersControllerTestSuite) TestCreateUserUnauthorized() { createUserPayload := newCreateUsersPayload(&user.Email, &user.FullName, &user.Bio, &user.ImageURL, &user.URL, &user.Company, &identity.Username, nil, user.ID.String(), &user.Cluster, &identity.RegistrationCompleted, nil, user.ContextInformation) test.CreateUsersUnauthorized(s.T(), context.Background(), nil, s.controller, createUserPayload) require.Equal(s.T(), uint64(0), s.witService.CreateUserCounter) - require.Equal(s.T(), uint64(0), s.clusterServiceMock.AddIdentityToClusterLinkCounter) + require.Equal(s.T(), uint64(0), s.clusterServiceMock.LinkIdentityToClusterCounter) } func newCreateUsersPayload(email, fullName, bio, imageURL, profileURL, company, username, rhdUsername *string, rhdUserID string, cluster *string, registrationCompleted, approved *bool, contextInformation map[string]interface{}) *app.CreateUsersPayload { diff --git a/test/clusterservice.go b/test/clusterservice.go index 8bc8d7a4..3e42b7fe 100644 --- a/test/clusterservice.go +++ b/test/clusterservice.go @@ -33,10 +33,10 @@ func NewClusterServiceMock(t minimock.Tester) *testservice.ClusterServiceMock { clusterServiceMock.StatusFunc = func(ctx context.Context, options ...rest.HTTPClientOption) (bool, error) { return false, nil } - clusterServiceMock.AddIdentityToClusterLinkFunc = func(p context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) (r error) { + clusterServiceMock.LinkIdentityToClusterFunc = func(p context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) (r error) { return nil } - clusterServiceMock.RemoveIdentityToClusterLinkFunc = func(p context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) (r error) { + clusterServiceMock.UnlinkIdentityFromClusterFunc = func(p context.Context, identityID uuid.UUID, clusterURL string, options ...rest.HTTPClientOption) (r error) { return nil } diff --git a/test/recorder/cluster.go b/test/recorder/cluster.go index 8b7fd35a..22a91f74 100644 --- a/test/recorder/cluster.go +++ b/test/recorder/cluster.go @@ -86,7 +86,7 @@ func WithLinkIdentityToClusterRequestPayloadMatcher(clusterURL, requestID, saTok } } -// WithLinkIdentityToClusterRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder +// WithUnLinkIdentityToClusterRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder func WithUnLinkIdentityToClusterRequestPayloadMatcher(clusterURL, requestID, saToken string) Option { unLinkIdentityToClusterMatcher := &UnLinkIdentityToClusterMatcher{DefaultRequestMatcher{requestID: requestID, saToken: saToken}} diff --git a/test/recorder/recorder.go b/test/recorder/recorder.go index b3b7dbe5..bddb50dd 100644 --- a/test/recorder/recorder.go +++ b/test/recorder/recorder.go @@ -38,13 +38,13 @@ func New(cassetteName string, options ...Option) (*recorder.Recorder, error) { return r, nil } +// DefaultRequestMatcher to compare the request method, URL requestID, Service Account Token. type DefaultRequestMatcher struct { saToken string requestID string } -// Default Matcher is used when a custom matcher is not defined -// and compares only the method and URL. +// RequestMethodAndURLMatch matches request Method and URL func (r DefaultRequestMatcher) RequestMethodAndURLMatch(httpRequest *http.Request, cassetteRequest cassette.Request) bool { if httpRequest.Method != cassetteRequest.Method || (httpRequest.URL != nil && httpRequest.URL.String() != cassetteRequest.URL) { @@ -59,6 +59,7 @@ func (r DefaultRequestMatcher) RequestMethodAndURLMatch(httpRequest *http.Reques return true } +// RequestMethodAndURLMatch matches requestID and Service Account Token func (r DefaultRequestMatcher) HeaderMatch(httpRequest *http.Request, cassetteRequest cassette.Request) bool { authorization := httpRequest.Header.Get("Authorization") reqID := httpRequest.Header.Get("X-Request-Id") @@ -66,7 +67,7 @@ func (r DefaultRequestMatcher) HeaderMatch(httpRequest *http.Request, cassetteRe return "Bearer "+r.saToken == authorization && reqID == r.requestID } -// WithLinkIdentityToClusterRequestPayloadMatcher an option to specify the RequestPayload matcher for the recorder +// WithDefaultMatcher an option to specify the custom matcher with requestID and Service Account Token for the recorder func WithDefaultMatcher(requestID, saToken string) Option { matcher := &DefaultRequestMatcher{saToken, requestID}