mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-27 07:03:17 +08:00
* feat(api)!: use sandbox names as canonical references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(cli): update forward color fixture for workspace scope Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(supervisor): use sandbox names for settings lookup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): use canonical sandbox request fields Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): use canonical sandbox receipt field Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(server): harden sandbox mutation handling Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): update rebased sandbox references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(api)!: standardize canonical entity references Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(api): codify protobuf API conventions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): preserve workspace selector semantics Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): restore workspace selector parity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): preserve descriptive name fields Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(api): update e2e request fixtures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(core): omit workspace selector during bootstrap Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(api): remove proto convention checker Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(api): refresh schema fingerprints after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(cli): use canonical provider receipt field Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com>
175 lines
5.9 KiB
Go
175 lines
5.9 KiB
Go
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package v1
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/internal/converter"
|
|
pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
type workspaceClient struct {
|
|
client pb.OpenShellClient
|
|
}
|
|
|
|
func newWorkspaceClient(conn grpc.ClientConnInterface) *workspaceClient {
|
|
return &workspaceClient{client: pb.NewOpenShellClient(conn)}
|
|
}
|
|
|
|
func (w *workspaceClient) Create(ctx context.Context, name string, labels map[string]string) (*Workspace, error) {
|
|
if name == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"}
|
|
}
|
|
|
|
resp, err := w.client.CreateWorkspace(ctx, &pb.CreateWorkspaceRequest{
|
|
Name: name,
|
|
Labels: labels,
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return converter.WorkspaceFromProto(resp.GetWorkspace()), nil
|
|
}
|
|
|
|
func (w *workspaceClient) Get(ctx context.Context, name string) (*Workspace, error) {
|
|
if name == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"}
|
|
}
|
|
|
|
resp, err := w.client.GetWorkspace(ctx, &pb.GetWorkspaceRequest{
|
|
Name: name,
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return converter.WorkspaceFromProto(resp.GetWorkspace()), nil
|
|
}
|
|
|
|
func (w *workspaceClient) List(opts ...ListOptions) (*Pager[*Workspace], error) {
|
|
pageSize, err := listPageSize(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pageToken, labelSelector string
|
|
if len(opts) > 0 {
|
|
pageToken = opts[0].PageToken
|
|
labelSelector = opts[0].LabelSelector
|
|
}
|
|
return newPager(pageToken, func(ctx context.Context, pageToken string) (*Page[*Workspace], error) {
|
|
req := &pb.ListWorkspacesRequest{PageSize: pageSize, PageToken: pageToken, LabelSelector: labelSelector}
|
|
resp, err := w.client.ListWorkspaces(ctx, req)
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
workspaces := make([]*Workspace, 0, len(resp.GetWorkspaces()))
|
|
for _, proto := range resp.GetWorkspaces() {
|
|
workspaces = append(workspaces, converter.WorkspaceFromProto(proto))
|
|
}
|
|
return &Page[*Workspace]{Items: workspaces, NextPageToken: resp.GetNextPageToken()}, nil
|
|
}), nil
|
|
}
|
|
|
|
func (w *workspaceClient) ListAll(ctx context.Context, opts ...ListOptions) ([]*Workspace, error) {
|
|
pager, err := w.List(opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pager.All(ctx)
|
|
}
|
|
|
|
func (w *workspaceClient) Delete(ctx context.Context, name string, opts ...DeleteOptions) (*DeletionResult, error) {
|
|
if name == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"}
|
|
}
|
|
|
|
resp, err := w.client.DeleteWorkspace(ctx, &pb.DeleteWorkspaceRequest{
|
|
AllowMissing: allowMissing(opts),
|
|
Name: name,
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil
|
|
}
|
|
|
|
func (w *workspaceClient) AddMember(ctx context.Context, workspace, principalSubject string, role WorkspaceRole) (*WorkspaceMember, error) {
|
|
if workspace == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"}
|
|
}
|
|
if principalSubject == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "principal subject must not be empty"}
|
|
}
|
|
|
|
protoRole := converter.WorkspaceRoleToProto(role)
|
|
if protoRole == pb.WorkspaceRole_WORKSPACE_ROLE_UNSPECIFIED {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "role must be Admin or User"}
|
|
}
|
|
|
|
resp, err := w.client.AddWorkspaceMember(ctx, &pb.AddWorkspaceMemberRequest{
|
|
WorkspaceScope: namedWorkspaceScope(workspace),
|
|
PrincipalSubject: principalSubject,
|
|
Role: protoRole,
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return converter.WorkspaceMemberFromProto(resp.GetMember()), nil
|
|
}
|
|
|
|
func (w *workspaceClient) RemoveMember(ctx context.Context, workspace, principalSubject string, opts ...DeleteOptions) (*DeletionResult, error) {
|
|
if workspace == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"}
|
|
}
|
|
if principalSubject == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "principal subject must not be empty"}
|
|
}
|
|
|
|
resp, err := w.client.RemoveWorkspaceMember(ctx, &pb.RemoveWorkspaceMemberRequest{
|
|
AllowMissing: allowMissing(opts),
|
|
WorkspaceScope: namedWorkspaceScope(workspace),
|
|
PrincipalSubject: principalSubject,
|
|
})
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
return &DeletionResult{Outcome: DeletionOutcome(resp.GetOutcome())}, nil
|
|
}
|
|
|
|
func (w *workspaceClient) ListMembers(workspace string, opts ...ListOptions) (*Pager[*WorkspaceMember], error) {
|
|
if workspace == "" {
|
|
return nil, &StatusError{Code: ErrorInvalidArgument, Message: "workspace name must not be empty"}
|
|
}
|
|
|
|
pageSize, err := listPageSize(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pageToken string
|
|
if len(opts) > 0 {
|
|
pageToken = opts[0].PageToken
|
|
}
|
|
return newPager(pageToken, func(ctx context.Context, pageToken string) (*Page[*WorkspaceMember], error) {
|
|
req := &pb.ListWorkspaceMembersRequest{WorkspaceScope: namedWorkspaceScope(workspace), PageSize: pageSize, PageToken: pageToken}
|
|
resp, err := w.client.ListWorkspaceMembers(ctx, req)
|
|
if err != nil {
|
|
return nil, converter.FromGRPCError(err)
|
|
}
|
|
members := make([]*WorkspaceMember, 0, len(resp.GetMembers()))
|
|
for _, proto := range resp.GetMembers() {
|
|
members = append(members, converter.WorkspaceMemberFromProto(proto))
|
|
}
|
|
return &Page[*WorkspaceMember]{Items: members, NextPageToken: resp.GetNextPageToken()}, nil
|
|
}), nil
|
|
}
|
|
|
|
func (w *workspaceClient) ListAllMembers(ctx context.Context, workspace string, opts ...ListOptions) ([]*WorkspaceMember, error) {
|
|
pager, err := w.ListMembers(workspace, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return pager.All(ctx)
|
|
}
|