diff --git a/go.mod b/go.mod index 49f24104a..b18dcbd8e 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/codeready-toolchain/toolchain-e2e require ( - github.com/codeready-toolchain/api v0.0.0-20230711103642-544bb7e0cf9e - github.com/codeready-toolchain/toolchain-common v0.0.0-20230710095440-719b09376de3 + github.com/codeready-toolchain/api v0.0.0-20230823083409-fe9ca973d9a9 + github.com/codeready-toolchain/toolchain-common v0.0.0-20230823084119-693476668406 github.com/davecgh/go-spew v1.1.1 github.com/fatih/color v1.12.0 github.com/ghodss/yaml v1.0.0 diff --git a/go.sum b/go.sum index 60f26ce4e..21123c294 100644 --- a/go.sum +++ b/go.sum @@ -123,10 +123,10 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/codeready-toolchain/api v0.0.0-20230711103642-544bb7e0cf9e h1:T6Ay8YwCGPXCufh1toy7QdCwqPJc5SIA0kkOh/be9ZE= -github.com/codeready-toolchain/api v0.0.0-20230711103642-544bb7e0cf9e/go.mod h1:nn3W6eKb9PFIVwSwZW7wDeLACMBOwAV+4kddGuN+ARM= -github.com/codeready-toolchain/toolchain-common v0.0.0-20230710095440-719b09376de3 h1:zPxv/JJRZsXS+OVgsrF1egFlTi45DXJ8MTPi50meujI= -github.com/codeready-toolchain/toolchain-common v0.0.0-20230710095440-719b09376de3/go.mod h1:vtUfWOJBDxQP1DtcIoxfjI5heBGcT8D+C8ux+PLouyg= +github.com/codeready-toolchain/api v0.0.0-20230823083409-fe9ca973d9a9 h1:ytFqNSSEvgevqvwMilmmqlrrDH1O/qUwzg8bO3CxCiY= +github.com/codeready-toolchain/api v0.0.0-20230823083409-fe9ca973d9a9/go.mod h1:nn3W6eKb9PFIVwSwZW7wDeLACMBOwAV+4kddGuN+ARM= +github.com/codeready-toolchain/toolchain-common v0.0.0-20230823084119-693476668406 h1:AkBhFV2tJhQaejTjntIhFC7r1FeLlQEiAL2gcn5/oW0= +github.com/codeready-toolchain/toolchain-common v0.0.0-20230823084119-693476668406/go.mod h1:wvbndymhFMqyc8syiaanid91GsywkDfuVyiTjdyiqNM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= diff --git a/test/e2e/parallel/spacebindingrequest_test.go b/test/e2e/parallel/spacebindingrequest_test.go new file mode 100644 index 000000000..4d02fc893 --- /dev/null +++ b/test/e2e/parallel/spacebindingrequest_test.go @@ -0,0 +1,288 @@ +package parallel + +import ( + "context" + "fmt" + "sort" + "testing" + + toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" + "github.com/gofrs/uuid" + "github.com/stretchr/testify/assert" + + spacebindingrequesttestcommon "github.com/codeready-toolchain/toolchain-common/pkg/test/spacebindingrequest" + + testspace "github.com/codeready-toolchain/toolchain-common/pkg/test/space" + . "github.com/codeready-toolchain/toolchain-e2e/testsupport" + . "github.com/codeready-toolchain/toolchain-e2e/testsupport/wait" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/types" +) + +func TestCreateSpaceBindingRequest(t *testing.T) { + // given + t.Parallel() + // make sure everything is ready before running the actual tests + awaitilities := WaitForDeployments(t) + hostAwait := awaitilities.Host() + memberAwait := awaitilities.Member1() + + t.Run("success", func(t *testing.T) { + t.Run("create space binding request", func(t *testing.T) { + // when + // we create a space to share , a new MUR and a SpaceBindingRequest + space, spaceBindingRequest, spaceBinding := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin") + + t.Run("spaceBinding is recreated if deleted ", func(t *testing.T) { + // now, delete the SpaceBinding, + // a new SpaceBinding will be provisioned by the SpaceBindingRequest. + // + // save the old UID that will be used to ensure that a new SpaceBinding was created with the same name but new UID + oldUID := spaceBinding.UID + + // when + err := hostAwait.Client.Delete(context.TODO(), spaceBinding) + + // then + // a new SpaceBinding is created + // with the same name but different UID. + require.NoError(t, err) + spaceBinding, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name, + UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord), + UntilSpaceBindingHasSpaceName(space.Name), + UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole), + UntilSpaceBindingHasDifferentUID(oldUID), + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()), + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()), + ) + require.NoError(t, err) + + t.Run("SpaceBinding always reflects values from spaceBindingRequest ", func(t *testing.T) { + // given + // something/someone updates the SpaceRole directly on the SpaceBinding object + + // when + spaceBinding, err = hostAwait.UpdateSpaceBinding(t, spaceBinding.Name, func(s *toolchainv1alpha1.SpaceBinding) { + s.Spec.SpaceRole = "invalidRole" // let's change the role + }) + require.NoError(t, err) + + // then + // spaceBindingRequest should reset back the SpaceRole + spaceBinding, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name, + UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord), + UntilSpaceBindingHasSpaceName(space.Name), + UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole), // should have back the role from the SBR + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()), + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()), + ) + require.NoError(t, err) + + t.Run("delete space binding request", func(t *testing.T) { + // now, delete the SpaceBindingRequest and expect that the SpaceBinding will be deleted as well, + + // when + err := memberAwait.Client.Delete(context.TODO(), spaceBindingRequest) + + // then + // spaceBinding should be deleted as well + require.NoError(t, err) + err = hostAwait.WaitUntilSpaceBindingDeleted(spaceBinding.Name) + require.NoError(t, err) + }) + }) + }) + }) + }) + + t.Run("error", func(t *testing.T) { + t.Run("unable create space binding request with invalid SpaceRole", func(t *testing.T) { + space, _, _ := CreateSpace(t, awaitilities, testspace.WithTierName("appstudio"), testspace.WithSpecTargetCluster(memberAwait.ClusterName)) + // wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it. + space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces()) + require.NoError(t, err) + // let's create a new MUR that will have access to the space + username := uuid.Must(uuid.NewV4()).String() + _, mur := NewSignupRequest(awaitilities). + Username(username). + Email(username + "@acme.com"). + ManuallyApprove(). + TargetCluster(memberAwait). + RequireConditions(ConditionSet(Default(), ApprovedByAdmin())...). + NoSpace(). + WaitForMUR().Execute(t).Resources() + // create the spacebinding request + spaceBindingRequest := CreateSpaceBindingRequest(t, awaitilities, memberAwait.ClusterName, + WithSpecSpaceRole("invalid"), // set invalid spacerole + WithSpecMasterUserRecord(mur.GetName()), + WithNamespace(GetDefaultNamespace(space.Status.ProvisionedNamespaces)), + ) + + // then + require.NoError(t, err) + // wait for spacebinding request status to be set + _, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()}, + UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.UnableToCreateSpaceBinding(fmt.Sprintf("invalid role 'invalid' for space '%s'", space.Name))), + ) + require.NoError(t, err) + bindings, err := hostAwait.ListSpaceBindings(space.Name) + require.NoError(t, err) + assert.Len(t, bindings, 1) + }) + + t.Run("unable create space binding request with invalid MasterUserRecord", func(t *testing.T) { + space, _, _ := CreateSpace(t, awaitilities, testspace.WithTierName("appstudio"), testspace.WithSpecTargetCluster(memberAwait.ClusterName)) + // wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it. + space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces()) + require.NoError(t, err) + // create the spacebinding request + spaceBindingRequest := CreateSpaceBindingRequest(t, awaitilities, memberAwait.ClusterName, + WithSpecSpaceRole("admin"), + WithSpecMasterUserRecord("invalidMUR"), // we set an invalid MUR + WithNamespace(GetDefaultNamespace(space.Status.ProvisionedNamespaces)), + ) + + // then + require.NoError(t, err) + // wait for spacebinding request status to be set + _, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()}, + UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.UnableToCreateSpaceBinding("unable to get MUR: MasterUserRecord.toolchain.dev.openshift.com \"invalidMUR\" not found")), + ) + require.NoError(t, err) + bindings, err := hostAwait.ListSpaceBindings(space.Name) + require.NoError(t, err) + assert.Len(t, bindings, 1) + }) + }) +} + +func TestUpdateSpaceBindingRequest(t *testing.T) { + // given + t.Parallel() + // make sure everything is ready before running the actual tests + awaitilities := WaitForDeployments(t) + hostAwait := awaitilities.Host() + memberAwait := awaitilities.Member1() + + t.Run("update space binding request SpaceRole", func(t *testing.T) { + // when + space, spaceBindingRequest, _ := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "contributor") + _, err := memberAwait.UpdateSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.Namespace, Name: spaceBindingRequest.Name}, + func(s *toolchainv1alpha1.SpaceBindingRequest) { + s.Spec.SpaceRole = "admin" // set to admin from contributor + }, + ) + require.NoError(t, err) + + //then + // wait for both SpaceBindingRequest and SpaceBinding to have same SpaceRole + spaceBindingRequest, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()}, + UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.Ready()), + UntilSpaceBindingRequestHasSpecSpaceRole("admin"), // has admin role + UntilSpaceBindingRequestHasSpecMUR(spaceBindingRequest.Spec.MasterUserRecord), + ) + require.NoError(t, err) + _, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name, + UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord), + UntilSpaceBindingHasSpaceName(space.Name), + UntilSpaceBindingHasSpaceRole("admin"), // has admin role + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()), + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()), + ) + require.NoError(t, err) + }) + + t.Run("update space binding request MasterUserRecord", func(t *testing.T) { + // when + space, spaceBindingRequest, _ := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin") + // let's create another MUR that will have access to the space + username := uuid.Must(uuid.NewV4()).String() + _, newmur := NewSignupRequest(awaitilities). + Username(username). + Email(username + "@acme.com"). + ManuallyApprove(). + TargetCluster(memberAwait). + RequireConditions(ConditionSet(Default(), ApprovedByAdmin())...). + NoSpace(). + WaitForMUR().Execute(t).Resources() + // and we update the MUR in the SBR + _, err := memberAwait.UpdateSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.Namespace, Name: spaceBindingRequest.Name}, + func(s *toolchainv1alpha1.SpaceBindingRequest) { + s.Spec.MasterUserRecord = newmur.GetName() // set to the new MUR + }, + ) + require.NoError(t, err) + + //then + // wait for both SpaceBindingRequest and SpaceBinding to have same MUR + spaceBindingRequest, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()}, + UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.Ready()), + UntilSpaceBindingRequestHasSpecSpaceRole(spaceBindingRequest.Spec.SpaceRole), + UntilSpaceBindingRequestHasSpecMUR(newmur.GetName()), // new MUR + ) + require.NoError(t, err) + _, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name, + UntilSpaceBindingHasMurName(newmur.GetName()), // has new MUR + UntilSpaceBindingHasSpaceName(space.Name), + UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole), + ) + require.NoError(t, err) + }) +} + +func NewSpaceBindingRequest(t *testing.T, awaitilities Awaitilities, memberAwait *MemberAwaitility, hostAwait *HostAwaitility, spaceRole string) (*toolchainv1alpha1.Space, *toolchainv1alpha1.SpaceBindingRequest, *toolchainv1alpha1.SpaceBinding) { + space, firstUserSignup, _ := CreateSpace(t, awaitilities, testspace.WithTierName("appstudio"), testspace.WithSpecTargetCluster(memberAwait.ClusterName)) + // wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it. + space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces()) + require.NoError(t, err) + // let's create a new MUR that will have access to the space + username := uuid.Must(uuid.NewV4()).String() + _, secondUserMUR := NewSignupRequest(awaitilities). + Username(username). + Email(username + "@acme.com"). + ManuallyApprove(). + TargetCluster(memberAwait). + RequireConditions(ConditionSet(Default(), ApprovedByAdmin())...). + NoSpace(). + WaitForMUR().Execute(t).Resources() + // create the spacebinding request + spaceBindingRequest := CreateSpaceBindingRequest(t, awaitilities, memberAwait.ClusterName, + WithSpecSpaceRole(spaceRole), + WithSpecMasterUserRecord(secondUserMUR.GetName()), + WithNamespace(GetDefaultNamespace(space.Status.ProvisionedNamespaces)), + ) + + // then + // check for the spaceBinding creation + spaceBinding, err := hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name, + UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord), + UntilSpaceBindingHasSpaceName(space.Name), + UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole), + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()), + UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()), + ) + require.NoError(t, err) + // wait for spacebinding request status + spaceBindingRequest, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()}, + UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.Ready()), + ) + require.NoError(t, err) + tier, err := awaitilities.Host().WaitForNSTemplateTier(t, space.Spec.TierName) + require.NoError(t, err) + if spaceRole == "admin" { + usernamesSorted := []string{firstUserSignup.Status.CompliantUsername, secondUserMUR.Name} + sort.Strings(usernamesSorted) + _, err = memberAwait.WaitForNSTmplSet(t, space.Name, + UntilNSTemplateSetHasSpaceRoles( + SpaceRole(tier.Spec.SpaceRoles[spaceRole].TemplateRef, usernamesSorted[0], usernamesSorted[1]))) + require.NoError(t, err) + } else { + _, err = memberAwait.WaitForNSTmplSet(t, space.Name, + UntilNSTemplateSetHasSpaceRoles( + SpaceRole(tier.Spec.SpaceRoles["admin"].TemplateRef, firstUserSignup.Status.CompliantUsername), + SpaceRole(tier.Spec.SpaceRoles[spaceRole].TemplateRef, secondUserMUR.Name))) + require.NoError(t, err) + } + VerifyResourcesProvisionedForSpace(t, awaitilities, space.Name) + return space, spaceBindingRequest, spaceBinding +} diff --git a/testsupport/spacebindingrequest.go b/testsupport/spacebindingrequest.go new file mode 100644 index 000000000..45065aaa9 --- /dev/null +++ b/testsupport/spacebindingrequest.go @@ -0,0 +1,50 @@ +package testsupport + +import ( + "testing" + + toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/wait" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type SpaceBindingRequestOption func(request *toolchainv1alpha1.SpaceBindingRequest) + +func WithSpecSpaceRole(spaceRole string) SpaceBindingRequestOption { + return func(s *toolchainv1alpha1.SpaceBindingRequest) { + s.Spec.SpaceRole = spaceRole + } +} + +func WithSpecMasterUserRecord(mur string) SpaceBindingRequestOption { + return func(s *toolchainv1alpha1.SpaceBindingRequest) { + s.Spec.MasterUserRecord = mur + } +} + +func WithNamespace(ns string) SpaceBindingRequestOption { + return func(s *toolchainv1alpha1.SpaceBindingRequest) { + s.ObjectMeta.Namespace = ns + } +} + +func CreateSpaceBindingRequest(t *testing.T, awaitilities wait.Awaitilities, memberName string, opts ...SpaceBindingRequestOption) *toolchainv1alpha1.SpaceBindingRequest { + memberAwait, err := awaitilities.Member(memberName) + require.NoError(t, err) + namePrefix := NewObjectNamePrefix(t) + + spaceBindingRequest := &toolchainv1alpha1.SpaceBindingRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: namePrefix + "-", + }, + } + for _, apply := range opts { + apply(spaceBindingRequest) + } + err = memberAwait.CreateWithCleanup(t, spaceBindingRequest) + require.NoError(t, err) + + return spaceBindingRequest +} diff --git a/testsupport/wait/host.go b/testsupport/wait/host.go index eab6752b0..37fcb5a0a 100644 --- a/testsupport/wait/host.go +++ b/testsupport/wait/host.go @@ -2206,6 +2206,48 @@ func UntilSpaceBindingHasSpaceRole(expected string) SpaceBindingWaitCriterion { } } +// UntilSpaceBindingHasDifferentUID returns a `SpaceBindingWaitCriterion` which checks that the given +// SpaceBinding has different UID (even if it has same name) +func UntilSpaceBindingHasDifferentUID(uid types.UID) SpaceBindingWaitCriterion { + return SpaceBindingWaitCriterion{ + Match: func(actual *toolchainv1alpha1.SpaceBinding) bool { + return actual.UID != uid + }, + Diff: func(actual *toolchainv1alpha1.SpaceBinding) string { + return fmt.Sprintf("expected SpaceBinding to not have UID %s; Actual UID %s", uid, actual.UID) + }, + } +} + +// UntilSpaceBindingHasCreationTimestampGreaterThan returns a `SpaceBindingWaitCriterion` which checks that the given +// SpaceBinding was created after a given creationTimestamp +func UntilSpaceBindingHasCreationTimestampGreaterThan(creationTimestamp time.Time) SpaceBindingWaitCriterion { + return SpaceBindingWaitCriterion{ + Match: func(actual *toolchainv1alpha1.SpaceBinding) bool { + return actual.CreationTimestamp.Time.After(creationTimestamp) + }, + Diff: func(actual *toolchainv1alpha1.SpaceBinding) string { + return fmt.Sprintf("expected SpaceBinding to be created after %s; Actual creation timestamp %s", creationTimestamp.String(), actual.CreationTimestamp.String()) + }, + } +} + +// UntilSpaceBindingHasLabel returns a `SpaceBindingWaitCriterion` which checks that the given +// SpaceBinding has a `key` equal to the given `value` +func UntilSpaceBindingHasLabel(key, value string) SpaceBindingWaitCriterion { + return SpaceBindingWaitCriterion{ + Match: func(actual *toolchainv1alpha1.SpaceBinding) bool { + return actual.Labels != nil && actual.Labels[key] == value + }, + Diff: func(actual *toolchainv1alpha1.SpaceBinding) string { + if len(actual.Labels) == 0 { + return fmt.Sprintf("expected to have a label with key '%s' and value '%s'", key, value) + } + return fmt.Sprintf("expected value of label '%s' to equal '%s'. Actual: '%s'", key, value, actual.Labels[key]) + }, + } +} + type SocialEventWaitCriterion struct { Match func(*toolchainv1alpha1.SocialEvent) bool Diff func(*toolchainv1alpha1.SocialEvent) string diff --git a/testsupport/wait/member.go b/testsupport/wait/member.go index eae3c6edb..306d3b95b 100644 --- a/testsupport/wait/member.go +++ b/testsupport/wait/member.go @@ -419,6 +419,104 @@ func (a *MemberAwaitility) printSpaceRequestWaitCriterionDiffs(t *testing.T, act t.Log(buf.String()) } +// SpaceBindingRequestWaitCriterion a struct to compare with a given SpaceBindingRequest +type SpaceBindingRequestWaitCriterion struct { + Match func(request *toolchainv1alpha1.SpaceBindingRequest) bool + Diff func(request *toolchainv1alpha1.SpaceBindingRequest) string +} + +// WaitForSpaceBindingRequest waits until there is a SpaceBindingRequest available with the given name, namespace, spec and the set of status conditions +func (a *MemberAwaitility) WaitForSpaceBindingRequest(t *testing.T, namespacedName types.NamespacedName, criteria ...SpaceBindingRequestWaitCriterion) (*toolchainv1alpha1.SpaceBindingRequest, error) { + var spaceBindingRequest *toolchainv1alpha1.SpaceBindingRequest + err := wait.Poll(a.RetryInterval, a.Timeout, func() (done bool, err error) { + obj := &toolchainv1alpha1.SpaceBindingRequest{} + if err := a.Client.Get(context.TODO(), namespacedName, obj); err != nil { + if errors.IsNotFound(err) { + return false, nil + } + return false, err + } + spaceBindingRequest = obj + return matchSpaceBindingRequestWaitCriterion(obj, criteria...), nil + }) + // no match found, print the diffs + if err != nil { + a.printSpaceBindingRequestWaitCriterionDiffs(t, spaceBindingRequest, criteria...) + } + return spaceBindingRequest, err +} + +// UntilSpaceBindingRequestHasConditions returns a `SpaceBindingRequestWaitCriterion` which checks that the given +// SpaceBindingRequest has exactly all the given status conditions +func UntilSpaceBindingRequestHasConditions(expected ...toolchainv1alpha1.Condition) SpaceBindingRequestWaitCriterion { + return SpaceBindingRequestWaitCriterion{ + Match: func(actual *toolchainv1alpha1.SpaceBindingRequest) bool { + return test.ConditionsMatch(actual.Status.Conditions, expected...) + }, + Diff: func(actual *toolchainv1alpha1.SpaceBindingRequest) string { + return fmt.Sprintf("expected conditions to match:\n%s", Diff(expected, actual.Status.Conditions)) + }, + } +} + +// UntilSpaceBindingRequestHasSpecSpaceRole returns a `SpaceBindingRequestWaitCriterion` which checks that the given +// SpaceBindingRequest has a specific .Spec.SpaceRole +func UntilSpaceBindingRequestHasSpecSpaceRole(expected string) SpaceBindingRequestWaitCriterion { + return SpaceBindingRequestWaitCriterion{ + Match: func(actual *toolchainv1alpha1.SpaceBindingRequest) bool { + return actual.Spec.SpaceRole == expected + }, + Diff: func(actual *toolchainv1alpha1.SpaceBindingRequest) string { + return fmt.Sprintf("expected SpaceRole to be '%s'\nbut it was '%s'", expected, actual.Spec.SpaceRole) + }, + } +} + +// UntilSpaceBindingRequestHasSpecMUR returns a `SpaceBindingRequestWaitCriterion` which checks that the given +// SpaceBindingRequest has a specific .Spec.MasterUserRecord +func UntilSpaceBindingRequestHasSpecMUR(expected string) SpaceBindingRequestWaitCriterion { + return SpaceBindingRequestWaitCriterion{ + Match: func(actual *toolchainv1alpha1.SpaceBindingRequest) bool { + return actual.Spec.MasterUserRecord == expected + }, + Diff: func(actual *toolchainv1alpha1.SpaceBindingRequest) string { + return fmt.Sprintf("expected MasterUserRecord to be '%s'\nbut it was '%s'", expected, actual.Spec.MasterUserRecord) + }, + } +} + +func matchSpaceBindingRequestWaitCriterion(actual *toolchainv1alpha1.SpaceBindingRequest, criteria ...SpaceBindingRequestWaitCriterion) bool { + for _, c := range criteria { + if !c.Match(actual) { + return false + } + } + return true +} + +func (a *MemberAwaitility) printSpaceBindingRequestWaitCriterionDiffs(t *testing.T, actual *toolchainv1alpha1.SpaceBindingRequest, criteria ...SpaceBindingRequestWaitCriterion) { + buf := &strings.Builder{} + if actual == nil { + buf.WriteString("failed to find SpaceBindingRequest\n") + buf.WriteString(a.listAndReturnContent("SpaceBindingRequest", a.Namespace, &toolchainv1alpha1.SpaceBindingRequestList{})) + } else { + buf.WriteString("failed to find SpaceBindingRequest with matching criteria:\n") + buf.WriteString("----\n") + buf.WriteString("actual:\n") + y, _ := StringifyObject(actual) + buf.Write(y) + buf.WriteString("\n----\n") + buf.WriteString("diffs:\n") + for _, c := range criteria { + if !c.Match(actual) && c.Diff != nil { + buf.WriteString(c.Diff(actual)) + buf.WriteString("\n") + } + } + } + t.Log(buf.String()) +} + // NSTemplateSetWaitCriterion a struct to compare with a given NSTemplateSet type NSTemplateSetWaitCriterion struct { Match func(*toolchainv1alpha1.NSTemplateSet) bool @@ -1315,6 +1413,27 @@ func (a *MemberAwaitility) UpdateSpaceRequest(t *testing.T, spaceRequestNamespac return sr, err } +// UpdateSpaceBindingRequest tries to update the Spec of the given SpaceBindingRequest +// If it fails with an error (for example if the object has been modified) then it retrieves the latest version and tries again +// Returns the updated SpaceBindingRequest +func (a *MemberAwaitility) UpdateSpaceBindingRequest(t *testing.T, spaceBindingRequestNamespacedName types.NamespacedName, modifySpaceBindingRequest func(s *toolchainv1alpha1.SpaceBindingRequest)) (*toolchainv1alpha1.SpaceBindingRequest, error) { + var sr *toolchainv1alpha1.SpaceBindingRequest + err := wait.Poll(a.RetryInterval, a.Timeout, func() (done bool, err error) { + freshSpaceBindingRequest := &toolchainv1alpha1.SpaceBindingRequest{} + if err := a.Client.Get(context.TODO(), spaceBindingRequestNamespacedName, freshSpaceBindingRequest); err != nil { + return true, err + } + modifySpaceBindingRequest(freshSpaceBindingRequest) + if err := a.Client.Update(context.TODO(), freshSpaceBindingRequest); err != nil { + t.Logf("error updating SpaceBindingRequest '%s' in namespace '%s': %s. Will retry again...", spaceBindingRequestNamespacedName.Name, spaceBindingRequestNamespacedName.Name, err.Error()) + return false, nil + } + sr = freshSpaceBindingRequest + return true, nil + }) + return sr, err +} + // Create tries to create the object until success // Workaround for https://github.com/kubernetes/kubernetes/issues/67761 func (a *MemberAwaitility) Create(t *testing.T, obj client.Object) error {