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

chunshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-horaedb.git


The following commit(s) were added to refs/heads/main by this push:
     new a1041cd9 feat(horaectl): initial commit (#1450)
a1041cd9 is described below

commit a1041cd97c243ae7ca90836b7741dc8bf4451a8b
Author: chunshao.rcs <[email protected]>
AuthorDate: Mon Jan 22 17:21:25 2024 +0800

    feat(horaectl): initial commit (#1450)
    
    ## Rationale
    Introduce command line tool for horaedb, `horaectl`.
    
    ## Detailed Changes
    * Initial commit.
    * Support `list clusters` and `cluster diagnose`.
    ```
    go run main.go --meta_addr 127.0.0.1:8080 --cluster_name defaultCluster
    127.0.0.1:8080(defaultCluster) > c
    
+----+----------------+------------+--------------+-----------------------------+-------------------------+-------------------------+
    | ID | NAME           | SHARDTOTAL | TOPOLOGYTYPE | 
PROCEDUREEXECUTINGBATCHSIZE | CREATEDAT               | MODIFIEDAT              
|
    
+----+----------------+------------+--------------+-----------------------------+-------------------------+-------------------------+
    |  0 | defaultCluster |          8 | static       |                  
4294967295 | 2024-01-19 15:18:57.034 | 2024-01-19 15:18:57.034 |
    
+----+----------------+------------+--------------+-----------------------------+-------------------------+-------------------------+
    127.0.0.1:8080(defaultCluster) > c d
    
+---------------------+-------------------------+--------------------------+-----------------------+
    | UNREGISTERED_SHARDS | UNREADY_SHARDS:SHARD_ID | UNREADY_SHARDS:NODE_NAME 
| UNREADY_SHARDS:STATUS |
    
+---------------------+-------------------------+--------------------------+-----------------------+
    | []                  |                         |                          
|                       |
    
+---------------------+-------------------------+--------------------------+-----------------------+
    
    ```
    
    ## Test Plan
    Manual test.
---
 ctl/README.md             |  14 +++++
 ctl/cmd/cluster.go        |  38 ++++++++++++++
 ctl/cmd/diagnose.go       |  38 ++++++++++++++
 ctl/cmd/quit.go           |  40 ++++++++++++++
 ctl/cmd/root.go           | 130 ++++++++++++++++++++++++++++++++++++++++++++++
 ctl/cmd/root_test.go      |  91 ++++++++++++++++++++++++++++++++
 ctl/go.mod                |  38 ++++++++++++++
 ctl/go.sum                |  83 +++++++++++++++++++++++++++++
 ctl/licenserc.toml        |  22 ++++++++
 ctl/main.go               |  26 ++++++++++
 ctl/operation/clusters.go |  98 ++++++++++++++++++++++++++++++++++
 ctl/operation/const.go    |  34 ++++++++++++
 ctl/operation/util.go     |  58 +++++++++++++++++++++
 13 files changed, 710 insertions(+)

diff --git a/ctl/README.md b/ctl/README.md
new file mode 100644
index 00000000..929d1681
--- /dev/null
+++ b/ctl/README.md
@@ -0,0 +1,14 @@
+# HoraeCTL
+
+![License](https://img.shields.io/badge/license-Apache--2.0-green.svg)
+
+HoraeCTL is the operation tool for managing the HoraeDB cluster.
+
+## Quick Start
+TODO
+
+## Contributing
+The project is under rapid development so that any contribution is welcome.
+
+## License
+HoraeCTL is under [Apache License 2.0](./LICENSE).
diff --git a/ctl/cmd/cluster.go b/ctl/cmd/cluster.go
new file mode 100644
index 00000000..4f59ab97
--- /dev/null
+++ b/ctl/cmd/cluster.go
@@ -0,0 +1,38 @@
+/*
+ * 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 (
+       "github.com/apache/incubator-horaedb/ctl/operation"
+       "github.com/spf13/cobra"
+)
+
+var clusterCmd = &cobra.Command{
+       Use:     "cluster",
+       Aliases: []string{"c"},
+       Short:   "Operations on cluster",
+       Run: func(cmd *cobra.Command, args []string) {
+               operation.ClustersList()
+       },
+}
+
+func init() {
+       rootCmd.AddCommand(clusterCmd)
+}
diff --git a/ctl/cmd/diagnose.go b/ctl/cmd/diagnose.go
new file mode 100644
index 00000000..044ed517
--- /dev/null
+++ b/ctl/cmd/diagnose.go
@@ -0,0 +1,38 @@
+/*
+ * 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 (
+       "github.com/apache/incubator-horaedb/ctl/operation"
+       "github.com/spf13/cobra"
+)
+
+var diagnoseCmd = &cobra.Command{
+       Use:     "diagnose",
+       Aliases: []string{"d"},
+       Short:   "Cluster diagnose",
+       Run: func(cmd *cobra.Command, args []string) {
+               operation.ClusterDiagnose()
+       },
+}
+
+func init() {
+       clusterCmd.AddCommand(diagnoseCmd)
+}
diff --git a/ctl/cmd/quit.go b/ctl/cmd/quit.go
new file mode 100644
index 00000000..faf3d70c
--- /dev/null
+++ b/ctl/cmd/quit.go
@@ -0,0 +1,40 @@
+/*
+ * 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 (
+       "github.com/spf13/cobra"
+
+       "os"
+)
+
+var quitCmd = &cobra.Command{
+       Use:     "quit",
+       Aliases: []string{"q", "exit"},
+       Short:   "Quit horaectl",
+       Run: func(cmd *cobra.Command, args []string) {
+               println("Bye!")
+               os.Exit(0)
+       },
+}
+
+func init() {
+       rootCmd.AddCommand(quitCmd)
+}
diff --git a/ctl/cmd/root.go b/ctl/cmd/root.go
new file mode 100644
index 00000000..e964d6ab
--- /dev/null
+++ b/ctl/cmd/root.go
@@ -0,0 +1,130 @@
+/*
+ * 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 (
+       "bufio"
+       "fmt"
+       "io"
+       "os"
+       "strings"
+
+       "github.com/apache/incubator-horaedb/ctl/operation"
+       "github.com/pkg/errors"
+       "github.com/spf13/cobra"
+       "github.com/spf13/viper"
+)
+
+var rootCmd = &cobra.Command{
+       Use:   "horaectl",
+       Short: "horaectl is a command line tool for HoraeDB",
+       Run:   func(cmd *cobra.Command, args []string) {},
+}
+
+// Execute adds all child commands to the root command and sets flags 
appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+       err := rootCmd.Execute()
+       if err != nil {
+               os.Exit(1)
+       }
+
+       for _, arg := range os.Args {
+               if arg == "-h" || arg == "--help" {
+                       os.Exit(0)
+               }
+       }
+
+       for {
+               printPrompt(viper.GetString(operation.RootMetaAddr), 
viper.GetString(operation.RootCluster))
+               err = ReadArgs(os.Stdin)
+               if err != nil {
+                       fmt.Println(err)
+                       continue
+               }
+               if err = rootCmd.Execute(); err != nil {
+                       fmt.Println(err)
+                       os.Args = []string{}
+               }
+       }
+}
+
+func init() {
+       rootCmd.PersistentFlags().String(operation.RootMetaAddr, 
"127.0.0.1:8080", "meta addr is used to connect to meta server")
+       viper.BindPFlag(operation.RootMetaAddr, 
rootCmd.PersistentFlags().Lookup(operation.RootMetaAddr))
+
+       rootCmd.PersistentFlags().StringP(operation.RootCluster, "c", 
"defaultCluster", "")
+       viper.BindPFlag(operation.RootCluster, 
rootCmd.PersistentFlags().Lookup(operation.RootCluster))
+
+       rootCmd.CompletionOptions = cobra.CompletionOptions{
+               DisableDefaultCmd:   true,
+               DisableNoDescFlag:   true,
+               DisableDescriptions: true,
+               HiddenDefaultCmd:    true,
+       }
+}
+
+func printPrompt(address, cluster string) {
+       fmt.Printf("%s(%s) > ", address, cluster)
+}
+
+// ReadArgs Forked from 
https://github.com/apache/incubator-seata-ctl/blob/8427314e04cdc435b925ed41573b37e3addeea34/action/common/args.go#L29
+func ReadArgs(in io.Reader) error {
+       os.Args = []string{""}
+
+       scanner := bufio.NewScanner(in)
+
+       var lines []string
+
+       for scanner.Scan() {
+               line := strings.Trim(scanner.Text(), "\r\n ")
+               if line == "" {
+                       return nil
+               }
+               if line[len(line)-1] == '\\' {
+                       line = line[:len(line)-1]
+                       lines = append(lines, line)
+               } else {
+                       lines = append(lines, line)
+                       break
+               }
+       }
+
+       argsStr := strings.Join(lines, " ")
+       rawArgs := strings.Split(argsStr, "'")
+
+       if len(rawArgs) != 1 && len(rawArgs) != 3 {
+               return errors.New("read args from input error")
+       }
+
+       args := strings.Split(rawArgs[0], " ")
+
+       if len(rawArgs) == 3 {
+               args = append(args, rawArgs[1])
+               args = append(args, strings.Split(rawArgs[2], " ")...)
+       }
+
+       for _, arg := range args {
+               if arg != "" {
+                       os.Args = append(os.Args, strings.TrimSpace(arg))
+               }
+       }
+       return nil
+}
diff --git a/ctl/cmd/root_test.go b/ctl/cmd/root_test.go
new file mode 100644
index 00000000..e7027c38
--- /dev/null
+++ b/ctl/cmd/root_test.go
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+// Forked from 
https://github.com/apache/incubator-seata-ctl/blob/8427314e04cdc435b925ed41573b37e3addeea34/action/common/args_test.go.
+
+package cmd
+
+import (
+       "bytes"
+       "os"
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+)
+
+var argsTestCases = []struct {
+       input string
+       args  []string
+       valid bool
+}{
+       {
+               `-a xxx -b yyy -c ' { "a": "b", "c": "d" }' -d -e`,
+               []string{"-a", "xxx", "-b", "yyy", "-c", `{ "a": "b", "c": "d" 
}`, "-d", "-e"},
+               true,
+       },
+       {
+               `-a xxx -b yyy \
+-c \
+' { \
+    "a": "b", \
+    "c": "d" \
+}' \
+-d \
+-e`,
+               []string{"-a", "xxx", "-b", "yyy", "-c", `{  "a": "b",  "c": 
"d"  }`, "-d", "-e"},
+               true,
+       },
+       {
+               `-a xxx -b yyy
+-c \
+' { \
+    "a": "b", \
+    "c": "d" \
+}' \
+-d \
+-e`,
+               []string{"-a", "xxx", "-b", "yyy"},
+               true,
+       },
+       {
+               `-a \
+' { \
+    "a": "b" \
+-b`,
+               []string{},
+               false,
+       },
+}
+
+func TestReadArgs(t *testing.T) {
+       var stdin bytes.Buffer
+       for _, testCase := range argsTestCases {
+               stdin.Reset()
+               stdin.Write([]byte(testCase.input))
+               if !testCase.valid {
+                       assert.NotNil(t, ReadArgs(&stdin))
+                       continue
+               }
+               assert.Nil(t, ReadArgs(&stdin))
+               assert.Equal(t, len(os.Args), len(testCase.args))
+               for i := 0; i < len(os.Args); i++ {
+                       assert.Equal(t, os.Args[i], testCase.args[i])
+               }
+       }
+}
diff --git a/ctl/go.mod b/ctl/go.mod
new file mode 100644
index 00000000..6a93d08f
--- /dev/null
+++ b/ctl/go.mod
@@ -0,0 +1,38 @@
+module github.com/apache/incubator-horaedb/ctl
+
+go 1.21.2
+
+require (
+       github.com/jedib0t/go-pretty/v6 v6.5.3
+       github.com/pkg/errors v0.9.1
+       github.com/spf13/cobra v1.8.0
+       github.com/spf13/viper v1.18.2
+       github.com/stretchr/testify v1.8.4
+)
+
+require (
+       github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // 
indirect
+       github.com/fsnotify/fsnotify v1.7.0 // indirect
+       github.com/hashicorp/hcl v1.0.0 // indirect
+       github.com/inconshreveable/mousetrap v1.1.0 // indirect
+       github.com/magiconair/properties v1.8.7 // indirect
+       github.com/mattn/go-runewidth v0.0.15 // indirect
+       github.com/mitchellh/mapstructure v1.5.0 // indirect
+       github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+       github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 
indirect
+       github.com/rivo/uniseg v0.2.0 // indirect
+       github.com/sagikazarmark/locafero v0.4.0 // indirect
+       github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+       github.com/sourcegraph/conc v0.3.0 // indirect
+       github.com/spf13/afero v1.11.0 // indirect
+       github.com/spf13/cast v1.6.0 // indirect
+       github.com/spf13/pflag v1.0.5 // indirect
+       github.com/subosito/gotenv v1.6.0 // indirect
+       go.uber.org/atomic v1.9.0 // indirect
+       go.uber.org/multierr v1.9.0 // indirect
+       golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+       golang.org/x/sys v0.16.0 // indirect
+       golang.org/x/text v0.14.0 // indirect
+       gopkg.in/ini.v1 v1.67.0 // indirect
+       gopkg.in/yaml.v3 v3.0.1 // indirect
+)
diff --git a/ctl/go.sum b/ctl/go.sum
new file mode 100644
index 00000000..4e8617b5
--- /dev/null
+++ b/ctl/go.sum
@@ -0,0 +1,83 @@
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod 
h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 
h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/frankban/quicktest v1.14.6 
h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod 
h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
+github.com/fsnotify/fsnotify v1.7.0 
h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod 
h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod 
h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.1.0 
h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod 
h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jedib0t/go-pretty/v6 v6.5.3 
h1:GIXn6Er/anHTkVUoufs7ptEvxdD6KIhR7Axa2wYCPF0=
+github.com/jedib0t/go-pretty/v6 v6.5.3/go.mod 
h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod 
h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod 
h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/magiconair/properties v1.8.7 
h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
+github.com/magiconair/properties v1.8.7/go.mod 
h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-runewidth v0.0.15 
h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod 
h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mitchellh/mapstructure v1.5.0 
h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod 
h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/pelletier/go-toml/v2 v2.1.0 
h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod 
h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod 
h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 
h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod 
h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod 
h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rogpeppe/go-internal v1.9.0 
h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod 
h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod 
h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.4.0 
h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
+github.com/sagikazarmark/locafero v0.4.0/go.mod 
h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
+github.com/sagikazarmark/slog-shim v0.1.0 
h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
+github.com/sagikazarmark/slog-shim v0.1.0/go.mod 
h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sourcegraph/conc v0.3.0 
h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
+github.com/sourcegraph/conc v0.3.0/go.mod 
h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod 
h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod 
h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod 
h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod 
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
+github.com/spf13/viper v1.18.2/go.mod 
h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
+github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod 
h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod 
h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.3.0/go.mod 
h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.1/go.mod 
h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod 
h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 
h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod 
h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/subosito/gotenv v1.6.0 
h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod 
h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod 
h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
+go.uber.org/multierr v1.9.0/go.mod 
h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9 
h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
+golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod 
h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod 
h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 
h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod 
h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/ctl/licenserc.toml b/ctl/licenserc.toml
new file mode 100644
index 00000000..b8df11c8
--- /dev/null
+++ b/ctl/licenserc.toml
@@ -0,0 +1,22 @@
+# 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.
+
+headerPath = "Apache-2.0-ASF.txt"
+
+excludes = [
+    # Derived
+]
diff --git a/ctl/main.go b/ctl/main.go
new file mode 100644
index 00000000..6f3695d8
--- /dev/null
+++ b/ctl/main.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 main
+
+import "github.com/apache/incubator-horaedb/ctl/cmd"
+
+func main() {
+       cmd.Execute()
+}
diff --git a/ctl/operation/clusters.go b/ctl/operation/clusters.go
new file mode 100644
index 00000000..ad5a1df0
--- /dev/null
+++ b/ctl/operation/clusters.go
@@ -0,0 +1,98 @@
+/*
+ * 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 operation
+
+import (
+       "fmt"
+       "net/http"
+
+       "github.com/jedib0t/go-pretty/v6/table"
+       "github.com/spf13/viper"
+)
+
+type Cluster struct {
+       ID                          uint32 `json:"id"`
+       Name                        string `json:"name"`
+       MinNodeCount                uint32 `json:"minNodeCount"`
+       ShardTotal                  uint32 `json:"shardTotal"`
+       TopologyType                string `json:"topologyType"`
+       ProcedureExecutingBatchSize uint32 `json:"procedureExecutingBatchSize"`
+       CreatedAt                   uint64 `json:"createdAt"`
+       ModifiedAt                  uint64 `json:"modifiedAt"`
+}
+
+type ClusterResponse struct {
+       Status string    `json:"status"`
+       Data   []Cluster `json:"data"`
+}
+
+type DiagnoseShardStatus struct {
+       NodeName string `json:"node_name"`
+       Status   string `json:"status"`
+}
+
+type DiagnoseShardResponse struct {
+       // shardID -> nodeName
+       UnregisteredShards []uint32                       
`json:"unregistered_shards"`
+       UnreadyShards      map[uint32]DiagnoseShardStatus 
`json:"unready_shards"`
+}
+
+func clusterUrl() string {
+       return HTTP + viper.GetString(RootMetaAddr) + APIClusters
+}
+func diagnoseUrl() string {
+       return HTTP + viper.GetString(RootMetaAddr) + APIClustersDiagnose + 
viper.GetString(RootCluster) + "/shards"
+}
+
+func ClustersList() {
+       url := clusterUrl()
+       var response ClusterResponse
+       err := HttpUtil(http.MethodGet, url, nil, &response)
+       if err != nil {
+               fmt.Println(err)
+       }
+
+       t := tableWriter(clustersListHeader)
+       for _, data := range response.Data {
+               row := table.Row{data.ID, data.Name, data.ShardTotal, 
data.TopologyType, data.ProcedureExecutingBatchSize, 
FormatTimeMilli(int64(data.CreatedAt)), FormatTimeMilli(int64(data.ModifiedAt))}
+               t.AppendRow(row)
+       }
+       fmt.Println(t.Render())
+       t.Style()
+}
+
+func ClusterDiagnose() {
+       url := diagnoseUrl()
+       var response DiagnoseShardResponse
+       err := HttpUtil(http.MethodGet, url, nil, &response)
+       if err != nil {
+               fmt.Println(err)
+       }
+
+       t := tableWriter(clustersDiagnoseHeader)
+       row := table.Row{response.UnregisteredShards}
+       t.AppendRow(row)
+       for shardID, data := range response.UnreadyShards {
+               row := table.Row{"", shardID, data.NodeName, data.Status}
+               t.AppendRow(row)
+       }
+       fmt.Println(t.Render())
+       t.Style()
+}
diff --git a/ctl/operation/const.go b/ctl/operation/const.go
new file mode 100644
index 00000000..21eb5bd3
--- /dev/null
+++ b/ctl/operation/const.go
@@ -0,0 +1,34 @@
+/*
+ * 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 operation
+
+const (
+       HTTP = "http://";
+       API  = "/api/v1"
+
+       APIClusters         = API + "/clusters"
+       APIClustersDiagnose = API + "/clusters/diagnose"
+
+       RootMetaAddr = "meta_addr"
+       RootCluster  = "cluster_name"
+)
+
+var clustersListHeader = []string{"ID", "Name", "ShardTotal", "TopologyType", 
"ProcedureExecutingBatchSize", "CreatedAt", "ModifiedAt"}
+var clustersDiagnoseHeader = []string{"unregistered_shards", 
"unready_shards:shard_id", "unready_shards:node_name", "unready_shards:status"}
diff --git a/ctl/operation/util.go b/ctl/operation/util.go
new file mode 100644
index 00000000..adc52807
--- /dev/null
+++ b/ctl/operation/util.go
@@ -0,0 +1,58 @@
+/*
+ * 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 operation
+
+import (
+       "encoding/json"
+       "io"
+       "net/http"
+       "time"
+
+       "github.com/jedib0t/go-pretty/v6/table"
+)
+
+func tableWriter(headers []string) table.Writer {
+       header := table.Row{}
+       for _, s := range headers {
+               header = append(header, s)
+       }
+       t := table.NewWriter()
+       t.AppendHeader(header)
+       return t
+}
+
+func HttpUtil(method, url string, body io.Reader, response interface{}) error {
+       request, _ := http.NewRequest(method, url, body)
+       resp, err := (&http.Client{}).Do(request)
+       if err != nil {
+               return err
+       }
+       defer resp.Body.Close()
+       b, err := io.ReadAll(resp.Body)
+       if err != nil {
+               return err
+       }
+       err = json.Unmarshal(b, &response)
+       return err
+}
+
+func FormatTimeMilli(milli int64) string {
+       return time.UnixMilli(milli).Format("2006-01-02 15:04:05.000")
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to