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

Feature to change delete policy on Dashboard UI #849

Merged
merged 3 commits into from
Jul 21, 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
41 changes: 41 additions & 0 deletions api/v1alpha1/utils.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
package v1alpha1

import (
"fmt"
"reflect"
"slices"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

// LabelControllerManaged is a label on all resources managed by the controllers
const LabelControllerManaged = "cosmo-workspace.github.io/controller-managed"

Expand Down Expand Up @@ -37,6 +49,35 @@ func KeepResourceDeletePolicy(obj AnnotationHolder) bool {
return v == ResourceAnnEnumDeletePolicyKeep
}

func SetOwnerReferenceIfNotKeepPolicy(owner metav1.Object, obj metav1.Object, scheme *runtime.Scheme) error {
if !KeepResourceDeletePolicy(owner) && !KeepResourceDeletePolicy(obj) {
// Set owner reference
err := ctrl.SetControllerReference(owner, obj, scheme)
if err != nil {
return fmt.Errorf("failed to set owner reference on %s: %w", obj.(runtime.Object).GetObjectKind().GroupVersionKind(), err)
}
return nil

} else {
// Remove owner reference
if len(obj.GetOwnerReferences()) > 0 {
gvk, _ := apiutil.GVKForObject(owner.(runtime.Object), scheme)
refs := slices.DeleteFunc(obj.GetOwnerReferences(), func(v metav1.OwnerReference) bool {
return reflect.DeepEqual(v, metav1.OwnerReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: owner.GetName(),
UID: owner.GetUID(),
Controller: ptr.To(true),
BlockOwnerDeletion: ptr.To(true),
})
})
obj.SetOwnerReferences(refs)
}
return nil
}
}

const (
EventAnnKeyUserName = "cosmo-workspace.github.io/user"
EventAnnKeyInstanceName = LabelKeyInstanceName
Expand Down
12 changes: 10 additions & 2 deletions internal/cmd/user/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ func printAddonWithVars(addons []*dashv1alpha1.UserAddon) string {
return strings.Join(arr, " ")
}

func printDeletePolicy(deletePolicy *dashv1alpha1.DeletePolicy) string {
if deletePolicy != nil {
return deletePolicy.String()
} else {
return dashv1alpha1.DeletePolicy_delete.String()
}
}

func OutputTable(out io.Writer, users []*dashv1alpha1.User) {
data := [][]string{}

Expand All @@ -242,11 +250,11 @@ func OutputWideTable(out io.Writer, users []*dashv1alpha1.User) {
data := [][]string{}

for _, v := range users {
data = append(data, []string{v.Name, v.DisplayName, strings.Join(v.Roles, ","), v.AuthType, cosmov1alpha1.UserNamespace(v.Name), v.Status, printAddonWithVars(v.Addons)})
data = append(data, []string{v.Name, v.DisplayName, strings.Join(v.Roles, ","), v.AuthType, cosmov1alpha1.UserNamespace(v.Name), v.Status, printDeletePolicy(v.DeletePolicy), printAddonWithVars(v.Addons)})
}

cli.OutputTable(out,
[]string{"NAME", "DISPLAYNAME", "ROLES", "AUTHTYPE", "NAMESPACE", "PHASE", "ADDONS"},
[]string{"NAME", "DISPLAYNAME", "ROLES", "AUTHTYPE", "NAMESPACE", "PHASE", "DELETEPOLOCY", "ADDONS"},
data)
}

Expand Down
5 changes: 5 additions & 0 deletions internal/cmd/user/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ func UpdateCmd(cmd *cobra.Command, o *cli.RootOptions) *cobra.Command {
Aliases: []string{"addon", "useraddon", "user-addon"},
Short: "Update addon",
}, o))
cmd.AddCommand(UpdateDeletePolicyCmd(&cobra.Command{
Use: "deletepolicy USER_NAME [delete|keep]",
Aliases: []string{"delete-policy"},
Short: "Update delete polocy",
}, o))
return cmd
}
122 changes: 122 additions & 0 deletions internal/cmd/user/update_deletepolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package user

