Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support sharing workspaces #842

Merged
merged 2 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions api/v1alpha1/user_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ type UserSpec struct {
}

type UserStatus struct {
Phase corev1.NamespacePhase `json:"phase,omitempty"`
Namespace ObjectRef `json:"namespace,omitempty"`
Addons []ObjectRef `json:"addons,omitempty"`
Phase corev1.NamespacePhase `json:"phase,omitempty"`
Namespace ObjectRef `json:"namespace,omitempty"`
Addons []ObjectRef `json:"addons,omitempty"`
SharedWorkspaces []ObjectRef `json:"sharedWorkspaces,omitempty"`
}

type UserAddon struct {
Expand Down
13 changes: 7 additions & 6 deletions api/v1alpha1/workspace_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,13 @@ const (

// NetworkRule is an abstract network configuration rule for workspace
type NetworkRule struct {
Protocol string `json:"protocol"`
PortNumber int32 `json:"portNumber"`
CustomHostPrefix string `json:"customHostPrefix,omitempty"`
HTTPPath string `json:"httpPath,omitempty"`
TargetPortNumber *int32 `json:"targetPortNumber,omitempty"`
Public bool `json:"public"`
Protocol string `json:"protocol"`
PortNumber int32 `json:"portNumber"`
CustomHostPrefix string `json:"customHostPrefix,omitempty"`
HTTPPath string `json:"httpPath,omitempty"`
TargetPortNumber *int32 `json:"targetPortNumber,omitempty"`
Public bool `json:"public"`
AllowedUsers []string `json:"allowedUsers,omitempty"`
}

func HTTPUniqueKey(host, httpPath string) string {
Expand Down
12 changes: 12 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions charts/cosmo/crds/cosmo-workspace.github.io_users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,56 @@ spec:
x-kubernetes-map-type: atomic
phase:
type: string
sharedWorkspaces:
items:
description: ObjectRef is a reference of resource which is created
by the Instance
properties:
apiVersion:
description: API version of the referent.
type: string
creationTimestamp:
format: date-time
type: string
fieldPath:
description: |-
If referring to a piece of an object instead of an entire object, this string
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within a pod, this would take on a value like:
"spec.containers{name}" (where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]" (container with
index 2 in this pod). This syntax is chosen only to have some well-defined way of
referencing a part of an object.
TODO: this design is not final and this field is subject to change in the future.
type: string
kind:
description: |-
Kind of the referent.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
name:
description: |-
Name of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
namespace:
description: |-
Namespace of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
type: string
resourceVersion:
description: |-
Specific resourceVersion to which this reference is made, if any.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
type: string
uid:
description: |-
UID of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
type: string
type: object
x-kubernetes-map-type: atomic
type: array
type: object
type: object
served: true
Expand Down
4 changes: 4 additions & 0 deletions charts/cosmo/crds/cosmo-workspace.github.io_workspaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ spec:
description: NetworkRule is an abstract network configuration rule
for workspace
properties:
allowedUsers:
items:
type: string
type: array
customHostPrefix:
type: string
httpPath:
Expand Down
1 change: 1 addition & 0 deletions charts/cosmo/templates/cosmo-username-headers-addon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ spec:
headers:
customRequestHeaders:
X-Cosmo-UserName: '{{ print "{{USER_NAME}}" }}'
X-Cosmo-UserName-{{ print "{{USER_NAME}}" }}: '1'
customResponseHeaders:
X-Cosmo-UserName: '{{ print "{{USER_NAME}}" }}'
{{- end }}
50 changes: 50 additions & 0 deletions config/crd/bases/cosmo-workspace.github.io_users.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,56 @@ spec:
x-kubernetes-map-type: atomic
phase:
type: string
sharedWorkspaces:
items:
description: ObjectRef is a reference of resource which is created
by the Instance
properties:
apiVersion:
description: API version of the referent.
type: string
creationTimestamp:
format: date-time
type: string
fieldPath:
description: |-
If referring to a piece of an object instead of an entire object, this string
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
For example, if the object reference is to a container within a pod, this would take on a value like:
"spec.containers{name}" (where "name" refers to the name of the container that triggered
the event) or if no container name is specified "spec.containers[2]" (container with
index 2 in this pod). This syntax is chosen only to have some well-defined way of
referencing a part of an object.
TODO: this design is not final and this field is subject to change in the future.
type: string
kind:
description: |-
Kind of the referent.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
name:
description: |-
Name of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
namespace:
description: |-
Namespace of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
type: string
resourceVersion:
description: |-
Specific resourceVersion to which this reference is made, if any.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
type: string
uid:
description: |-
UID of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
type: string
type: object
x-kubernetes-map-type: atomic
type: array
type: object
type: object
served: true
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/cosmo-workspace.github.io_workspaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ spec:
description: NetworkRule is an abstract network configuration rule
for workspace
properties:
allowedUsers:
items:
type: string
type: array
customHostPrefix:
type: string
httpPath:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ spec:
headers:
customRequestHeaders:
X-Cosmo-UserName: '{{USER_NAME}}'
X-Cosmo-UserName-{{USER_NAME}}: '1'
customResponseHeaders:
X-Cosmo-UserName: '{{USER_NAME}}'
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ spec:
headers:
customRequestHeaders:
X-Cosmo-UserName: "{{USER_NAME}}"
X-Cosmo-UserName-{{USER_NAME}}: "1"
customResponseHeaders:
X-Cosmo-UserName: "{{USER_NAME}}"
2 changes: 1 addition & 1 deletion internal/cmd/workspace/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (o *CreateOption) RunE(cmd *cobra.Command, args []string) error {
}

fmt.Fprintln(cmd.OutOrStdout(), color.GreenString("Successfully created workspace %s", o.WorkspaceName))
OutputTable(cmd.OutOrStdout(), []*dashv1alpha1.Workspace{ws})
OutputTable(cmd.OutOrStdout(), o.UserName, []*dashv1alpha1.Workspace{ws})

return nil
}
Expand Down
47 changes: 32 additions & 15 deletions internal/cmd/workspace/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/cosmo-workspace/cosmo/pkg/apiconv"
"github.com/cosmo-workspace/cosmo/pkg/cli"
"github.com/cosmo-workspace/cosmo/pkg/clog"
"github.com/cosmo-workspace/cosmo/pkg/kosmo"
dashv1alpha1 "github.com/cosmo-workspace/cosmo/proto/gen/dashboard/v1alpha1"
)

