From d7a9612e100bc5116554146563fcb5b36b005980 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 22 Sep 2023 13:30:15 +0200 Subject: [PATCH] Added UI command Signed-off-by: Thomas Kosiewski --- cmd/vclusterctl/cmd/root.go | 6 ++ cmd/vclusterctl/cmd/ui.go | 55 ++++++++++++++ go.mod | 2 +- go.sum | 2 + .../v3/cmd/loftctl/cmd/create/space.go | 2 +- .../v3/cmd/loftctl/cmd/create/vcluster.go | 52 ++++++++++--- .../loftctl/v3/cmd/loftctl/cmd/root.go | 1 + .../loft-sh/loftctl/v3/cmd/loftctl/cmd/ui.go | 76 +++++++++++++++++++ .../loftctl/v3/pkg/client/helper/helper.go | 4 +- vendor/modules.txt | 2 +- 10 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 cmd/vclusterctl/cmd/ui.go create mode 100644 vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/ui.go diff --git a/cmd/vclusterctl/cmd/root.go b/cmd/vclusterctl/cmd/root.go index 11ff5a5fa7..3b99f3e451 100644 --- a/cmd/vclusterctl/cmd/root.go +++ b/cmd/vclusterctl/cmd/root.go @@ -103,6 +103,12 @@ func BuildRoot(log log.Logger) (*cobra.Command, error) { } rootCmd.AddCommand(logoutCmd) + uiCmd, err := NewUICmd(globalFlags) + if err != nil { + return nil, fmt.Errorf("failed to create ui command: %w", err) + } + rootCmd.AddCommand(uiCmd) + // add completion command err = rootCmd.RegisterFlagCompletionFunc("namespace", newNamespaceCompletionFunc(rootCmd.Context())) if err != nil { diff --git a/cmd/vclusterctl/cmd/ui.go b/cmd/vclusterctl/cmd/ui.go new file mode 100644 index 0000000000..049afdbc17 --- /dev/null +++ b/cmd/vclusterctl/cmd/ui.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "errors" + "fmt" + + loftctl "github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd" + "github.com/loft-sh/log" + "github.com/loft-sh/vcluster/cmd/vclusterctl/flags" + "github.com/loft-sh/vcluster/pkg/pro" + "github.com/spf13/cobra" +) + +func NewUICmd(globalFlags *flags.GlobalFlags) (*cobra.Command, error) { + loftctlGlobalFlags, err := pro.GlobalFlags(globalFlags) + if err != nil { + return nil, fmt.Errorf("failed to parse pro flags: %w", err) + } + + cmd := &loftctl.UiCmd{ + GlobalFlags: loftctlGlobalFlags, + Log: log.GetInstance(), + } + + description := `######################################################## +##################### vcluster ui ###################### +######################################################## +Open the vCluster.Pro web UI + +Example: +vcluster ui +######################################################## + ` + + uiCmd := &cobra.Command{ + Use: "ui", + Short: "Start the web UI", + Long: description, + Args: cobra.NoArgs, + RunE: func(cobraCmd *cobra.Command, args []string) error { + err := cmd.Run(cobraCmd.Context(), args) + if err != nil { + if errors.Is(err, loftctl.ErrNoUrl) { + return fmt.Errorf("%w: please login first using 'vcluster login' or start using 'vcluster pro start'", err) + } + + return fmt.Errorf("failed to run ui command: %w", err) + } + + return nil + }, + } + + return uiCmd, nil +} diff --git a/go.mod b/go.mod index ddaacad0f4..e669f4bac8 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.2 github.com/kubernetes-csi/external-snapshotter/client/v4 v4.2.0 github.com/loft-sh/api/v3 v3.0.0-20230922094800-6d0c1cbf0fa6 - github.com/loft-sh/loftctl/v3 v3.0.0-20230922094952-2a6aef29f31e + github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7 github.com/loft-sh/utils v0.0.25 github.com/mitchellh/go-homedir v1.1.0 github.com/moby/term v0.5.0 diff --git a/go.sum b/go.sum index c31876880f..4885ab2a7f 100644 --- a/go.sum +++ b/go.sum @@ -564,6 +564,8 @@ github.com/loft-sh/loftctl/v3 v3.0.0-20230921143437-669b265e3ecf h1:SwIal5P1coM0 github.com/loft-sh/loftctl/v3 v3.0.0-20230921143437-669b265e3ecf/go.mod h1:I+dMG4zKZWOn+CcteN0cyPGOclHlqqmXBC6THcUwj60= github.com/loft-sh/loftctl/v3 v3.0.0-20230922094952-2a6aef29f31e h1:OhQRp9uH7U14lYg8yOrnOiVhhxxC9tFdwWSyxoSUJ/I= github.com/loft-sh/loftctl/v3 v3.0.0-20230922094952-2a6aef29f31e/go.mod h1:g2HZHmpkqtiQGqM8QVRuaCgNDI6IMW6DZQP0cOUgxp8= +github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7 h1:lsBF4llFeHcDvYj2TCFVNsdYAPemIXXg7v0w2A6txPw= +github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7/go.mod h1:g2HZHmpkqtiQGqM8QVRuaCgNDI6IMW6DZQP0cOUgxp8= github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a h1:/gqqjKpcHEdFXIX41lx1Y/FBqT/72gbPpf7sa20tyM8= github.com/loft-sh/log v0.0.0-20230824104949-bd516c25712a/go.mod h1:YImeRjXH34Yf5E79T7UHBQpDZl9fIaaFRgyZ/bkY+UQ= github.com/loft-sh/utils v0.0.25 h1:JbbRJfXO1Rd34fQcaoDSmwyPBEzsrLwBSR21C90hHuk= diff --git a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/space.go b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/space.go index 35b5f2950b..4cda199f83 100644 --- a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/space.go +++ b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/space.go @@ -154,7 +154,7 @@ func (cmd *SpaceCmd) Run(ctx context.Context, args []string) error { } // determine cluster name - cmd.Cluster, cmd.Project, err = helper.SelectProjectOrCluster(baseClient, cmd.Cluster, cmd.Project, cmd.Log) + cmd.Cluster, cmd.Project, err = helper.SelectProjectOrCluster(baseClient, cmd.Cluster, cmd.Project, true, cmd.Log) if err != nil { return err } diff --git a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/vcluster.go b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/vcluster.go index d58ba888f8..612355d89e 100644 --- a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/vcluster.go +++ b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/create/vcluster.go @@ -168,7 +168,7 @@ func (cmd *VirtualClusterCmd) Run(ctx context.Context, args []string) error { } // determine cluster name - cmd.Cluster, cmd.Project, err = helper.SelectProjectOrCluster(baseClient, cmd.Cluster, cmd.Project, cmd.Log) + cmd.Cluster, cmd.Project, err = helper.SelectProjectOrCluster(baseClient, cmd.Cluster, cmd.Project, true, cmd.Log) if err != nil { return err } @@ -217,9 +217,8 @@ func (cmd *VirtualClusterCmd) createVirtualCluster(ctx context.Context, baseClie } } - var virtualClusterInstance *managementv1.VirtualClusterInstance - // make sure there is not existing virtual cluster + var virtualClusterInstance *managementv1.VirtualClusterInstance virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterNamespace).Get(ctx, virtualClusterName, metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return fmt.Errorf("couldn't retrieve virtual cluster instance: %w", err) @@ -254,7 +253,15 @@ func (cmd *VirtualClusterCmd) createVirtualCluster(ctx context.Context, baseClie // create virtual cluster if necessary if virtualClusterInstance == nil { // resolve template - virtualClusterTemplate, resolvedParameters, err := cmd.resolveTemplate(baseClient) + virtualClusterTemplate, resolvedParameters, err := ResolveTemplate( + baseClient, + cmd.Project, + cmd.Template, + cmd.Version, + cmd.Set, + cmd.ParametersFile, + cmd.Log, + ) if err != nil { return err } @@ -282,13 +289,18 @@ func (cmd *VirtualClusterCmd) createVirtualCluster(ctx context.Context, baseClie Version: cmd.Version, }, ClusterRef: storagev1.VirtualClusterClusterRef{ - ClusterRef: storagev1.ClusterRef{Cluster: cmd.Cluster}, + ClusterRef: storagev1.ClusterRef{ + Cluster: cmd.Cluster, + }, }, Parameters: resolvedParameters, }, }, } + + // set links SetCustomLinksAnnotation(virtualClusterInstance, cmd.Links) + // create virtualclusterinstance cmd.Log.Infof("Creating virtual cluster %s in project %s with template %s...", ansi.Color(virtualClusterName, "white+b"), ansi.Color(cmd.Project, "white+b"), ansi.Color(virtualClusterTemplate.Name, "white+b")) virtualClusterInstance, err = managementClient.Loft().ManagementV1().VirtualClusterInstances(virtualClusterInstance.Namespace).Create(ctx, virtualClusterInstance, metav1.CreateOptions{}) @@ -297,7 +309,15 @@ func (cmd *VirtualClusterCmd) createVirtualCluster(ctx context.Context, baseClie } } else if cmd.Update { // resolve template - virtualClusterTemplate, resolvedParameters, err := cmd.resolveTemplate(baseClient) + virtualClusterTemplate, resolvedParameters, err := ResolveTemplate( + baseClient, + cmd.Project, + cmd.Template, + cmd.Version, + cmd.Set, + cmd.ParametersFile, + cmd.Log, + ) if err != nil { return err } @@ -361,9 +381,17 @@ func (cmd *VirtualClusterCmd) createVirtualCluster(ctx context.Context, baseClie return nil } -func (cmd *VirtualClusterCmd) resolveTemplate(baseClient client.Client) (*managementv1.VirtualClusterTemplate, string, error) { +func ResolveTemplate( + baseClient client.Client, + project, + template, + templateVersion string, + setParams []string, + fileParams string, + log log.Logger, +) (*managementv1.VirtualClusterTemplate, string, error) { // determine space template to use - virtualClusterTemplate, err := helper.SelectVirtualClusterTemplate(baseClient, cmd.Project, cmd.Template, cmd.Log) + virtualClusterTemplate, err := helper.SelectVirtualClusterTemplate(baseClient, project, template, log) if err != nil { return nil, "", err } @@ -371,7 +399,7 @@ func (cmd *VirtualClusterCmd) resolveTemplate(baseClient client.Client) (*manage // get parameters var templateParameters []storagev1.AppParameter if len(virtualClusterTemplate.Spec.Versions) > 0 { - if cmd.Version == "" { + if templateVersion == "" { latestVersion := version.GetLatestVersion(virtualClusterTemplate) if latestVersion == nil { return nil, "", fmt.Errorf("couldn't find any version in template") @@ -379,11 +407,11 @@ func (cmd *VirtualClusterCmd) resolveTemplate(baseClient client.Client) (*manage templateParameters = latestVersion.(*storagev1.VirtualClusterTemplateVersion).Parameters } else { - _, latestMatched, err := version.GetLatestMatchedVersion(virtualClusterTemplate, cmd.Version) + _, latestMatched, err := version.GetLatestMatchedVersion(virtualClusterTemplate, templateVersion) if err != nil { return nil, "", err } else if latestMatched == nil { - return nil, "", fmt.Errorf("couldn't find any matching version to %s", cmd.Version) + return nil, "", fmt.Errorf("couldn't find any matching version to %s", templateVersion) } templateParameters = latestMatched.(*storagev1.VirtualClusterTemplateVersion).Parameters @@ -393,7 +421,7 @@ func (cmd *VirtualClusterCmd) resolveTemplate(baseClient client.Client) (*manage } // resolve space template parameters - resolvedParameters, err := parameters.ResolveTemplateParameters(cmd.Set, templateParameters, cmd.ParametersFile) + resolvedParameters, err := parameters.ResolveTemplateParameters(setParams, templateParameters, fileParams) if err != nil { return nil, "", err } diff --git a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/root.go b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/root.go index 3656d4e324..b30c35de51 100644 --- a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/root.go +++ b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/root.go @@ -95,6 +95,7 @@ func BuildRoot(log *log.StreamLogger) *cobra.Command { rootCmd.AddCommand(NewStartCmd(globalFlags)) rootCmd.AddCommand(NewLoginCmd(globalFlags)) rootCmd.AddCommand(NewLogoutCmd(globalFlags)) + rootCmd.AddCommand(NewUiCmd(globalFlags)) rootCmd.AddCommand(NewTokenCmd(globalFlags)) rootCmd.AddCommand(NewBackupCmd(globalFlags)) rootCmd.AddCommand(NewCompletionCmd(rootCmd, globalFlags)) diff --git a/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/ui.go b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/ui.go new file mode 100644 index 0000000000..8c5ee9ca7f --- /dev/null +++ b/vendor/github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/ui.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/loft-sh/api/v3/pkg/product" + "github.com/loft-sh/loftctl/v3/cmd/loftctl/flags" + "github.com/loft-sh/loftctl/v3/pkg/client" + "github.com/loft-sh/log" + "github.com/skratchdot/open-golang/open" + "github.com/spf13/cobra" +) + +var ( + ErrNoUrl = errors.New("no url found") +) + +// UiCmd holds the ui cmd flags +type UiCmd struct { + *flags.GlobalFlags + + Log log.Logger +} + +func NewUiCmd(globalFlags *flags.GlobalFlags) *cobra.Command { + cmd := &UiCmd{ + GlobalFlags: globalFlags, + Log: log.GetInstance(), + } + + description := product.ReplaceWithHeader("ui", ` +Open the loft management UI in the browser + +Example: +loft ui +######################################################## + `) + + uiCmd := &cobra.Command{ + Use: "ui", + Short: product.Replace("Open the loft management UI in the browser"), + Long: description, + Args: cobra.NoArgs, + RunE: func(cobraCmd *cobra.Command, args []string) error { + return cmd.Run(cobraCmd.Context(), args) + }, + } + + return uiCmd +} + +func (cmd *UiCmd) Run(ctx context.Context, args []string) error { + loader, err := client.NewClientFromPath(cmd.Config) + if err != nil { + return err + } + + url := os.Getenv(LoftUrl) + if url == "" { + url = loader.Config().Host + } + + if url == "" { + return fmt.Errorf("%w: please login first using 'loft login' or start using 'loft start'", ErrNoUrl) + } + + err = open.Run(url) + if err != nil { + return fmt.Errorf("error opening url: %w", err) + } + + return nil +} diff --git a/vendor/github.com/loft-sh/loftctl/v3/pkg/client/helper/helper.go b/vendor/github.com/loft-sh/loftctl/v3/pkg/client/helper/helper.go index fb53ebfe83..5b98e5bd08 100644 --- a/vendor/github.com/loft-sh/loftctl/v3/pkg/client/helper/helper.go +++ b/vendor/github.com/loft-sh/loftctl/v3/pkg/client/helper/helper.go @@ -363,10 +363,10 @@ func SelectSpaceInstanceOrSpace(baseClient client.Client, spaceName, projectName return "", "", "", fmt.Errorf("couldn't find answer") } -func SelectProjectOrCluster(baseClient client.Client, clusterName, projectName string, log log.Logger) (cluster string, project string, err error) { +func SelectProjectOrCluster(baseClient client.Client, clusterName, projectName string, allowClusterOnly bool, log log.Logger) (cluster string, project string, err error) { if projectName != "" { return clusterName, projectName, nil - } else if clusterName != "" { + } else if allowClusterOnly && clusterName != "" { return clusterName, "", nil } diff --git a/vendor/modules.txt b/vendor/modules.txt index d03b8f4d1a..514bfd095d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -340,7 +340,7 @@ github.com/loft-sh/external-types/loft-sh/admin-services/pkg/server # github.com/loft-sh/jspolicy v0.1.0 ## explicit; go 1.16 github.com/loft-sh/jspolicy/pkg/apis/policy/v1beta1 -# github.com/loft-sh/loftctl/v3 v3.0.0-20230922094952-2a6aef29f31e +# github.com/loft-sh/loftctl/v3 v3.0.0-20230922112452-20febcea05a7 ## explicit; go 1.21.1 github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd github.com/loft-sh/loftctl/v3/cmd/loftctl/cmd/connect