import (
"context"
"errors"
"fmt"
"time"

"github.com/fatih/color"
"github.com/spf13/cobra"
"k8s.io/utils/ptr"

"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"
)

type UpdateDeletePolicyOption struct {
*cli.RootOptions

UserName string
DeletePolicy string
}

func UpdateDeletePolicyCmd(cmd *cobra.Command, cliOpt *cli.RootOptions) *cobra.Command {
o := &UpdateDeletePolicyOption{RootOptions: cliOpt}
cmd.RunE = cli.ConnectErrorHandler(o)
return cmd
}

func (o *UpdateDeletePolicyOption) Validate(cmd *cobra.Command, args []string) error {
if err := o.RootOptions.Validate(cmd, args); err != nil {
return err
}
if len(args) < 2 {
return errors.New("invalid args")
}
return nil
}

func (o *UpdateDeletePolicyOption) Complete(cmd *cobra.Command, args []string) error {
if err := o.RootOptions.Complete(cmd, args); err != nil {
return err
}

o.UserName = args[0]
o.DeletePolicy = args[1]

cmd.SilenceErrors = true
cmd.SilenceUsage = true
return nil
}

func (o *UpdateDeletePolicyOption) RunE(cmd *cobra.Command, args []string) error {
if err := o.Validate(cmd, args); err != nil {
return fmt.Errorf("validation error: %w", err)
}
if err := o.Complete(cmd, args); err != nil {
return fmt.Errorf("invalid options: %w", err)
}

ctx, cancel := context.WithTimeout(o.Ctx, time.Second*10)
defer cancel()
ctx = clog.IntoContext(ctx, o.Logr)

o.Logr.Info("updating user delete polocy", "userName", o.UserName, "deletepolicy", o.DeletePolicy)

var (
user *dashv1alpha1.User
err error
)
if o.UseKubeAPI {
user, err = o.UpdateUserDeletePolicyWithKubeClient(ctx)
} else {
user, err = o.UpdateUserDeletePolicyWithDashClient(ctx)
}
if err != nil {
return err
}
fmt.Fprintln(cmd.OutOrStdout(), color.GreenString("Successfully updated user %s", o.UserName))
OutputWideTable(cmd.OutOrStdout(), []*dashv1alpha1.User{user})

return nil
}

func (o *UpdateDeletePolicyOption) UpdateUserDeletePolicyWithDashClient(ctx context.Context) (*dashv1alpha1.User, error) {
delPolicy := apiconv.C2D_DeletePolicy(o.DeletePolicy)
if delPolicy == nil {
delPolicy = ptr.To(dashv1alpha1.DeletePolicy_delete)
}

req := &dashv1alpha1.UpdateUserDeletePolicyRequest{
UserName: o.UserName,
DeletePolicy: *delPolicy,
}
o.Logr.DebugAll().Info("UserServiceClient.UpdateUserDeletePolicy", "req", req)
c := o.CosmoDashClient
res, err := c.UserServiceClient.UpdateUserDeletePolicy(ctx, cli.NewRequestWithToken(req, o.CliConfig))
if err != nil {
return nil, fmt.Errorf("failed to connect dashboard server: %w", err)
}
o.Logr.DebugAll().Info("UserServiceClient.UpdateUserDeletePolicy", "res", res)

return res.Msg.User, nil
}

