diff --git a/apiserver/organization/create.go b/apiserver/organization/create.go index e2f63fa7..56699cfc 100644 --- a/apiserver/organization/create.go +++ b/apiserver/organization/create.go @@ -35,9 +35,11 @@ func (s *organizationStorage) Create(ctx context.Context, obj runtime.Object, cr } func (s *organizationStorage) create(ctx context.Context, org *orgv1.Organization, options *metav1.CreateOptions) (*orgv1.Organization, error) { - if err := s.namepaces.CreateNamespace(ctx, org.ToNamespace(), options); err != nil { + ns, err := s.namepaces.CreateNamespace(ctx, org.ToNamespace(), options) + if err != nil { return nil, convertNamespaceError(err) } + org = orgv1.NewOrganizationFromNS(ns) if err := s.rbac.CreateRoleBindings(ctx, org.Name); err != nil { // rollback @@ -48,7 +50,7 @@ func (s *organizationStorage) create(ctx context.Context, org *orgv1.Organizatio return nil, fmt.Errorf("failed to create organization: %w", err) } - orgMembers := newOrganizationMembers(ctx, org.Name, s.usernamePrefix) + orgMembers := newOrganizationMembers(ctx, org, s.usernamePrefix) if err := s.members.CreateMembers(ctx, orgMembers); err != nil { // rollback @@ -63,7 +65,7 @@ func (s *organizationStorage) create(ctx context.Context, org *orgv1.Organizatio return org, nil } -func newOrganizationMembers(ctx context.Context, organization, usernamePrefix string) *controlv1.OrganizationMembers { +func newOrganizationMembers(ctx context.Context, organization *orgv1.Organization, usernamePrefix string) *controlv1.OrganizationMembers { userRefs := []controlv1.UserRef{} user, ok := request.UserFrom(ctx) if ok { @@ -72,10 +74,18 @@ func newOrganizationMembers(ctx context.Context, organization, usernamePrefix st }) } + isController := true return &controlv1.OrganizationMembers{ ObjectMeta: metav1.ObjectMeta{ Name: "members", - Namespace: organization, + Namespace: organization.Name, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: orgv1.GroupVersion.String(), + Kind: "Organization", + UID: organization.UID, + Name: organization.Name, + Controller: &isController, + }}, }, Spec: controlv1.OrganizationMembersSpec{ UserRefs: userRefs, diff --git a/apiserver/organization/create_test.go b/apiserver/organization/create_test.go index 6f96eff1..1ec0b1bb 100644 --- a/apiserver/organization/create_test.go +++ b/apiserver/organization/create_test.go @@ -14,6 +14,7 @@ import ( controlv1 "github.com/appuio/control-api/apis/v1" mock "github.com/appuio/control-api/apiserver/organization/mock" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -88,20 +89,24 @@ func TestOrganizationStorage_Create(t *testing.T) { mmemb := mock.NewMockmemberProvider(ctrl) os.members = mmemb os.usernamePrefix = "appuio#" + nsOut := &corev1.Namespace{} + if tc.organizationOut != nil { + nsOut = tc.organizationOut.ToNamespace() + } mauth.EXPECT(). Authorize(gomock.Any(), isAuthRequest("create")). Return(tc.authDecision.decision, tc.authDecision.reason, tc.authDecision.err). Times(1) mnp.EXPECT(). CreateNamespace(gomock.Any(), gomock.Any(), gomock.Any()). - Return(tc.namespaceErr). + Return(nsOut, tc.namespaceErr). AnyTimes() mrb.EXPECT(). CreateRoleBindings(gomock.Any(), gomock.Any()). Return(nil). AnyTimes() mmemb.EXPECT(). - CreateMembers(gomock.Any(), containsMember(tc.memberName)). + CreateMembers(gomock.Any(), containsMemberAndOwner(tc.organizationIn.Name, tc.memberName)). Return(nil). AnyTimes() @@ -158,7 +163,7 @@ func TestOrganizationStorage_Create_Abort(t *testing.T) { Times(1) mnp.EXPECT(). CreateNamespace(gomock.Any(), gomock.Any(), gomock.Any()). - Return(nil). + Return(fooNs, nil). Times(1) if tc.failRoleBinding { @@ -202,7 +207,8 @@ func TestOrganizationStorage_Create_Abort(t *testing.T) { } type memberMatcher struct { - user string + owner string + user string } func (m memberMatcher) Matches(x interface{}) bool { @@ -210,13 +216,14 @@ func (m memberMatcher) Matches(x interface{}) bool { if !ok { return ok } - return len(mem.Spec.UserRefs) > 0 && mem.Spec.UserRefs[0].ID == m.user + return len(mem.Spec.UserRefs) > 0 && mem.Spec.UserRefs[0].ID == m.user && + len(mem.OwnerReferences) > 0 && mem.OwnerReferences[0].Name == m.owner } func (m memberMatcher) String() string { - return fmt.Sprintf("contains %s", m.user) + return fmt.Sprintf("contains %s and owned by %s", m.user, m.owner) } -func containsMember(user string) memberMatcher { - return memberMatcher{user: user} +func containsMemberAndOwner(owner, user string) memberMatcher { + return memberMatcher{user: user, owner: owner} } diff --git a/apiserver/organization/mock/namespace.go b/apiserver/organization/mock/namespace.go index 94330504..9e14c9da 100644 --- a/apiserver/organization/mock/namespace.go +++ b/apiserver/organization/mock/namespace.go @@ -39,11 +39,12 @@ func (m *MocknamespaceProvider) EXPECT() *MocknamespaceProviderMockRecorder { } // CreateNamespace mocks base method. -func (m *MocknamespaceProvider) CreateNamespace(ctx context.Context, ns *v1.Namespace, options *v10.CreateOptions) error { +func (m *MocknamespaceProvider) CreateNamespace(ctx context.Context, ns *v1.Namespace, options *v10.CreateOptions) (*v1.Namespace, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateNamespace", ctx, ns, options) - ret0, _ := ret[0].(error) - return ret0 + ret0, _ := ret[0].(*v1.Namespace) + ret1, _ := ret[1].(error) + return ret0, ret1 } // CreateNamespace indicates an expected call of CreateNamespace. diff --git a/apiserver/organization/namespace.go b/apiserver/organization/namespace.go index bee16fd9..9a4aad41 100644 --- a/apiserver/organization/namespace.go +++ b/apiserver/organization/namespace.go @@ -16,7 +16,7 @@ import ( type namespaceProvider interface { GetNamespace(ctx context.Context, name string, options *metav1.GetOptions) (*corev1.Namespace, error) DeleteNamespace(ctx context.Context, name string, options *metav1.DeleteOptions) (*corev1.Namespace, error) - CreateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) error + CreateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) (*corev1.Namespace, error) UpdateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.UpdateOptions) error ListNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (*corev1.NamespaceList, error) WatchNamespaces(ctx context.Context, options *metainternalversion.ListOptions) (watch.Interface, error) @@ -41,10 +41,11 @@ func (p *kubeNamespaceProvider) DeleteNamespace(ctx context.Context, name string return &ns, err } -func (p *kubeNamespaceProvider) CreateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) error { - return p.Client.Create(ctx, ns, &client.CreateOptions{ +func (p *kubeNamespaceProvider) CreateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.CreateOptions) (*corev1.Namespace, error) { + err := p.Client.Create(ctx, ns, &client.CreateOptions{ Raw: options, }) + return ns, err } func (p *kubeNamespaceProvider) UpdateNamespace(ctx context.Context, ns *corev1.Namespace, options *metav1.UpdateOptions) error {