This is an automated email from the ASF dual-hosted git repository.

zhongxjian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new 9a2a13cf [dubboctl] Recovering from missing git records
9a2a13cf is described below

commit 9a2a13cfd528bc85d02214486bf6f1ca220aa34d
Author: mfordjody <[email protected]>
AuthorDate: Tue Oct 29 21:01:19 2024 +0800

    [dubboctl] Recovering from missing git records
---
 dubboctl/cmd/build.go           | 220 +++++++++++++++
 dubboctl/cmd/client.go          |  87 ++++++
 dubboctl/cmd/common.go          |  26 ++
 dubboctl/cmd/completion_util.go |  73 +++++
 dubboctl/cmd/create.go          | 572 ++++++++++++++++++++++++++++++++++++++
 dubboctl/cmd/deploy.go          | 317 +++++++++++++++++++++
 dubboctl/cmd/repository.go      | 596 ++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1891 insertions(+)

diff --git a/dubboctl/cmd/build.go b/dubboctl/cmd/build.go
new file mode 100644
index 00000000..9d6278d5
--- /dev/null
+++ b/dubboctl/cmd/build.go
@@ -0,0 +1,220 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "fmt"
+       "os"
+       "strings"
+)
+
+import (
+       "github.com/AlecAivazis/survey/v2"
+
+       "github.com/ory/viper"
+
+       "github.com/spf13/cobra"
+)
+
+import (
+       
"github.com/apache/dubbo-kubernetes/dubboctl/internal/builders/dockerfile"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/builders/pack"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/util"
+)
+
+func addBuild(baseCmd *cobra.Command, newClient ClientFactory) {
+       cmd := &cobra.Command{
+               Use:        "build",
+               Short:      "Build the image for the application",
+               Long:       ``,
+               SuggestFor: []string{"biuld", "buidl", "built"},
+               PreRunE: bindEnv("useDockerfile", "image", "path", "push", 
"force", "envs",
+                       "builder-image"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runBuildCmd(cmd, newClient)
+               },
+       }
+
+       cmd.Flags().StringP("builder-image", "b", "",
+               "Specify a custom builder image for use by the builder other 
than its default.")
+       cmd.Flags().BoolP("useDockerfile", "d", false,
+               "Use the dockerfile with the specified path to build")
+       cmd.Flags().StringP("image", "i", "",
+               "Container image( [registry]/[namespace]/[name]:[tag] )")
+       cmd.Flags().BoolP("push", "", true,
+               "Whether to push the image to the registry center by the way")
+       cmd.Flags().BoolP("force", "f", false,
+               "Whether to force build")
+       cmd.Flags().StringArrayP("envs", "e", []string{},
+               "environment variable for an application, KEY=VALUE format")
+       addPathFlag(cmd)
+       baseCmd.AddCommand(cmd)
+}
+
+func runBuildCmd(cmd *cobra.Command, newClient ClientFactory) error {
+       if err := util.CreatePaths(); err != nil {
+               return err
+       }
+       cfg := newBuildConfig(cmd)
+       f, err := dubbo.NewDubbo(cfg.Path)
+       if err != nil {
+               return err
+       }
+
+       cfg, err = cfg.Prompt(f)
+       if err != nil {
+               return err
+       }
+       if !f.Initialized() {
+               return dubbo.NewErrNotInitialized(f.Root)
+       }
+       cfg.Configure(f)
+
+       clientOptions, err := cfg.buildclientOptions()
+       if err != nil {
+               return err
+       }
+       client, done := newClient(clientOptions...)
+       defer done()
+       if f.Built() && !cfg.Force {
+               fmt.Fprintln(cmd.OutOrStdout(), "The Application is up to date, 
If you still want to build, use `--force true`")
+               return nil
+       }
+       if f, err = client.Build(cmd.Context(), f); err != nil {
+               return err
+       }
+       if cfg.Push {
+               if f, err = client.Push(cmd.Context(), f); err != nil {
+                       return err
+               }
+       }
+
+       if err = f.Write(); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (c *buildConfig) Prompt(d *dubbo.Dubbo) (*buildConfig, error) {
+       var err error
+       if !util.InteractiveTerminal() {
+               return c, nil
+       }
+
+       if c.Image == "" && d.Image == "" {
+
+               qs := []*survey.Question{
+                       {
+                               Name:     "image",
+                               Validate: survey.Required,
+                               Prompt: &survey.Input{
+                                       Message: "the container image( 
[registry]/[namespace]/[name]:[tag] ). For example: 
docker.io/sjmshsh/testapp:latest",
+                                       Default: c.Image,
+                               },
+                       },
+               }
+               if err = survey.Ask(qs, c); err != nil {
+                       return c, err
+               }
+       }
+       return c, err
+}
+
+func (c buildConfig) buildclientOptions() ([]dubbo.Option, error) {
+       var o []dubbo.Option
+
+       if c.UseDockerfile {
+               o = append(o, dubbo.WithBuilder(dockerfile.NewBuilder()))
+       } else {
+               o = append(o, dubbo.WithBuilder(pack.NewBuilder()))
+       }
+
+       return o, nil
+}
+
+type buildConfig struct {
+       Envs          []string
+       Force         bool
+       UseDockerfile bool
+       // Push the resulting image to the registry after building.
+       Push bool
+       // BuilderImage is the image (name or mapping) to use for building.  
Usually
+       // set automatically.
+       BuilderImage string
+       Image        string
+
+       // Path of the application implementation on local disk. Defaults to 
current
+       // working directory of the process.
+       Path string
+}
+
+func newBuildConfig(cmd *cobra.Command) *buildConfig {
+       c := &buildConfig{
+               Envs:          viper.GetStringSlice("envs"),
+               Force:         viper.GetBool("force"),
+               UseDockerfile: viper.GetBool("useDockerfile"),
+               Push:          viper.GetBool("push"),
+               BuilderImage:  viper.GetString("builder-image"),
+               Image:         viper.GetString("image"),
+               Path:          viper.GetString("path"),
+       }
+
+       var err error
+       if c.Envs, err = cmd.Flags().GetStringArray("envs"); err != nil {
+               fmt.Fprintf(cmd.OutOrStdout(), "error reading envs: %v\n", err)
+       }
+       return c
+}
+
+func (c *buildConfig) Configure(f *dubbo.Dubbo) {
+       if c.Path == "" {
+               root, err := os.Getwd()
+               if err != nil {
+                       return
+               }
+               f.Root = root
+       } else {
+               f.Root = c.Path
+       }
+       if c.BuilderImage != "" {
+               f.Build.BuilderImages["pack"] = c.BuilderImage
+       }
+       if c.Image != "" {
+               f.Image = c.Image
+       }
+
+       if len(c.Envs) > 0 {
+               envs := map[string]string{}
+               for _, env := range f.Build.BuildEnvs {
+                       envs[*env.Name] = *env.Value
+               }
+               for _, pair := range c.Envs {
+                       parts := strings.Split(pair, "=")
+                       if len(parts) == 2 {
+                               envs[parts[0]] = parts[1]
+                       }
+               }
+               f.Build.BuildEnvs = make([]dubbo.Env, 0, len(envs))
+               for k, v := range envs {
+                       f.Build.BuildEnvs = append(f.Build.BuildEnvs, dubbo.Env{
+                               Name:  &k,
+                               Value: &v,
+                       })
+               }
+       }
+}
diff --git a/dubboctl/cmd/client.go b/dubboctl/cmd/client.go
new file mode 100644
index 00000000..71894a8c
--- /dev/null
+++ b/dubboctl/cmd/client.go
@@ -0,0 +1,87 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "net/http"
+       "os"
+)
+
+import (
+       "github.com/apache/dubbo-kubernetes/dubboctl/cmd/prompt"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/builders/pack"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/docker"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/docker/creds"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/dubbo"
+       dubbohttp "github.com/apache/dubbo-kubernetes/dubboctl/internal/http"
+       config "github.com/apache/dubbo-kubernetes/dubboctl/internal/util"
+)
+
+// ClientFactory defines a constructor which assists in the creation of a 
Client
+// for use by commands.
+// See the NewClient constructor which is the fully populated ClientFactory 
used
+// by commands by default.
+// See NewClientFactory which constructs a minimal ClientFactory for use
+// during testing.
+type ClientFactory func(...dubbo.Option) (*dubbo.Client, func())
+
+func NewClient(options ...dubbo.Option) (*dubbo.Client, func()) {
+       var (
+               t = newTransport(false)
+               c = newCredentialsProvider(config.Dir(), t)
+               d = newDubboDeployer()
+               o = []dubbo.Option{
+                       dubbo.WithRepositoriesPath(config.RepositoriesPath()),
+                       dubbo.WithBuilder(pack.NewBuilder()),
+                       dubbo.WithPusher(docker.NewPusher(
+                               docker.WithCredentialsProvider(c),
+                               docker.WithTransport(t))),
+                       dubbo.WithDeployer(d),
+               }
+       )
+       // Client is constructed with standard options plus any additional 
options
+       // which either augment or override the defaults.
+       client := dubbo.New(append(o, options...)...)
+
+       cleanup := func() {}
+       return client, cleanup
+}
+
+func newDubboDeployer() dubbo.Deployer {
+       var options []dubbo.DeployerOpt
+
+       return dubbo.NewDeployer(options...)
+}
+
+// newTransport returns a transport with cluster-flavor-specific variations
+// which take advantage of additional features offered by cluster variants.
+func newTransport(insecureSkipVerify bool) dubbohttp.RoundTripCloser {
+       return 
dubbohttp.NewRoundTripper(dubbohttp.WithInsecureSkipVerify(insecureSkipVerify))
+}
+
+// newCredentialsProvider returns a credentials provider which possibly
+// has cluster-flavor specific additional credential loaders to take advantage
+// of features or configuration nuances of cluster variants.
+func newCredentialsProvider(configPath string, t http.RoundTripper) 
docker.CredentialsProvider {
+       options := []creds.Opt{
+               
creds.WithPromptForCredentials(prompt.NewPromptForCredentials(os.Stdin, 
os.Stdout, os.Stderr)),
+               
creds.WithPromptForCredentialStore(prompt.NewPromptForCredentialStore()),
+               creds.WithTransport(t),
+       }
+
+       // Other cluster variants can be supported here
+       return creds.NewCredentialsProvider(configPath, options...)
+}
diff --git a/dubboctl/cmd/common.go b/dubboctl/cmd/common.go
new file mode 100644
index 00000000..5fa60c67
--- /dev/null
+++ b/dubboctl/cmd/common.go
@@ -0,0 +1,26 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+var (
+       // TestInstallFlag and TestCli are uses for black box testing
+       TestInstallFlag bool
+       TestCli         client.Client
+)
diff --git a/dubboctl/cmd/completion_util.go b/dubboctl/cmd/completion_util.go
new file mode 100644
index 00000000..96f4d8c6
--- /dev/null
+++ b/dubboctl/cmd/completion_util.go
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "fmt"
+       "os"
+       "strings"
+)
+
+import (
+       "github.com/spf13/cobra"
+)
+
+import (
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/dubbo"
+)
+
+func CompleteRuntimeList(cmd *cobra.Command, args []string, toComplete string, 
client *dubbo.Client) (matches []string, directive cobra.ShellCompDirective) {
+       runtimes, err := client.Runtimes()
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "error listing runtimes for flag 
completion: %v\n", err)
+               return
+       }
+       for _, runtime := range runtimes {
+               if strings.HasPrefix(runtime, toComplete) {
+                       matches = append(matches, runtime)
+               }
+       }
+       return
+}
+
+func CompleteTemplateList(cmd *cobra.Command, args []string, toComplete 
string, client *dubbo.Client) (matches []string, directive 
cobra.ShellCompDirective) {
+       directive = cobra.ShellCompDirectiveError
+
+       lang, err := cmd.Flags().GetString("language")
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "cannot list templates: %v\n", err)
+               return
+       }
+       if lang == "" {
+               fmt.Fprintln(os.Stderr, "cannot list templates: language not 
specified")
+               return
+       }
+
+       templates, err := client.Templates().List(lang)
+       if err != nil {
+               fmt.Fprintf(os.Stderr, "cannot list templates: %v\n", err)
+               return
+       }
+
+       directive = cobra.ShellCompDirectiveDefault
+       for _, t := range templates {
+               if strings.HasPrefix(t, toComplete) {
+                       matches = append(matches, t)
+               }
+       }
+
+       return
+}
diff --git a/dubboctl/cmd/create.go b/dubboctl/cmd/create.go
new file mode 100644
index 00000000..b3ff7387
--- /dev/null
+++ b/dubboctl/cmd/create.go
@@ -0,0 +1,572 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "os"
+       "strings"
+       "text/tabwriter"
+       "text/template"
+)
+
+import (
+       "github.com/AlecAivazis/survey/v2"
+
+       "github.com/ory/viper"
+
+       "github.com/spf13/cobra"
+)
+
+import (
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/util"
+)
+
+// ErrNoRuntime indicates that the language runtime flag was not passed.
+type ErrNoRuntime error
+
+// ErrInvalidRuntime indicates that the passed language runtime was invalid.
+type ErrInvalidRuntime error
+
+// ErrInvalidTemplate indicates that the passed template was invalid.
+type ErrInvalidTemplate error
+
+// NewCreateCmd creates a create command using the given client creator.
+func addCreate(baseCmd *cobra.Command, newClient ClientFactory) {
+       cmd := &cobra.Command{
+               Use:   "create",
+               Short: "Create an application",
+               Long: `
+NAME
+       {{.Name}} create - Create an application
+
+SYNOPSIS
+       {{.Name}} create [-l|--language] [-t|--template] [-r|--repository]
+                   [-c|--confirm] [path]
+
+DESCRIPTION
+       Creates a new application.
+
+         $ {{.Name}} create -l go
+
+       Creates a function in the current directory '.' which is written in the
+       language/runtime 'go' common .
+
+       If [path] is provided, the function is initialized at that path, 
creating
+       the path if necessary.
+
+       To complete this command interactively, use --confirm (-c):
+         $ {{.Name}} create -c
+
+       Initialize the current project directly into a dubbo project without 
using a template
+      $ dubboctl create --init
+
+       Available Language Runtimes and Templates:
+{{ .Options | indent 2 " " | indent 1 "\t" }}
+
+       To install more language runtimes and their templates see '{{.Name}} 
repository'.
+
+
+EXAMPLES
+       o Create a Node.js function in the current directory (the default path) 
which
+         handles http events (the default template).
+         $ {{.Name}} create -l java
+
+       o Create a java common in the directory 'mydubbo'.
+         $ {{.Name}} create -l java mydubbo
+
+       o Create a Main common in ./mydubbo.
+         $ {{.Name}} create -l go -t common mydubbo
+               `,
+               SuggestFor: []string{"vreate", "creaet", "craete", "new"},
+               PreRunE:    bindEnv("language", "template", "repository", 
"confirm", "init"),
+               Aliases:    []string{"init"},
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runCreate(cmd, args, newClient)
+               },
+       }
+
+       // Flags
+       cmd.Flags().StringP("language", "l", "", "Language Runtime (see help 
text for list) ($DUBBO_LANGUAGE)")
+       cmd.Flags().StringP("template", "t", "", "Application template. (see 
help text for list) ($DUBBO_TEMPLATE)")
+       cmd.Flags().StringP("repository", "r", "", "URI to a Git repository 
containing the specified template ($DUBBO_REPOSITORY)")
+       cmd.Flags().BoolP("init", "i", false,
+               "Initialize the current project directly into a dubbo project 
without using a template")
+
+       addConfirmFlag(cmd, false)
+
+       // Help Action
+       cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { 
runCreateHelp(cmd, args, newClient) })
+
+       // Tab completion
+       if err := cmd.RegisterFlagCompletionFunc("language", 
newRuntimeCompletionFunc(newClient)); err != nil {
+               fmt.Fprintf(os.Stderr, "unable to provide language runtime 
suggestions: %v\n", err)
+       }
+       if err := cmd.RegisterFlagCompletionFunc("template", 
newTemplateCompletionFunc(newClient)); err != nil {
+               fmt.Fprintf(os.Stderr, "unable to provide template suggestions: 
%v\n", err)
+       }
+
+       baseCmd.AddCommand(cmd)
+}
+
+// Run Create
+func runCreate(cmd *cobra.Command, args []string, newClient ClientFactory) 
(err error) {
+       // Config
+       // Create a config based on args.  Also uses the newClient to create a
+       // temporary client for completing options such as available runtimes.
+       cfg, err := newCreateConfig(cmd, args, newClient)
+       if err != nil {
+               return
+       }
+
+       // Client
+       // From environment variables, flags, arguments, and user prompts if 
--confirm
+       // (in increasing levels of precedence)
+       client, done := newClient(
+               dubbo.WithRepository(cfg.Repository))
+       defer done()
+
+       // Validate - a deeper validation than that which is performed when
+       // instantiating the client with the raw config above.
+       if err = cfg.Validate(client); err != nil {
+               return
+       }
+
+       // Create
+       _, err = client.Init(&dubbo.Dubbo{
+               Name:     cfg.Name,
+               Root:     cfg.Path,
+               Runtime:  cfg.Runtime,
+               Template: cfg.Template,
+               Build: dubbo.BuildSpec{
+                       CnMirror: dubbo.BooleanWithComment{
+                               Comment: "Specify `cnMirror: true` to use the 
mirror in mainland China",
+                       },
+               },
+       }, cfg.Init, cmd)
+       if err != nil {
+               return err
+       }
+
+       // Confirm
+       fmt.Fprintf(cmd.OutOrStderr(), "Created %v dubbo application in %v\n", 
cfg.Runtime, cfg.Path)
+       return nil
+}
+
+type createConfig struct {
+       Path       string // Absolute path to function source
+       Runtime    string // Language Runtime
+       Repository string // Repository URI (overrides builtin and installed)
+       Confirm    bool   // Confirm values via an interactive prompt
+
+       // like common is a template
+       Template string
+
+       // Name of the function
+       Name string
+
+       Init bool
+}
+
+// newCreateConfig returns a config populated from the current execution 
context
+// (args, flags and environment variables)
+// The client constructor function is used to create a transient client for
+// accessing things like the current valid templates list, and uses the
+// current value of the config at time of prompting.
+func newCreateConfig(cmd *cobra.Command, args []string, newClient 
ClientFactory) (cfg createConfig, err error) {
+       var (
+               path         string
+               dirName      string
+               absolutePath string
+       )
+
+       if len(args) >= 1 {
+               path = args[0]
+       }
+
+       dirName, absolutePath = deriveNameAndAbsolutePathFromPath(path)
+
+       // Config is the final default values based off the execution context.
+       // When prompting, these become the defaults presented.
+       cfg = createConfig{
+               Name:       dirName,
+               Path:       absolutePath,
+               Repository: viper.GetString("repository"),
+               Runtime:    viper.GetString("language"), // users refer to it 
is language
+               Template:   viper.GetString("template"),
+               Confirm:    viper.GetBool("confirm"),
+               Init:       viper.GetBool("init"),
+       }
+       // If not in confirm/prompting mode, this cfg structure is complete.
+       if !cfg.Confirm {
+               return
+       }
+
+       // Create a temporary client for use by the following prompts to 
complete
+       // runtime/template suggestions etc
+       client, done := newClient()
+       defer done()
+
+       // IN confirm mode.  If also in an interactive terminal, run prompts.
+       if util.InteractiveTerminal() {
+               createdCfg, err := cfg.prompt(client)
+               if err != nil {
+                       return createdCfg, err
+               }
+               fmt.Println("Command:")
+               fmt.Println(singleCommand(cmd, args, createdCfg))
+               return createdCfg, nil
+       }
+
+       // Confirming, but noninteractive
+       // Print out the final values as a confirmation.  Only show Repository 
or
+       // Repositories, not both (repository takes precedence) in order to 
avoid
+       // likely confusion if both are displayed and one is empty.
+       // be removed and both displayed.
+       fmt.Printf("Path:         %v\n", cfg.Path)
+       fmt.Printf("Language:     %v\n", cfg.Runtime) // users refer to it as 
language
+       if cfg.Repository != "" {                     // if an override was 
provided
+               fmt.Printf("Repository:   %v\n", cfg.Repository) // show only 
the override
+       }
+       fmt.Printf("Template:     %v\n", cfg.Template)
+       return
+}
+
+// singleCommand that could be used by the current user to minimally recreate 
the current state.
+func singleCommand(cmd *cobra.Command, args []string, cfg createConfig) string 
{
+       var b strings.Builder
+       b.WriteString(cmd.Root().Name())    // process executable
+       b.WriteString(" -l " + cfg.Runtime) // language runtime is required
+       if cmd.Flags().Lookup("template").Changed {
+               b.WriteString(" -t " + cfg.Template)
+       }
+       if cmd.Flags().Lookup("repository").Changed {
+               b.WriteString(" -r " + cfg.Repository)
+       }
+       if len(args) > 0 {
+               b.WriteString(" " + cfg.Path) // optional trailing <path> 
argument
+       }
+       return b.String()
+}
+
+// Validate the current state of the config, returning any errors.
+// Note this is a deeper validation using a client already configured with a
+// preliminary config object from flags/config, such that the client instance
+// can be used to determine possible values for runtime, templates, etc.  a
+// pre-client validation should not be required, as the Client does its own
+// validation.
+func (c createConfig) Validate(client *dubbo.Client) (err error) {
+       dirName, _ := deriveNameAndAbsolutePathFromPath(c.Path)
+       if err = util.ValidateApplicationName(dirName); err != nil {
+               return
+       }
+
+       if c.Runtime == "" {
+               return noRuntimeError(client)
+       }
+       if c.Runtime != "" && c.Repository == "" &&
+               !isValidRuntime(client, c.Runtime) {
+               return newInvalidRuntimeError(client, c.Runtime)
+       }
+
+       if c.Template != "" && c.Repository == "" &&
+               !isValidTemplate(client, c.Runtime, c.Template) {
+               return newInvalidTemplateError(client, c.Runtime, c.Template)
+       }
+
+       return
+}
+
+// isValidRuntime determines if the given language runtime is a valid choice.
+func isValidRuntime(client *dubbo.Client, runtime string) bool {
+       runtimes, err := client.Runtimes()
+       if err != nil {
+               return false
+       }
+       for _, v := range runtimes {
+               if v == runtime {
+                       return true
+               }
+       }
+       return false
+}
+
+// isValidTemplate determines if the given template is valid for the given
+// runtime.
+func isValidTemplate(client *dubbo.Client, runtime, template string) bool {
+       if !isValidRuntime(client, runtime) {
+               return false
+       }
+       templates, err := client.Templates().List(runtime)
+       if err != nil {
+               return false
+       }
+       for _, v := range templates {
+               if v == template {
+                       return true
+               }
+       }
+       return false
+}
+
+func noRuntimeError(client *dubbo.Client) error {
+       b := strings.Builder{}
+       fmt.Fprintln(&b, "Required flag \"language\" not set.")
+       fmt.Fprintln(&b, "Available language runtimes are:")
+       runtimes, err := client.Runtimes()
+       if err != nil {
+               return err
+       }
+       for _, v := range runtimes {
+               fmt.Fprintf(&b, "  %v\n", v)
+       }
+       return ErrNoRuntime(errors.New(b.String()))
+}
+
+func newInvalidRuntimeError(client *dubbo.Client, runtime string) error {
+       b := strings.Builder{}
+       fmt.Fprintf(&b, "The language runtime '%v' is not recognized.\n", 
runtime)
+       fmt.Fprintln(&b, "Available language runtimes are:")
+       runtimes, err := client.Runtimes()
+       if err != nil {
+               return err
+       }
+       for _, v := range runtimes {
+               fmt.Fprintf(&b, "  %v\n", v)
+       }
+       return ErrInvalidRuntime(errors.New(b.String()))
+}
+
+func newInvalidTemplateError(client *dubbo.Client, runtime, template string) 
error {
+       b := strings.Builder{}
+       fmt.Fprintf(&b, "The template '%v' was not found for language runtime 
'%v'.\n", template, runtime)
+       fmt.Fprintln(&b, "Available templates for this language runtime are:")
+       templates, err := client.Templates().List(runtime)
+       if err != nil {
+               return err
+       }
+       for _, v := range templates {
+               fmt.Fprintf(&b, "  %v\n", v)
+       }
+       return ErrInvalidTemplate(errors.New(b.String()))
+}
+
+// prompt the user with value of config members, allowing for interactively
+// mutating the values. The provided clientFn is used to construct a transient
+// client for use during prompt autocompletion/suggestions (such as suggesting
+// valid templates)
+func (c createConfig) prompt(client *dubbo.Client) (createConfig, error) {
+       var qs []*survey.Question
+
+       runtimes, err := client.Runtimes()
+       if err != nil {
+               return createConfig{}, err
+       }
+
+       init := false
+
+       // ask for init
+       qs = []*survey.Question{
+               {
+                       Name: "Init",
+                       Prompt: &survey.Confirm{
+                               Message: "Create a new dubbo project or 
directly initialize it into a dubbo project",
+                               Default: init,
+                       },
+               },
+       }
+       if err = survey.Ask(qs, &init); err != nil {
+               return createConfig{}, err
+       }
+
+       // ask for path...
+       qs = []*survey.Question{
+               {
+                       Name: "Path",
+                       Prompt: &survey.Input{
+                               Message: "Scaffold Path:",
+                               Default: c.Path,
+                       },
+                       Validate: func(val interface{}) error {
+                               derivedName, _ := 
deriveNameAndAbsolutePathFromPath(val.(string))
+                               return util.ValidateApplicationName(derivedName)
+                       },
+                       Transform: func(ans interface{}) interface{} {
+                               _, absolutePath := 
deriveNameAndAbsolutePathFromPath(ans.(string))
+                               return absolutePath
+                       },
+               }, {
+                       Name: "Runtime",
+                       Prompt: &survey.Select{
+                               Message: "Language Runtime:",
+                               Options: runtimes,
+                               Default: surveySelectDefault(c.Runtime, 
runtimes),
+                       },
+               },
+       }
+       if err := survey.Ask(qs, &c); err != nil {
+               return c, err
+       }
+
+       if !init {
+               // Second loop: choose template with autocompletion filtered by 
chosen runtime
+               qs = []*survey.Question{
+                       {
+                               Name: "Template",
+                               Prompt: &survey.Input{
+                                       Message: "Template:",
+                                       Default: c.Template,
+                                       Suggest: func(prefix string) []string {
+                                               suggestions, err := 
templatesWithPrefix(prefix, c.Runtime, client)
+                                               if err != nil {
+                                                       fmt.Fprintf(os.Stderr, 
"unable to suggest: %v\n", err)
+                                               }
+                                               return suggestions
+                                       },
+                               },
+                       },
+               }
+               if err := survey.Ask(qs, &c); err != nil {
+                       return c, err
+               }
+       }
+       return c, nil
+}
+
+// Tab Completion and Prompt Suggestions Helpers
+// ---------------------------------------------
+
+type flagCompletionFunc func(*cobra.Command, []string, string) ([]string, 
cobra.ShellCompDirective)
+
+func newRuntimeCompletionFunc(newClient ClientFactory) flagCompletionFunc {
+       return func(cmd *cobra.Command, args []string, toComplete string) 
([]string, cobra.ShellCompDirective) {
+               _, err := newCreateConfig(cmd, args, newClient)
+               if err != nil {
+                       fmt.Fprintf(os.Stderr, "error creating client config 
for flag completion: %v\n", err)
+               }
+               client, done := newClient()
+               defer done()
+               return CompleteRuntimeList(cmd, args, toComplete, client)
+       }
+}
+
+func newTemplateCompletionFunc(newClient ClientFactory) flagCompletionFunc {
+       return func(cmd *cobra.Command, args []string, toComplete string) 
([]string, cobra.ShellCompDirective) {
+               _, err := newCreateConfig(cmd, args, newClient)
+               if err != nil {
+                       fmt.Fprintf(os.Stderr, "error creating client config 
for flag completion: %v\n", err)
+               }
+               client, done := newClient()
+               defer done()
+               return CompleteTemplateList(cmd, args, toComplete, client)
+       }
+}
+
+// return templates for language runtime whose full name (including repository)
+// have the given prefix.
+func templatesWithPrefix(prefix, runtime string, client *dubbo.Client) 
([]string, error) {
+       var (
+               suggestions    []string
+               templates, err = client.Templates().List(runtime)
+       )
+       if err != nil {
+               return suggestions, err
+       }
+       for _, template := range templates {
+               if strings.HasPrefix(template, prefix) {
+                       suggestions = append(suggestions, template)
+               }
+       }
+       return suggestions, nil
+}
+
+// runCreateHelp prints help for the create command using a template
+// and options.
+func runCreateHelp(cmd *cobra.Command, args []string, newClient ClientFactory) 
{
+       failSoft := func(err error) {
+               if err != nil {
+                       fmt.Fprintf(cmd.OutOrStderr(), "error: help text may be 
partial: %v\n", err)
+               }
+       }
+
+       tpl := newHelpTemplate(cmd)
+
+       cfg, err := newCreateConfig(cmd, args, newClient)
+       failSoft(err)
+
+       client, done := newClient(
+               dubbo.WithRepository(cfg.Repository))
+       defer done()
+
+       options, err := RuntimeTemplateOptions(client) // human-friendly
+       failSoft(err)
+
+       data := struct {
+               Options string
+               Name    string
+       }{
+               Options: options,
+               Name:    cmd.Root().Use,
+       }
+
+       if err := tpl.Execute(cmd.OutOrStdout(), data); err != nil {
+               fmt.Fprintf(cmd.ErrOrStderr(), "unable to display help text: 
%v\n", err)
+       }
+}
+
+// newHelpTemplate returns a template for the create command's help text
+func newHelpTemplate(cmd *cobra.Command) *template.Template {
+       body := cmd.Long + "\n\n" + cmd.UsageString()
+       t := template.New("help")
+       fm := template.FuncMap{
+               "indent": func(i int, c string, v string) string {
+                       indentation := strings.Repeat(c, i)
+                       return indentation + strings.Replace(v, "\n", 
"\n"+indentation, -1)
+               },
+       }
+       t.Funcs(fm)
+       return template.Must(t.Parse(body))
+}
+
+// RuntimeTemplateOptions is a human-friendly table of valid Language Runtime
+// to Template combinations.
+// Exported for use in docs.
+func RuntimeTemplateOptions(client *dubbo.Client) (string, error) {
+       runtimes, err := client.Runtimes()
+       if err != nil {
+               return "", err
+       }
+       builder := strings.Builder{}
+       writer := tabwriter.NewWriter(&builder, 0, 0, 3, ' ', 0)
+
+       fmt.Fprint(writer, "Language\tTemplate\n")
+       fmt.Fprint(writer, "--------\t--------\n")
+       for _, r := range runtimes {
+               templates, err := client.Templates().List(r)
+               // Not all language packs will have templates for
+               // all available runtimes. Without this check
+               if err != nil && !errors.Is(err, dubbo.ErrTemplateNotFound) {
+                       return "", err
+               }
+               for _, t := range templates {
+                       fmt.Fprintf(writer, "%v\t%v\n", r, t) // write tabbed
+               }
+       }
+       writer.Flush()
+       return builder.String(), nil
+}
diff --git a/dubboctl/cmd/deploy.go b/dubboctl/cmd/deploy.go
new file mode 100644
index 00000000..2d27385b
--- /dev/null
+++ b/dubboctl/cmd/deploy.go
@@ -0,0 +1,317 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "os"
+       "os/exec"
+       "path/filepath"
+)
+
+import (
+       "github.com/AlecAivazis/survey/v2"
+
+       "github.com/ory/viper"
+
+       "github.com/spf13/cobra"
+
+       "k8s.io/client-go/rest"
+
+       "k8s.io/client-go/tools/clientcmd"
+
+       "k8s.io/client-go/util/homedir"
+)
+
+import (
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/kube"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/util"
+)
+
+const (
+       basePort  = 30000
+       portLimit = 32767
+)
+
+func addDeploy(baseCmd *cobra.Command, newClient ClientFactory) {
+       cmd := &cobra.Command{
+               Use:   "deploy",
+               Short: "Generate the k8s yaml of the application. By the way, 
you can choose to build the image, push the image and apply to the k8s 
cluster.",
+               Long: `
+NAME
+       dubboctl deploy - Generate the k8s yaml of the application. By the way, 
you can choose to build the image, push the image and apply to the k8s cluster.
+
+SYNOPSIS
+       dubboctl deploy [flags]
+`,
+               SuggestFor: []string{"delpoy", "deplyo"},
+               PreRunE: bindEnv("path", "output", "namespace", "image", 
"envs", "name", "containerPort",
+                       "targetPort", "nodePort", "apply", "useDockerfile", 
"force", "builder-image", "build", "context",
+                       "kubeConfig", "push"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runDeploy(cmd, newClient)
+               },
+       }
+       cmd.Flags().StringP("namespace", "n", "default",
+               "Deploy into a specific namespace")
+       cmd.Flags().StringP("output", "o", "kube.yaml",
+               "output kubernetes manifest")
+       cmd.Flags().StringP("name", "", "",
+               "The name of application")
+       cmd.Flags().IntP("containerPort", "", 0,
+               "The port of the deployment to listen on pod (required)")
+       cmd.Flags().IntP("targetPort", "", 0,
+               "The targetPort of the deployment, default to port")
+       cmd.Flags().IntP("nodePort", "", 0,
+               "The nodePort of the deployment to expose")
+
+       cmd.Flags().StringP("context", "", "",
+               "Context in kubeconfig to use")
+       cmd.Flags().StringP("kubeConfig", "k", "",
+               "Path to kubeconfig")
+
+       cmd.Flags().StringArrayP("envs", "e", nil,
+               "DeployMode variable to set in the form NAME=VALUE. "+
+                       "This is for the environment variables passed in by the 
builderpack build method.")
+       cmd.Flags().StringP("builder-image", "b", "",
+               "Specify a custom builder image for use by the builder other 
than its default.")
+       cmd.Flags().BoolP("useDockerfile", "d", false,
+               "Use the dockerfile with the specified path to build")
+       cmd.Flags().StringP("image", "i", "",
+               "Container image( [registry]/[namespace]/[name]:[tag] )")
+       cmd.Flags().BoolP("push", "", true,
+               "Whether to push the image to the registry center by the way")
+       cmd.Flags().BoolP("force", "f", false,
+               "Whether to force build")
+
+       cmd.Flags().BoolP("build", "", true,
+               "Whether to build the image")
+       cmd.Flags().BoolP("apply", "a", false,
+               "Whether to apply the application to the k8s cluster by the 
way")
+       cmd.Flags().StringP("portName", "", "http",
+               "Name of the port to be exposed")
+
+       addPathFlag(cmd)
+       cmd.Flags().SetInterspersed(false)
+       baseCmd.AddCommand(cmd)
+}
+
+func runDeploy(cmd *cobra.Command, newClient ClientFactory) error {
+       if err := util.CreatePaths(); err != nil {
+               return err
+       }
+       cfg := newDeployConfig(cmd)
+       f, err := dubbo.NewDubbo(cfg.Path)
+       if err != nil {
+               return err
+       }
+       cfg, err = cfg.Prompt(f)
+       if err != nil {
+               return err
+       }
+       if err := cfg.Validate(cmd); err != nil {
+               return err
+       }
+
+       if !f.Initialized() {
+               return dubbo.NewErrNotInitialized(f.Root)
+       }
+
+       cfg.Configure(f)
+
+       clientOptions, err := cfg.deployclientOptions()
+       if err != nil {
+               return err
+       }
+       client, done := newClient(clientOptions...)
+       defer done()
+
+       kubeEnv := true
+       _, err = rest.InClusterConfig()
+       if err != nil {
+               kubeconfig := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
+               if len(kubeconfig) <= 0 {
+                       if home := homedir.HomeDir(); home != "" {
+                               kubeconfig = filepath.Join(home, ".kube", 
"config")
+                       }
+               }
+               _, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
+               if err != nil {
+                       kubeEnv = false
+               }
+       }
+
+       if kubeEnv {
+               err := f.CheckLabels(cfg.Namespace, client)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // generate template first
+       f, err = client.Deploy(cmd.Context(), f)
+       if err != nil {
+               return err
+       }
+
+       if cfg.Build {
+               if f.Built() && !cfg.Force {
+                       fmt.Fprintf(cmd.OutOrStdout(), "The Application is up 
to date, If you still want to build, use `--force true`\n")
+                       return nil
+               }
+               if f, err = client.Build(cmd.Context(), f); err != nil {
+                       return err
+               }
+               if cfg.Push {
+                       if f, err = client.Push(cmd.Context(), f); err != nil {
+                               return err
+                       }
+               }
+       }
+
+       if cfg.Apply {
+               err := applyTok8s(cmd, f)
+               if err != nil {
+                       return err
+               }
+       }
+
+       if err = f.Write(); err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (d DeployConfig) deployclientOptions() ([]dubbo.Option, error) {
+       o, err := d.buildclientOptions()
+       if err != nil {
+               return o, err
+       }
+       var cliOpts []kube.CtlClientOption
+       cliOpts = []kube.CtlClientOption{
+               kube.WithKubeConfigPath(d.KubeConfig),
+               kube.WithContext(d.Context),
+       }
+       cli, err := kube.NewCtlClient(cliOpts...)
+       if err != nil {
+               return o, err
+       }
+       o = append(o, dubbo.WithKubeClient(cli))
+       return o, nil
+}
+
+func applyTok8s(cmd *cobra.Command, d *dubbo.Dubbo) error {
+       file := filepath.Join(d.Root, d.Deploy.Output)
+       c := exec.CommandContext(cmd.Context(), "kubectl", "apply", "-f", file)
+       c.Stdout = os.Stdout
+       c.Stderr = os.Stderr
+       err := c.Run()
+       return err
+}
+
+func (c DeployConfig) Validate(cmd *cobra.Command) (err error) {
+       nodePort := c.NodePort
+       if nodePort != 0 && (nodePort < basePort || nodePort > portLimit) {
+               return errors.New("nodePort should be between 30000 and 32767")
+       }
+       return nil
+}
+
+func (c *DeployConfig) Prompt(d *dubbo.Dubbo) (*DeployConfig, error) {
+       var err error
+       if !util.InteractiveTerminal() {
+               return c, nil
+       }
+       buildconfig, err := c.buildConfig.Prompt(d)
+       if err != nil {
+               return c, err
+       }
+       c.buildConfig = buildconfig
+
+       if d.Deploy.ContainerPort == 0 && c.ContainerPort == 0 {
+               qs := []*survey.Question{
+                       {
+                               Name:     "containerPort",
+                               Validate: survey.Required,
+                               Prompt: &survey.Input{
+                                       Message: "The container port",
+                               },
+                       },
+               }
+               if err = survey.Ask(qs, c); err != nil {
+                       return c, err
+               }
+       }
+       return c, err
+}
+
+func (c DeployConfig) Configure(f *dubbo.Dubbo) {
+       c.buildConfig.Configure(f)
+       if c.Namespace != "" {
+               f.Deploy.Namespace = c.Namespace
+       }
+       if c.Output != "" {
+               f.Deploy.Output = c.Output
+       }
+       if c.ContainerPort != 0 {
+               f.Deploy.ContainerPort = c.ContainerPort
+       }
+       if c.TargetPort != 0 {
+               f.Deploy.TargetPort = c.TargetPort
+       }
+       if c.NodePort != 0 {
+               f.Deploy.NodePort = c.NodePort
+       }
+       if c.PortName != "" {
+               f.Deploy.PortName = c.PortName
+       }
+}
+
+type DeployConfig struct {
+       *buildConfig
+       KubeConfig    string
+       Context       string
+       Build         bool
+       Apply         bool
+       Namespace     string
+       ContainerPort int
+       Output        string
+       Force         bool
+       TargetPort    int
+       NodePort      int
+       PortName      string
+}
+
+func newDeployConfig(cmd *cobra.Command) (c *DeployConfig) {
+       c = &DeployConfig{
+               buildConfig:   newBuildConfig(cmd),
+               KubeConfig:    viper.GetString("kubeConfig"),
+               Context:       viper.GetString("context"),
+               Build:         viper.GetBool("build"),
+               Apply:         viper.GetBool("apply"),
+               Output:        viper.GetString("output"),
+               Namespace:     viper.GetString("namespace"),
+               Force:         viper.GetBool("force"),
+               ContainerPort: viper.GetInt("containerPort"),
+               TargetPort:    viper.GetInt("targetPort"),
+               NodePort:      viper.GetInt("nodePort"),
+               PortName:      viper.GetString("portName"),
+       }
+       return
+}
diff --git a/dubboctl/cmd/repository.go b/dubboctl/cmd/repository.go
new file mode 100644
index 00000000..2714d5f2
--- /dev/null
+++ b/dubboctl/cmd/repository.go
@@ -0,0 +1,596 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cmd
+
+import (
+       "errors"
+       "fmt"
+       "os"
+)
+
+import (
+       "github.com/AlecAivazis/survey/v2"
+
+       "github.com/ory/viper"
+
+       "github.com/spf13/cobra"
+)
+
+import (
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/dubbo"
+       "github.com/apache/dubbo-kubernetes/dubboctl/internal/util"
+)
+
+// command constructors
+// --------------------
+func addRepository(baseCmd *cobra.Command, newClient ClientFactory) {
+       cmd := &cobra.Command{
+               Short:   "Manage installed template repositories",
+               Use:     "repository",
+               Aliases: []string{"repo", "repositories"},
+               Long: `
+NAME
+       dubboctl - Manage set of installed repositories.
+
+SYNOPSIS
+       dubboctl repo [-c|--confirm]
+       dubboctl repo list [-r|--repositories] [-c|--confirm]
+       dubboctl repo add <name> <url>[-r|--repositories] [-c|--confirm]
+       dubboctl repo rename <old> <new> [-r|--repositories] [-c|--confirm]
+       dubboctl repo remove <name> [-r|--repositories] [-c|--confirm]
+
+DESCRIPTION
+       Manage template repositories installed on disk at either the default 
location
+       (~/.config/dubbo/repositories) or the location specified by the 
--repository
+       flag.  Once added, a template from the repository can be used when 
creating
+       a new Dubbo.
+
+       Interactive Prompts:
+       To complete these commands interactively, pass the --confirm (-c) flag 
to
+       the 'repository' command, or any of the inidivual subcommands.
+
+       The Default Repository:
+       The default repository is not stored on disk, but embedded in the 
binary and
+       can be used without explicitly specifying the name.  The default 
repository
+       is always listed first, and is assumed when creating a new function 
without
+       specifying a repository name prefix.
+       For example, to create a new one using the 'common' template from the
+       default repository.
+               $ dubboctl create -l go -t common
+
+       The Repository Flag:
+       Installing repositories locally is optional.  To use a template from a 
remote
+       repository directly, it is possible to use the --repository flag on 
create.
+       This leaves the local disk untouched.  For example, To create a 
scaffold using
+       the dubboctl-samples http template without installing the template
+       repository locally, use the --repository (-r) flag on create:
+               $ dubboctl create -l go \
+                       --template http \
+                       --repository https://github.com/sjmshsh/dubboctl-samples
+
+       Alternative Repositories Location:
+       Repositories are stored on disk in ~/.config/dubbo/repositories by 
default.
+       This location can be altered by setting the DUBBO_REPOSITORIES_PATH
+       environment variable.
+
+
+COMMANDS
+
+       With no arguments, this help text is shown.  To manage repositories with
+       an interactive prompt, use the use the --confirm (-c) flag.
+         $ dubboctl repository -c
+
+       add
+         Add a new repository to the installed set.
+           $ dubboctl repository add <name> <URL>
+
+         For Example, to add the ruiyi Project repository:
+           $ dubboctl repository add ruiyi 
https://github.com/sjmshsh/dubboctl-samples
+
+         Once added, a function can be created with templates from the new 
repository
+         by prefixing the template name with the repository.  For example, to 
create
+         a new function using the dubbogo template:
+           $ dubboctl create -l go -t ruiyi/dubbogo
+
+       list
+         List all available repositories, including the installed default
+         repository.  Repositories available are listed by name. 
+
+       rename
+         Rename a previously installed repository from <old> to <new>. Only 
installed
+         repositories can be renamed.
+           $ dubboctl repository rename <name> <new name>
+
+       remove
+         Remove a repository by name.  Removes the repository from local 
storage
+         entirely.  When in confirm mode (--confirm) it will confirm before
+         deletion, but in regular mode this is done immediately, so please use
+         caution, especially when using an altered repositories location
+         (via the DUBBO_REPOSITORIES_PATH environment variable).
+           $ dubboctl repository remove <name>
+
+EXAMPLES
+       o Run in confirmation mode (interactive prompts) using the --confirm 
flag
+         $ dubboctl repository -c
+
+       o Add a repository and create a new function using a template from it:
+         $ dubboctl repository add ruiyi 
https://github.com/sjmshsh/dubboctl-samples
+         $ dubboctl repository list
+         default
+         functastic
+         $ dubboctl create -l go -t ruiyi/dubbogo
+         ...
+
+       o Add a repository specifying the branch to use (dubboctl):
+         $ dubboctl repository add ruiyi 
https://github.com/sjmshsh/dubboctl-samples#dubboctl
+         $ dubboctl repository list
+         default
+         ruiyi
+         $ dubboctl create -l go -t http
+         ...
+
+       o List all repositories including the URL from which remotes were 
installed
+         $ dubboctl repository list -v
+         default
+         master        https://github.com/sjmshsh/dubboctl-samples#master
+
+       o Rename an installed repository
+         $ dubboctl repository list
+         default
+         ruiyi
+         $ dubboctl repository rename ruiyi dubboTest
+         $ dubboctl repository list
+         default
+         dubboTest
+
+       o Remove an installed repository
+         $ dubboctl repository list
+         default
+         dubboTest
+         $ dubboctl repository remove dubboTest
+         $ dubboctl repository list
+         default
+`,
+               SuggestFor: []string{"repositories", "repos", "template", 
"templates", "pack", "packs"},
+               PreRunE:    bindEnv("confirm"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runRepository(cmd, args, newClient)
+               },
+       }
+
+       addConfirmFlag(cmd, false)
+
+       cmd.AddCommand(NewRepositoryListCmd(newClient))
+       cmd.AddCommand(NewRepositoryAddCmd(newClient))
+       cmd.AddCommand(NewRepositoryRenameCmd(newClient))
+       cmd.AddCommand(NewRepositoryRemoveCmd(newClient))
+
+       baseCmd.AddCommand(cmd)
+}
+
+func NewRepositoryListCmd(newClient ClientFactory) *cobra.Command {
+       cmd := &cobra.Command{
+               Short:   "List repositories",
+               Use:     "list",
+               Aliases: []string{"ls"},
+               PreRunE: bindEnv("confirm"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runRepositoryList(cmd, args, newClient)
+               },
+       }
+
+       addConfirmFlag(cmd, false)
+       return cmd
+}
+
+func NewRepositoryAddCmd(newClient ClientFactory) *cobra.Command {
+       cmd := &cobra.Command{
+               Short:      "Add a repository",
+               Use:        "add <name> <url>",
+               SuggestFor: []string{"ad", "install"},
+               PreRunE:    bindEnv("confirm"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runRepositoryAdd(cmd, args, newClient)
+               },
+       }
+
+       addConfirmFlag(cmd, false)
+       return cmd
+}
+
+func NewRepositoryRenameCmd(newClient ClientFactory) *cobra.Command {
+       cmd := &cobra.Command{
+               Short:   "Rename a repository",
+               Use:     "rename <old> <new>",
+               Aliases: []string{"mv"},
+               PreRunE: bindEnv("confirm"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runRepositoryRename(cmd, args, newClient)
+               },
+       }
+
+       addConfirmFlag(cmd, false)
+       return cmd
+}
+
+func NewRepositoryRemoveCmd(newClient ClientFactory) *cobra.Command {
+       cmd := &cobra.Command{
+               Short:      "Remove a repository",
+               Use:        "remove <name>",
+               Aliases:    []string{"rm"},
+               SuggestFor: []string{"delete", "del"},
+               PreRunE:    bindEnv("confirm"),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       return runRepositoryRemove(cmd, args, newClient)
+               },
+       }
+
+       addConfirmFlag(cmd, false)
+       return cmd
+}
+
+// command implementations
+// -----------------------
+
+// Run
+// (list by default or interactive with -c|--confirm)
+func runRepository(cmd *cobra.Command, args []string, newClient ClientFactory) 
(err error) {
+       cfg, err := newRepositoryConfig(args)
+       if err != nil {
+               return
+       }
+
+       // If in noninteractive, normal mode the help text is shown
+       if !cfg.Confirm {
+               return cmd.Help()
+       }
+
+       // If in interactive mode, the user chan choose which subcommand to 
invoke
+       // Prompt for action to perform
+       question := &survey.Question{
+               Name: "Action",
+               Prompt: &survey.Select{
+                       Message: "Operation to perform:",
+                       Options: []string{"list", "add", "rename", "remove"},
+                       Default: "list",
+               },
+       }
+       answer := struct{ Action string }{}
+       if err = survey.Ask([]*survey.Question{question}, &answer); err != nil {
+               return
+       }
+
+       // Run the command indicated
+       switch answer.Action {
+       case "list":
+               return runRepositoryList(cmd, args, newClient)
+       case "add":
+               return runRepositoryAdd(cmd, args, newClient)
+       case "rename":
+               return runRepositoryRename(cmd, args, newClient)
+       case "remove":
+               return runRepositoryRemove(cmd, args, newClient)
+       }
+       return fmt.Errorf("invalid action '%v'", answer.Action) // Unreachable
+}
+
+// List
+func runRepositoryList(_ *cobra.Command, args []string, newClient 
ClientFactory) (err error) {
+       _, err = newRepositoryConfig(args)
+       if err != nil {
+               return
+       }
+
+       client, done := newClient()
+       defer done()
+
+       // List all repositories given a client instantiated about config.
+       rr, err := client.Repositories().All()
+       if err != nil {
+               return
+       }
+
+       // Print repository names, or name plus url
+       // This follows the format of `git remote`, as it is likely familiar.
+       for _, r := range rr {
+               fmt.Fprintln(os.Stdout, r.Name+"\t"+r.URL())
+       }
+       return
+}
+
+// Add
+func runRepositoryAdd(_ *cobra.Command, args []string, newClient 
ClientFactory) (err error) {
+       // Supports both composable, discrete CLI commands or prompt-based 
"config"
+       // by setting the argument values (name and ulr) to value of positional 
args,
+       // but only requires them if not prompting.  If prompting, those values
+       // become the prompt defaults.
+
+       cfg, err := newRepositoryConfig(args)
+       if err != nil {
+               return
+       }
+
+       // Adding a repository requires there be a config path structure on disk
+       if err = util.CreatePaths(); err != nil {
+               return
+       }
+
+       // Create a client instance which utilizes the given repositories path.
+       // Note that this MAY not be in the config structure if the environment
+       // variable to override said path was provided explicitly.
+       // be created in XDG_CONFIG_HOME/dubbo even if the repo path environment
+       // was set to some other location on disk.
+       client, done := newClient()
+       defer done()
+
+       // Preconditions
+       // If not confirming/prompting, assert the args were both provided.
+       if len(args) != 2 && !cfg.Confirm {
+               return fmt.Errorf("usage: dubbo repository add <name> <url>")
+       }
+
+       // Extract Params
+       // Populate a struct with the arguments (if provided)
+       params := struct {
+               Name string
+               URL  string
+       }{}
+       if len(args) > 0 {
+               params.Name = args[0]
+       }
+       if len(args) > 1 {
+               params.URL = args[1]
+       }
+
+       // Prompt/Confirm
+       // If confirming/prompting, interactively populate the params from the 
user
+       // (using the current values as defaults)
+       //
+       // If terminal not interactive, effective values are echoed.
+       //
+       // Note that empty values can be passed to the final client's Add 
method if:
+       //   Argument(s) not provided
+       //   Confirming (-c|--confirm)
+       //   Is a noninteractive terminal
+       // This is an expected case.  The empty value will be echoed to stdout, 
the
+       // API will be invoked, and a helpful error message will indicate that 
the
+       // request is missing required parameters.
+       if cfg.Confirm && util.InteractiveTerminal() {
+               questions := []*survey.Question{
+                       {
+                               Name:     "Name",
+                               Validate: survey.Required,
+                               Prompt: &survey.Input{
+                                       Message: "Name for the new repository:",
+                                       Default: params.Name,
+                               },
+                       }, {
+                               Name:     "URL",
+                               Validate: survey.Required,
+                               Prompt: &survey.Input{
+                                       Message: "URL of the new repository:",
+                                       Default: params.URL,
+                               },
+                       },
+               }
+               if err = survey.Ask(questions, &params); err != nil {
+                       return
+                       // not checking for terminal.InterruptError because 
failure to complete,
+                       // for whatever reason, should exit the program 
non-zero.
+               }
+       } else if cfg.Confirm {
+               fmt.Fprintf(os.Stdout, "Name: %v\n", params.Name)
+               fmt.Fprintf(os.Stdout, "URL:  %v\n", params.URL)
+       }
+
+       // Add repository
+       var n string
+       if n, err = client.Repositories().Add(params.Name, params.URL); err != 
nil {
+               return
+       }
+       fmt.Fprintf(os.Stdout, "Repository added: %s\n", n)
+       return
+}
+
+// Rename
+func runRepositoryRename(_ *cobra.Command, args []string, newClient 
ClientFactory) (err error) {
+       cfg, err := newRepositoryConfig(args)
+       if err != nil {
+               return
+       }
+       client, done := newClient()
+       defer done()
+
+       // Preconditions
+       if len(args) != 2 && !cfg.Confirm {
+               return fmt.Errorf("usage: dubbo repository rename <old> <new>")
+       }
+
+       // Extract Params
+       params := struct {
+               Old string
+               New string
+       }{}
+       if len(args) > 0 {
+               params.Old = args[0]
+       }
+       if len(args) > 1 {
+               params.New = args[1]
+       }
+
+       // Repositories installed according to the client
+       // (does not include the builtin default)
+       repositories, err := installedRepositories(client)
+       if err != nil {
+               return
+       }
+
+       // Confirm (interactive prompt mode)
+       if cfg.Confirm && util.InteractiveTerminal() {
+               questions := []*survey.Question{
+                       {
+                               Name:     "Old",
+                               Validate: survey.Required,
+                               Prompt: &survey.Select{
+                                       Message: "Repository to rename:",
+                                       Options: repositories,
+                               },
+                       }, {
+                               Name:     "New",
+                               Validate: survey.Required,
+                               Prompt: &survey.Input{
+                                       Message: "New name:",
+                                       Default: params.New,
+                               },
+                       },
+               }
+               if err = survey.Ask(questions, &params); err != nil {
+                       return // for any reason, including interrupt, is an 
nonzero exit
+               }
+       } else if cfg.Confirm {
+               fmt.Fprintf(os.Stdout, "Old: %v\n", params.Old)
+               fmt.Fprintf(os.Stdout, "New: %v\n", params.New)
+       }
+
+       // Rename the repository
+       if err = client.Repositories().Rename(params.Old, params.New); err != 
nil {
+               return
+       }
+       fmt.Fprintln(os.Stdout, "Repository renamed")
+       return
+}
+
+// Remove
+func runRepositoryRemove(_ *cobra.Command, args []string, newClient 
ClientFactory) (err error) {
+       cfg, err := newRepositoryConfig(args)
+       if err != nil {
+               return
+       }
+       client, done := newClient()
+       defer done()
+
+       // Preconditions
+       if len(args) != 1 && !cfg.Confirm {
+               return fmt.Errorf("usage: dubbo repository remove <name>")
+       }
+
+       // Extract param(s)
+       params := struct {
+               Name string
+               Sure bool
+       }{}
+       if len(args) > 0 {
+               params.Name = args[0]
+       }
+       // "Are you sure" confirmation flag
+       // (not using name 'Confirm' to avoid confusion with cfg.Confirm)
+       // defaults to Yes.  This is debatable, but I don't want to choose the 
repo
+       // to remove and then have to see a prompt and then have to hit 'y'.  
Just
+       // prompting once to make sure, which requires another press of enter, 
seems
+       // sufficient.
+       params.Sure = true
+
+       // Repositories installed according to the client
+       // (does not include the builtin default)
+       repositories, err := installedRepositories(client)
+       if err != nil {
+               return
+       }
+
+       if len(repositories) == 0 {
+               return errors.New("no repositories installed. use 'add' to 
install")
+       }
+
+       // Confirm (interactive prompt mode)
+       if cfg.Confirm && util.InteractiveTerminal() {
+               questions := []*survey.Question{
+                       {
+                               Name:     "Name",
+                               Validate: survey.Required,
+                               Prompt: &survey.Select{
+                                       Message: "Repository to remove:",
+                                       Options: repositories,
+                               },
+                       }, {
+                               Name: "Sure",
+                               Prompt: &survey.Confirm{
+                                       Message: "This will remove the 
repository from local disk. Are you sure?",
+                                       Default: params.Sure,
+                               },
+                       },
+               }
+               if err = survey.Ask(questions, &params); err != nil {
+                       return // for any reason, including interrupt, is a 
nonzero exit
+               }
+       } else if cfg.Confirm {
+               fmt.Fprintf(os.Stdout, "Repository: %v\n", params.Name)
+       }
+
+       // Cancel if they got cold feet.
+       if !params.Sure {
+               // While an argument could be made to the contrary, I believe 
it is
+               // important than an abort by the user, either by answering no 
to the
+               // confirmation or by an os interrupt such as ^C be considered 
an error,
+               // and thus a non-zero program exit.  This is because a user 
may have
+               // chained the command, and an abort (for whatever reason) 
should cancel
+               // the whole chain.  For example, given the command:
+               //    dubbo repo rm -cv && doSomethingOnSuccess
+               // The trailing command 'doSomethingOnSuccess' should not be 
evaluated if
+               // the first, `dubbo repo rm`, does not exit 0.
+               fmt.Fprintln(os.Stdout, "Repository remove canceled")
+               return fmt.Errorf("repository removal canceled")
+       }
+
+       // Remove the repository
+       if err = client.Repositories().Remove(params.Name); err != nil {
+               return
+       }
+       fmt.Fprintln(os.Stdout, "Repository removed")
+       return
+}
+
+// Installed repositories
+// All repositories which have been installed (does not include builtin)
+func installedRepositories(client *dubbo.Client) ([]string, error) {
+       // Client API contract stipulates the list always lists the defeault 
builtin
+       // repo, and always lists it at index 0
+       repositories, err := client.Repositories().List()
+       if err != nil {
+               return []string{}, err
+       }
+       return repositories[1:], nil
+}
+
+// client config
+// -------------
+
+// repositoryConfig used for instantiating a fn.Client
+type repositoryConfig struct {
+       Confirm bool // Enables interactive confirmation/prompting mode
+}
+
+// newRepositoryConfig creates a configuration suitable for use instantiating 
the
+// fn Client. Note that parameters for the individual commands (add, remove 
etc)
+// are collected separately in their requisite run functions.
+func newRepositoryConfig(args []string) (cfg repositoryConfig, err error) {
+       // initial config is populated based on flags, which are themselves
+       // first populated by static defaults, then environment variables,
+       // finally command flags.
+       cfg = repositoryConfig{
+               Confirm: viper.GetBool("confirm"),
+       }
+       return
+}

Reply via email to