Skip to content
This repository has been archived by the owner on Nov 27, 2024. It is now read-only.

Commit

Permalink
rest: add persistence/writeclient (#132)
Browse files Browse the repository at this point in the history
* rest: add persistence/writeclient

Signed-off-by: Francesco Ilario <filario@redhat.com>

* add more tests, refactor

Signed-off-by: Francesco Ilario <filario@redhat.com>

* add comments and rename BuildClientFunc constructor func

Signed-off-by: Francesco Ilario <filario@redhat.com>

* update tests

Signed-off-by: Francesco Ilario <filario@redhat.com>

---------

Signed-off-by: Francesco Ilario <filario@redhat.com>
  • Loading branch information
filariow authored May 16, 2024
1 parent d5553cf commit cef4037
Show file tree
Hide file tree
Showing 7 changed files with 434 additions and 0 deletions.
19 changes: 19 additions & 0 deletions server/core/workspace/v2/interfaces_write.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package workspace

import (
"context"

"sigs.k8s.io/controller-runtime/pkg/client"

restworkspacesv1alpha1 "github.com/konflux-workspaces/workspaces/server/api/v1alpha1"
)

// WorkspaceCreator is the interface the data source needs to implement to allow the CreateWorkspaceHandler to properly create the workspace
type WorkspaceCreator interface {
CreateUserWorkspace(ctx context.Context, user string, workspace *restworkspacesv1alpha1.Workspace, opts ...client.CreateOption) error
}

// WorkspaceUpdater is the interface the data source needs to implement to allow the UpdateWorkspaceHandler to update the workspace
type WorkspaceUpdater interface {
UpdateUserWorkspace(ctx context.Context, user string, obj *restworkspacesv1alpha1.Workspace, opts ...client.UpdateOption) error
}
53 changes: 53 additions & 0 deletions server/persistence/writeclient/writeclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package writeclient

import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/konflux-workspaces/workspaces/server/persistence/iwclient"

workspacesv1alpha1 "github.com/konflux-workspaces/workspaces/operator/api/v1alpha1"
restworkspacesv1alpha1 "github.com/konflux-workspaces/workspaces/server/api/v1alpha1"
)

// BuildClientFunc defines a function that builds a controller-runtime client
// that impersonates the given user
type BuildClientFunc func(user string) (client.Client, error)

// WriteClient implements Write primitives on Workspaces.
// Creates or updates InternalWorkspaces starting from a request on Workspaces.
type WriteClient struct {
buildClient BuildClientFunc
workspacesNamespace string
workspacesReader *iwclient.Client
}

// New creates a new WriteClient
func New(buildClient BuildClientFunc, workspacesNamespace string, workspacesReader *iwclient.Client) *WriteClient {
return &WriteClient{
buildClient: buildClient,
workspacesNamespace: workspacesNamespace,
workspacesReader: workspacesReader,
}
}

// BuildBuildClientFuncForConfig provides a configured BuildClientFunc for building a controller-runtime client
// for a given cluster and impersonating an user
func BuildBuildClientFuncForConfig(config *rest.Config) BuildClientFunc {
newConfig := rest.CopyConfig(config)

return func(user string) (client.Client, error) {
newConfig.Impersonate.UserName = user

s := runtime.NewScheme()
if err := restworkspacesv1alpha1.AddToScheme(s); err != nil {
return nil, err
}
if err := workspacesv1alpha1.AddToScheme(s); err != nil {
return nil, err
}

return client.New(newConfig, client.Options{Scheme: s})
}
}
48 changes: 48 additions & 0 deletions server/persistence/writeclient/writeclient_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package writeclient

import (
"context"

"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/konflux-workspaces/workspaces/server/core/workspace/v2"
"github.com/konflux-workspaces/workspaces/server/log"
"github.com/konflux-workspaces/workspaces/server/persistence/mapper"

restworkspacesv1alpha1 "github.com/konflux-workspaces/workspaces/server/api/v1alpha1"
)

var _ workspace.WorkspaceCreator = &WriteClient{}

// CreateUserWorkspace creates as `user` the InternalWorkspace representing the provided Workspace
func (c *WriteClient) CreateUserWorkspace(ctx context.Context, user string, workspace *restworkspacesv1alpha1.Workspace, opts ...client.CreateOption) error {
cli, err := c.buildClient(user)
if err != nil {
return err
}

// map Workspace to InternalWorkspace
iw, err := mapper.Default.WorkspaceToInternalWorkspace(workspace)
if err != nil {
return err
}
iw.SetNamespace(c.workspacesNamespace)
iw.SetName("")
iw.SetGenerateName(workspace.Name)

// create InternalWorkspace
log.FromContext(ctx).Debug("creating user workspace", "workspace", workspace, "user", user)
if err := cli.Create(ctx, iw, opts...); err != nil {
return err
}

// map InternalWorkspace to Workspace
w, err := mapper.Default.InternalWorkspaceToWorkspace(iw)
if err != nil {
return err
}

// fill return value
w.DeepCopyInto(workspace)
return nil
}
93 changes: 93 additions & 0 deletions server/persistence/writeclient/writeclient_create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package writeclient_test