Expand Down Expand Up @@ -105,7 +106,7 @@ func (o *GetOption) RunE(cmd *cobra.Command, args []string) error {
users = u
}
for _, user := range users {
wss, err := o.ListWorkspaces(ctx, user.Name)
wss, err := o.ListWorkspaces(ctx, user.Name, !o.AllUsers)
if err != nil {
return err
}
Expand All @@ -116,14 +117,18 @@ func (o *GetOption) RunE(cmd *cobra.Command, args []string) error {

workspaces = o.ApplyFilters(workspaces)

username := o.UserName
if o.AllUsers {
username = ""
}
if o.OutputFormat == "yaml" {
o.OutputYAML(cmd.OutOrStdout(), workspaces)
return nil
} else if o.OutputFormat == "wide" {
OutputWideTable(cmd.OutOrStdout(), workspaces)
OutputWideTable(cmd.OutOrStdout(), username, workspaces)
return nil
} else {
OutputTable(cmd.OutOrStdout(), workspaces)
OutputTable(cmd.OutOrStdout(), username, workspaces)
return nil
}
}
Expand All @@ -136,11 +141,11 @@ func (o *GetOption) ListUsers(ctx context.Context) ([]*dashv1alpha1.User, error)
}
}

func (o *GetOption) ListWorkspaces(ctx context.Context, userName string) ([]*dashv1alpha1.Workspace, error) {
func (o *GetOption) ListWorkspaces(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) {
if o.UseKubeAPI {
return o.listWorkspacesByKubeClient(ctx, userName)
return o.listWorkspacesByKubeClient(ctx, userName, includeShared)
} else {
return o.listWorkspacesWithDashClient(ctx, userName)
return o.listWorkspacesWithDashClient(ctx, userName, includeShared)
}
}

Expand All @@ -156,12 +161,14 @@ func (o *GetOption) listUsersWithDashClient(ctx context.Context) ([]*dashv1alpha
return res.Msg.Items, nil
}

func (o *GetOption) listWorkspacesWithDashClient(ctx context.Context, userName string) ([]*dashv1alpha1.Workspace, error) {
func (o *GetOption) listWorkspacesWithDashClient(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) {
req := &dashv1alpha1.GetWorkspacesRequest{
UserName: userName,
WithRaw: ptr.To(o.OutputFormat == "yaml"),
UserName: userName,
WithRaw: ptr.To(o.OutputFormat == "yaml"),
IncludeShared: ptr.To(includeShared),
}
c := o.CosmoDashClient
o.Logr.DebugAll().Info("WorkspaceServiceClient.GetWorkspaces", "req", req)
res, err := c.WorkspaceServiceClient.GetWorkspaces(ctx, cli.NewRequestWithToken(req, o.CliConfig))
if err != nil {
return nil, fmt.Errorf("failed to connect dashboard server: %w", err)
Expand Down Expand Up @@ -210,37 +217,47 @@ func (o *GetOption) OutputYAML(w io.Writer, objs []*dashv1alpha1.Workspace) {
fmt.Fprintln(w, strings.Join(docs, "---\n"))
}

func OutputTable(out io.Writer, workspaces []*dashv1alpha1.Workspace) {
func OutputTable(out io.Writer, username string, workspaces []*dashv1alpha1.Workspace) {
data := [][]string{}

for _, v := range workspaces {
data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, v.Status.Phase, v.Status.MainUrl})
mainURL := v.Status.MainUrl
if username != "" && v.OwnerName != username {
mainURL = "[shared workspace. see shared URLs by `cosmoctl ws get-network`]"
}
data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, v.Status.Phase, mainURL})
}

cli.OutputTable(out,
[]string{"USER", "NAME", "TEMPLATE", "PHASE", "MAINURL"},
data)
}

func OutputWideTable(out io.Writer, workspaces []*dashv1alpha1.Workspace) {
func OutputWideTable(out io.Writer, username string, workspaces []*dashv1alpha1.Workspace) {
data := [][]string{}

for _, v := range workspaces {
mainURL := v.Status.MainUrl
if v.OwnerName != username {
mainURL = `[shared workspace. see shared URLs by "cosmoctl ws get-network"]`
}
vars := make([]string, 0, len(v.Spec.Vars))
for k, vv := range v.Spec.Vars {
vars = append(vars, fmt.Sprintf("%s=%s", k, vv))
}
data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, strings.Join(vars, ","), v.Status.Phase, v.Status.MainUrl})
data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, strings.Join(vars, ","), v.Status.Phase, mainURL})
}