func (o *UpdateDeletePolicyOption) UpdateUserDeletePolicyWithKubeClient(ctx context.Context) (*dashv1alpha1.User, error) {
c := o.KosmoClient
opts := kosmo.UpdateUserOpts{
DeletePolicy: &o.DeletePolicy,
}
o.Logr.DebugAll().Info("UpdateUser", "opts", opts)
user, err := c.UpdateUser(ctx, o.UserName, opts)
if err != nil {
return nil, err
}
d := apiconv.C2D_User(*user)

return d, nil
}
5 changes: 2 additions & 3 deletions internal/cmd/user/update_display_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ type UpdateDisplayNameOption struct {
func UpdateDisplayNameCmd(cmd *cobra.Command, cliOpt *cli.RootOptions) *cobra.Command {
o := &UpdateDisplayNameOption{RootOptions: cliOpt}
cmd.RunE = cli.ConnectErrorHandler(o)
cmd.Flags().StringVar(&o.DisplayName, "display-name", "", "user display name (Required)")
cmd.MarkFlagRequired("display-name")
cmd.Flags().BoolVar(&o.Force, "force", false, "not ask confirmation")
return cmd
}
Expand All @@ -39,7 +37,7 @@ func (o *UpdateDisplayNameOption) Validate(cmd *cobra.Command, args []string) er
if err := o.RootOptions.Validate(cmd, args); err != nil {
return err
}
if len(args) < 1 {
if len(args) < 2 {
return errors.New("invalid args")
}
return nil
Expand All @@ -51,6 +49,7 @@ func (o *UpdateDisplayNameOption) Complete(cmd *cobra.Command, args []string) er
}

o.UserName = args[0]
o.DisplayName = args[1]

cmd.SilenceErrors = true
cmd.SilenceUsage = true
Expand Down
36 changes: 26 additions & 10 deletions internal/cmd/workspace/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,22 +237,38 @@ func OutputWideTable(out io.Writer, username string, workspaces []*dashv1alpha1.
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, mainURL})
data = append(data, []string{v.OwnerName, v.Name, v.Spec.Template, printVars(v.Spec.Vars), v.Status.Phase, printDeletePolicy(v.DeletePolicy), printMainURL(v, username)})
}

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

func printMainURL(v *dashv1alpha1.Workspace, username string) string {
mainURL := v.Status.MainUrl
if v.OwnerName != username {
mainURL = `[shared workspace. see shared URLs by "cosmoctl ws get-network"]`
}
return mainURL
}

func printVars(vars map[string]string) string {
varSlice := make([]string, 0, len(vars))
for k, vv := range vars {
varSlice = append(varSlice, fmt.Sprintf("%s=%s", k, vv))
}
return strings.Join(varSlice, ",")
}

func printDeletePolicy(deletePolicy *dashv1alpha1.DeletePolicy) string {
if deletePolicy != nil {
return deletePolicy.String()
} else {
return dashv1alpha1.DeletePolicy_delete.String()
}
}

func (o *GetOption) listWorkspacesByKubeClient(ctx context.Context, userName string, includeShared bool) ([]*dashv1alpha1.Workspace, error) {
c := o.KosmoClient
workspaces, err := c.ListWorkspacesByUserName(ctx, userName, func(o *kosmo.ListWorkspacesOptions) {
Expand Down
22 changes: 22 additions & 0 deletions internal/cmd/workspace/suspend.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/fatih/color"
Expand All @@ -21,6 +22,7 @@ type SuspendOption struct {

WorkspaceNames []string
UserName string
Force bool
}

func SuspendCmd(cmd *cobra.Command, cliOpt *cli.RootOptions) *cobra.Command {
Expand All @@ -29,6 +31,7 @@ func SuspendCmd(cmd *cobra.Command, cliOpt *cli.RootOptions) *cobra.Command {
cmd.RunE = cli.ConnectErrorHandler(o)

cmd.Flags().StringVarP(&o.UserName, "user", "u", "", "user name (defualt: login user)")
cmd.Flags().BoolVar(&o.Force, "force", false, "not ask confirmation")

return cmd
}
Expand Down Expand Up @@ -69,6 +72,25 @@ func (o *SuspendOption) RunE(cmd *cobra.Command, args []string) error {
return fmt.Errorf("invalid options: %w", err)
}

o.Logr.Info("suspend workspaces", "workspaces", o.WorkspaceNames)

if !o.Force {
AskLoop:
for {
input, err := cli.AskInput("Confirm? [y/n] ", false)
if err != nil {
return err
}
switch strings.ToLower(input) {
case "y":
break AskLoop
case "n":
fmt.Println("canceled")
return nil
}
}
}

ctx, cancel := context.WithTimeout(o.Ctx, time.Second*10)
defer cancel()
ctx = clog.IntoContext(ctx, o.Logr)
Expand Down
Loading
Loading