import (
"context"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/konflux-workspaces/workspaces/server/persistence/iwclient"
"github.com/konflux-workspaces/workspaces/server/persistence/writeclient"

workspacesv1alpha1 "github.com/konflux-workspaces/workspaces/operator/api/v1alpha1"
restworkspacesv1alpha1 "github.com/konflux-workspaces/workspaces/server/api/v1alpha1"
)

var _ = Describe("WriteclientCreate", func() {
var ctx context.Context
var fakeClient client.WithWatch
var cli *writeclient.WriteClient

user := "foo"
namespace := "bar"
workspace := restworkspacesv1alpha1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Namespace: "owner",
Name: "workspace-foo",
},
Spec: restworkspacesv1alpha1.WorkspaceSpec{},
}

validateCreatedInternalWorkspace := func(w *restworkspacesv1alpha1.Workspace, expectedVisibility workspacesv1alpha1.InternalWorkspaceVisibility) {
ww := workspacesv1alpha1.InternalWorkspaceList{}
err := fakeClient.List(
ctx,
&ww,
client.InNamespace(namespace),
client.MatchingLabels{
workspacesv1alpha1.LabelDisplayName: workspace.Name,
workspacesv1alpha1.LabelWorkspaceOwner: "owner",
},
)
Expect(err).NotTo(HaveOccurred())
Expect(ww.Items).To(HaveLen(1))
Expect(ww.Items[0].Spec).ToNot(BeNil())
Expect(ww.Items[0].Spec.Visibility).To(Equal(expectedVisibility))
}

BeforeEach(func() {
ctx = context.Background()
fakeClient = fake.NewFakeClient()
err := workspacesv1alpha1.AddToScheme(fakeClient.Scheme())
Expect(err).NotTo(HaveOccurred())

clientFunc := func(string) (client.Client, error) {
return fakeClient, nil
}

iwcli := iwclient.New(fakeClient, namespace, namespace)
cli = writeclient.New(clientFunc, namespace, iwcli)
})

When("creating a community workspace", func() {
It("should create workspaces using the given client", func() {
// given
workspace.Spec.Visibility = restworkspacesv1alpha1.WorkspaceVisibilityCommunity

// when
err := cli.CreateUserWorkspace(ctx, user, &workspace)

// then
Expect(err).NotTo(HaveOccurred())
validateCreatedInternalWorkspace(&workspace, workspacesv1alpha1.InternalWorkspaceVisibilityCommunity)
})
})

When("creating a community workspace", func() {
It("should create workspaces using the given client", func() {
// given
workspace.Spec.Visibility = restworkspacesv1alpha1.WorkspaceVisibilityPrivate

// when
err := cli.CreateUserWorkspace(ctx, user, &workspace)

// then
Expect(err).NotTo(HaveOccurred())
validateCreatedInternalWorkspace(&workspace, workspacesv1alpha1.InternalWorkspaceVisibilityPrivate)
})
})
})
18 changes: 18 additions & 0 deletions server/persistence/writeclient/writeclient_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package writeclient_test

import (
"log/slog"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/konflux-workspaces/workspaces/server/log"
)

func TestWriteclient(t *testing.T) {
slog.SetDefault(slog.New(&log.NoOpHandler{}))

RegisterFailHandler(Fail)
RunSpecs(t, "WriteClient Suite")
}
52 changes: 52 additions & 0 deletions server/persistence/writeclient/writeclient_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package writeclient

import (
"context"

kerrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/konflux-workspaces/workspaces/server/core/workspace/v2"
"github.com/konflux-workspaces/workspaces/server/log"
"github.com/konflux-workspaces/workspaces/server/persistence/iwclient"
"github.com/konflux-workspaces/workspaces/server/persistence/mapper"

workspacesv1alpha1 "github.com/konflux-workspaces/workspaces/operator/api/v1alpha1"
restworkspacesv1alpha1 "github.com/konflux-workspaces/workspaces/server/api/v1alpha1"
)

var _ workspace.WorkspaceUpdater = &WriteClient{}

// UpdateUserWorkspace updates as `user` the InternalWorkspace representing the provided Workspace
func (c *WriteClient) UpdateUserWorkspace(ctx context.Context, user string, workspace *restworkspacesv1alpha1.Workspace, opts ...client.UpdateOption) error {
// build client impersonating the user
cli, err := c.buildClient(user)
if err != nil {
return err
}

// map to InternalWorkspace
iw, err := mapper.Default.WorkspaceToInternalWorkspace(workspace)
if err != nil {
return kerrors.NewBadRequest("malformed workspace")
}

// get the InternalWorkspace as user
ciw := workspacesv1alpha1.InternalWorkspace{}
key := iwclient.SpaceKey{Owner: workspace.Namespace, Name: workspace.Name}
if err := c.workspacesReader.GetAsUser(ctx, user, key, &ciw); err != nil {
return kerrors.NewNotFound(restworkspacesv1alpha1.GroupVersion.WithResource("workspaces").GroupResource(), workspace.Name)
}

// check Generation matching
if iw.Generation != ciw.Generation {
return kerrors.NewResourceExpired("workspace version changed")
}

// update the InternalWorkspace
iw.SetName(ciw.GetName())
iw.SetNamespace(ciw.GetNamespace())
iw.SetResourceVersion(ciw.GetResourceVersion())
log.FromContext(ctx).Debug("updating user workspace", "workspace", iw, "user", user)
return cli.Update(ctx, iw, opts...)
}
Loading

0 comments on commit cef4037

Please sign in to comment.