cli.OutputTable(out,
[]string{"USER", "NAME", "TEMPLATE", "VARS", "PHASE", "MAINURL"},
data)
}

func (o *GetOption) listWorkspacesByKubeClient(ctx context.Context, userName string) ([]*dashv1alpha1.Workspace, error) {
func (o *GetOption) listWorkspacesByKubeClient(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) {
c := o.KosmoClient
workspaces, err := c.ListWorkspacesByUserName(ctx, userName)
workspaces, err := c.ListWorkspacesByUserName(ctx, userName, func(o *kosmo.ListWorkspacesOptions) {
o.IncludeShared = includeShared
})
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions internal/cmd/workspace/get_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"strconv"
"strings"
"time"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -109,11 +110,11 @@ func (o *GetNetworkOption) OutputTable(w io.Writer, workspace *dashv1alpha1.Work
data := [][]string{}

for _, v := range workspace.Spec.Network {
data = append(data, []string{fmt.Sprintf("%d", v.PortNumber), v.CustomHostPrefix, v.HttpPath, strconv.FormatBool(v.Public), v.Url})
data = append(data, []string{fmt.Sprintf("%d", v.PortNumber), v.CustomHostPrefix, v.HttpPath, strconv.FormatBool(v.Public), strings.Join(v.AllowedUsers, ","), v.Url})
}

cli.OutputTable(w,
[]string{"PORT", "CUSTOM_HOST_PREFIX", "HTTP_PATH", "PUBLIC", "URL"},
[]string{"PORT", "CUSTOM_HOST_PREFIX", "HTTP_PATH", "PUBLIC", "ALLOWED_USERS", "URL"},
data)
}

Expand Down
3 changes: 2 additions & 1 deletion internal/cmd/workspace/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (o *UpdateOption) RunE(cmd *cobra.Command, args []string) error {
}

fmt.Fprintln(cmd.OutOrStdout(), color.GreenString("Successfully updated workspace %s", o.WorkspaceName))
OutputTable(cmd.OutOrStdout(), []*dashv1alpha1.Workspace{ws})
OutputTable(cmd.OutOrStdout(), o.UserName, []*dashv1alpha1.Workspace{ws})

return nil
}
Expand Down Expand Up @@ -171,6 +171,7 @@ func (o *UpdateOption) GetWorkspaceWithDashClient(ctx context.Context) (*dashv1a
UserName: o.UserName,
}
c := o.CosmoDashClient
o.Logr.DebugAll().Info("WorkspaceServiceClient.GetWorkspace", "req", req)
res, err := c.WorkspaceServiceClient.GetWorkspace(ctx, cli.NewRequestWithToken(req, o.CliConfig))
if err != nil {
return nil, fmt.Errorf("failed to connect dashboard server: %w", err)
Expand Down
Loading
Loading