From bb50a037cbf262f9c19297626cca44011f566caf Mon Sep 17 00:00:00 2001 From: git-hulk Date: Tue, 30 Apr 2024 21:41:25 +0800 Subject: [PATCH 1/3] Add support of the terminal client for the server --- README.md | 25 ++++ build.sh | 8 +- cmd/client/command/client.go | 86 +++++++++++++ cmd/client/command/consts.go | 28 +++++ cmd/client/command/create.go | 207 ++++++++++++++++++++++++++++++++ cmd/client/command/delete.go | 185 ++++++++++++++++++++++++++++ cmd/client/command/get.go | 103 ++++++++++++++++ cmd/client/command/helper.go | 62 ++++++++++ cmd/client/command/import.go | 101 ++++++++++++++++ cmd/client/command/list.go | 133 ++++++++++++++++++++ cmd/client/command/migrate.go | 119 ++++++++++++++++++ cmd/client/main.go | 40 ++++++ consts/context_key.go | 5 +- consts/errors.go | 3 +- controller/controller.go | 7 +- go.mod | 57 +++++---- go.sum | 192 ++++++++++++++++------------- server/api/cluster.go | 43 ++++++- server/api/cluster_test.go | 1 + server/api/namespace.go | 3 +- server/api/node.go | 6 +- server/api/shard.go | 2 +- server/helper/helper.go | 2 + server/middleware/middleware.go | 12 +- server/route.go | 10 +- store/cluster.go | 8 ++ store/cluster_node.go | 3 +- store/store.go | 52 ++++++-- store/store_test.go | 11 ++ 29 files changed, 1372 insertions(+), 142 deletions(-) create mode 100644 cmd/client/command/client.go create mode 100644 cmd/client/command/consts.go create mode 100644 cmd/client/command/create.go create mode 100644 cmd/client/command/delete.go create mode 100644 cmd/client/command/get.go create mode 100644 cmd/client/command/helper.go create mode 100644 cmd/client/command/import.go create mode 100644 cmd/client/command/list.go create mode 100644 cmd/client/command/migrate.go diff --git a/README.md b/README.md index 07fca9bf..68377937 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,29 @@ $ ./_build/kvctl-server -c config/config.yaml ``` ![image](docs/images/server.gif) +### 2. Use the terminal client to interact with the controller server + +```shell +# Show help +$ ./_build/kvctl --help + +# Create namespace +$ ./_build/kvctl create namespace test-ns + +# List namespaces +$ ./_build/kvctl list namespaces + +# Create cluster in the namespace +$ ./_build/kvctl create cluster test-cluster --nodes 127.0.0.1:6666,127.0.0.1:6667 -n test-ns + +# List clusters in the namespace +$ ./_build/kvctl list clusters -n test-ns + +# Get cluster in the namespace +$ ./_build/kvctl get cluster test-cluster -n test-ns + +# Migrate slot from source to target +$ ./_build/kvctl migrate slot 123 --target 1 -n test-ns -c test-cluster +``` + For the HTTP API, you can find the [HTTP API(work in progress)](docs/API.md) for more details. diff --git a/build.sh b/build.sh index 3751e57d..44a82c8a 100644 --- a/build.sh +++ b/build.sh @@ -79,10 +79,14 @@ BUILD_DATE=`date -u +'%Y-%m-%dT%H:%M:%SZ'` GIT_REVISION=`git rev-parse --short HEAD` SERVER_TARGET_NAME=kvctl-server -CLIENT_TARGET_NAME=kvctl-client +CLIENT_TARGET_NAME=kvctl for TARGET_NAME in "$SERVER_TARGET_NAME" "$CLIENT_TARGET_NAME"; do - CMD_PATH="${GO_PROJECT}/cmd/${TARGET_NAME##*-}" # Remove everything to the left of the last - in the TARGET_NAME variable + if [[ "$TARGET_NAME" == "$SERVER_TARGET_NAME" ]]; then + CMD_PATH="${GO_PROJECT}/cmd/server" + else + CMD_PATH="${GO_PROJECT}/cmd/client" + fi if [[ "$BUILDER_IMAGE" == "none" ]]; then GOOS="$TARGET_OS" GOARCH="$TARGET_ARCH" CGO_ENABLED=0 go build -v -ldflags \ diff --git a/cmd/client/command/client.go b/cmd/client/command/client.go new file mode 100644 index 00000000..12277a06 --- /dev/null +++ b/cmd/client/command/client.go @@ -0,0 +1,86 @@ +/* + * 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 command + +import ( + "encoding/json" + "errors" + "reflect" + + "github.com/go-resty/resty/v2" +) + +const ( + apiVersionV1 = "/api/v1" + + defaultHost = "http://127.0.0.1:9379" +) + +type client struct { + restyCli *resty.Client + host string +} + +type ErrorMessage struct { + Message string `json:"message"` +} + +type response struct { + Error *ErrorMessage `json:"error"` + Data any `json:"data"` +} + +func newClient(host string) *client { + if host == "" { + host = defaultHost + } + restyCli := resty.New().SetBaseURL(host + apiVersionV1) + return &client{ + restyCli: restyCli, + host: host, + } +} + +func unmarshalData(body []byte, v any) error { + if len(body) == 0 { + return errors.New("empty response body") + } + + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return errors.New("unmarshal receiver was non-pointer") + } + + var rsp response + rsp.Data = v + return json.Unmarshal(body, &rsp) +} + +func unmarshalError(body []byte) error { + var rsp response + if err := json.Unmarshal(body, &rsp); err != nil { + return err + } + if rsp.Error != nil { + return errors.New(rsp.Error.Message) + } + return nil +} diff --git a/cmd/client/command/consts.go b/cmd/client/command/consts.go new file mode 100644 index 00000000..59b6ef5a --- /dev/null +++ b/cmd/client/command/consts.go @@ -0,0 +1,28 @@ +/* + * 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 command + +const ( + ResourceNamespace = "namespace" + ResourceCluster = "cluster" + ResourceShard = "shard" + ResourceNode = "node" +) diff --git a/cmd/client/command/create.go b/cmd/client/command/create.go new file mode 100644 index 00000000..514079fd --- /dev/null +++ b/cmd/client/command/create.go @@ -0,0 +1,207 @@ +/* + * 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 command + +import ( + "errors" + "strconv" + "strings" + + "github.com/spf13/cobra" +) + +type CreateOptions struct { + namespace string + cluster string + shard int + replica int + nodes []string + password string +} + +var createOptions CreateOptions + +var CreateCommand = &cobra.Command{ + Use: "create", + Short: "Create a resource", + Example: ` +# Create a namespace +kvctl create namespace + +# Create a cluster in the namespace +kvctl create cluster -n --replica 1 --nodes 127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381 + +# Create a shard in the cluster +kvctl create shard -n -c --nodes 127.0.0.1:6379,127.0.0.1:6380 + +# Create nodes in the cluster +kvctl create node 127.0.0.1:6379 -n -c --shard +`, + PreRunE: createPreRun, + RunE: func(cmd *cobra.Command, args []string) error { + host, _ := cmd.Flags().GetString("host") + client := newClient(host) + switch strings.ToLower(args[0]) { + case ResourceNamespace: + if len(args) < 2 { + return errors.New("missing namespace name") + } + return createNamespace(client, args[1]) + case ResourceCluster: + if len(args) < 2 { + return errors.New("missing cluster name") + } + createOptions.cluster = args[1] + return createCluster(client, &createOptions) + case ResourceShard: + return createShard(client, &createOptions) + case ResourceNode: + if len(args) < 2 { + return errors.New("missing node address") + } + createOptions.nodes = []string{args[1]} + return createNodes(client, &createOptions) + default: + return errors.New("unsupported resource type, please specify one of [namespace, cluster, shard, nodes]") + } + }, + SilenceUsage: true, + SilenceErrors: true, +} + +func createPreRun(_ *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("missing resource type, please specify one of [namespace, cluster, shard, node]") + } + resource := strings.ToLower(args[0]) + if resource == "namespace" { + return nil + } + if createOptions.namespace == "" { + return errors.New("missing namespace, please specify the namespace via -n or --namespace option") + } + if resource != "node" && createOptions.nodes == nil { + return errors.New("missing nodes, please specify the nodes via --nodes option") + } + if resource == "cluster" { + return nil + } + if createOptions.cluster == "" { + return errors.New("missing cluster, please specify the cluster via -c or --cluster option") + } + if resource == "shard" { + return nil + } + if createOptions.shard == -1 { + return errors.New("missing shard, please specify the shard via -s or --shard option") + } + if createOptions.shard < 0 { + return errors.New("shard must be a positive number") + } + return nil +} + +func createNamespace(cli *client, name string) error { + rsp, err := cli.restyCli.R(). + SetBody(map[string]string{"namespace": name}). + Post("/namespaces") + if err != nil { + return err + } + + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("create namespace: %s successfully.", name) + return nil +} + +func createCluster(cli *client, options *CreateOptions) error { + rsp, err := cli.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetBody(map[string]interface{}{ + "name": options.cluster, + "replica": options.replica, + "nodes": options.nodes, + "password": options.password, + }). + Post("/namespaces/{namespace}/clusters") + if err != nil { + return err + } + + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("create cluster: %s successfully.", options.cluster) + return nil +} + +func createShard(cli *client, options *CreateOptions) error { + rsp, err := cli.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetPathParam("cluster", options.cluster). + SetBody(map[string]interface{}{ + "name": options.cluster, + "nodes": options.nodes, + "password": options.password, + }). + Post("/namespaces/{namespace}/clusters/{cluster}/shards") + if err != nil { + return err + } + + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("create the new shard successfully.") + return nil +} + +func createNodes(cli *client, options *CreateOptions) error { + rsp, err := cli.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetPathParam("cluster", options.cluster). + SetPathParam("shard", strconv.Itoa(options.shard)). + SetBody(map[string]interface{}{ + "addr": options.nodes[0], + "password": options.password, + }). + Post("/namespaces/{namespace}/clusters/{cluster}/shards/{shard}/nodes") + if err != nil { + return err + } + + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("create node: %v successfully.", options.nodes[0]) + return nil +} + +func init() { + CreateCommand.Flags().StringVarP(&createOptions.namespace, "namespace", "n", "", "The namespace") + CreateCommand.Flags().StringVarP(&createOptions.cluster, "cluster", "c", "", "The cluster") + CreateCommand.Flags().IntVarP(&createOptions.shard, "shard", "s", -1, "The shard number") + CreateCommand.Flags().IntVarP(&createOptions.replica, "replica", "r", 1, "The replica number") + CreateCommand.Flags().StringSliceVarP(&createOptions.nodes, "nodes", "", nil, "The node list") + CreateCommand.Flags().StringVarP(&createOptions.password, "password", "", "", "The password") +} diff --git a/cmd/client/command/delete.go b/cmd/client/command/delete.go new file mode 100644 index 00000000..bcfdca0a --- /dev/null +++ b/cmd/client/command/delete.go @@ -0,0 +1,185 @@ +/* + * 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 command + +import ( + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" +) + +type DeleteOptions struct { + namespace string + cluster string + shard int +} + +var deleteOptions DeleteOptions + +var DeleteCommand = &cobra.Command{ + Use: "delete", + Short: "Delete a resource", + Example: ` +# Delete a namespace +kvctl delete namespace + +# Delete a cluster in the namespace +kvctl delete cluster -n + +# Delete a shard in the cluster +kvctl delete shard -n -c + +# Delete a node in the cluster +kvctl delete node -n -c --shard +`, + PreRunE: deletePreRun, + RunE: func(cmd *cobra.Command, args []string) error { + resource := strings.ToLower(args[0]) + if len(args) < 2 { + return fmt.Errorf("missing resource name") + } + host, _ := cmd.Flags().GetString("host") + client := newClient(host) + switch resource { + case ResourceNamespace: + namespace := args[1] + return deleteNamespace(client, namespace) + case ResourceCluster: + deleteOptions.cluster = args[1] + return deleteCluster(client, &deleteOptions) + case ResourceShard: + shard, err := strconv.Atoi(args[1]) + if err != nil { + return err + } + deleteOptions.shard = shard + return deleteShard(client, &deleteOptions) + case ResourceNode: + nodeID := args[1] + return deleteNode(client, &deleteOptions, nodeID) + default: + return fmt.Errorf("unsupported resource type %s", resource) + } + }, + SilenceUsage: true, + SilenceErrors: true, +} + +func deletePreRun(_ *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("missing resource type") + } + + resource := strings.ToLower(args[0]) + if resource == "namespace" { + return nil + } + if deleteOptions.namespace == "" { + return fmt.Errorf("missing namespace, please specify the namespace via -n or --namespace option") + } + if resource == "cluster" { + return nil + } + if deleteOptions.cluster == "" { + return fmt.Errorf("missing cluster, please specify the cluster via -c or --cluster option") + } + if resource == "shard" { + return nil + } + if deleteOptions.shard == -1 { + return fmt.Errorf("missing shard, please specify the shard via -s or --shard option") + } + if deleteOptions.shard < 0 { + return fmt.Errorf("invalid shard %d", deleteOptions.shard) + } + return nil +} + +func deleteNamespace(client *client, namespace string) error { + rsp, err := client.restyCli.R(). + SetPathParam("namespace", namespace). + Delete("/namespaces/{namespace}") + if err != nil { + return err + } + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("delete namespace: %s successfully.", namespace) + return nil +} + +func deleteCluster(client *client, options *DeleteOptions) error { + rsp, err := client.restyCli.R(). + SetPathParams(map[string]string{ + "namespace": options.namespace, + "cluster": options.cluster, + }).Delete("/namespaces/{namespace}/clusters/{cluster}") + if err != nil { + return err + } + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("delete cluster: %s successfully.", options.cluster) + return nil +} + +func deleteShard(client *client, options *DeleteOptions) error { + rsp, err := client.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetPathParam("cluster", options.cluster). + SetPathParam("shard", strconv.Itoa(options.shard)). + Delete("/namespaces/{namespace}/clusters/{cluster}/shards/{shard}") + if err != nil { + return err + } + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("delete shard %d successfully.", options.shard) + return nil +} + +func deleteNode(client *client, options *DeleteOptions, nodeID string) error { + rsp, err := client.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetPathParam("cluster", options.cluster). + SetPathParam("shard", strconv.Itoa(options.shard)). + SetPathParam("node", nodeID). + Delete("/namespaces/{namespace}/clusters/{cluster}/shards/{shard}/nodes/{node}") + if err != nil { + return err + } + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + printLine("delete node: %s successfully.", nodeID) + return nil +} + +func init() { + DeleteCommand.Flags().StringVarP(&deleteOptions.namespace, "namespace", "n", "", "The namespace") + DeleteCommand.Flags().StringVarP(&deleteOptions.cluster, "cluster", "c", "", "The cluster") + DeleteCommand.Flags().IntVarP(&deleteOptions.shard, "shard", "s", -1, "The shard") +} diff --git a/cmd/client/command/get.go b/cmd/client/command/get.go new file mode 100644 index 00000000..73a51b3b --- /dev/null +++ b/cmd/client/command/get.go @@ -0,0 +1,103 @@ +/* + * 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 command + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/apache/kvrocks-controller/store" +) + +type GetOptions struct { + namespace string + cluster string + shard int +} + +var getOptions GetOptions + +var GetCommand = &cobra.Command{ + Use: "get", + Short: "Get a resource", + Example: ` +# Get a cluster +kvctl get cluster -n +`, + PreRunE: getPreRun, + RunE: func(cmd *cobra.Command, args []string) error { + host, _ := cmd.Flags().GetString("host") + client := newClient(host) + if len(args) < 2 { + return fmt.Errorf("missing resource name, must be one of [cluster, shard]") + } + resource := strings.ToLower(args[0]) + switch resource { + case "cluster": + getOptions.cluster = args[1] + return getCluster(client, &getOptions) + default: + return fmt.Errorf("unsupported resource type %s", resource) + } + }, + SilenceUsage: true, + SilenceErrors: true, +} + +func getPreRun(_ *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("missing resource type") + } + + if getOptions.namespace == "" { + return fmt.Errorf("missing namespace, please specify the namespace via -n or --namespace option") + } + return nil +} + +func getCluster(client *client, options *GetOptions) error { + rsp, err := client.restyCli.R().SetPathParams(map[string]string{ + "namespace": options.namespace, + "cluster": options.cluster, + }).Get("/namespaces/{namespace}/clusters/{cluster}") + if err != nil { + return err + } + if rsp.IsError() { + return unmarshalError(rsp.Body()) + } + + var result struct { + Cluster *store.Cluster `json:"cluster"` + } + if err := unmarshalData(rsp.Body(), &result); err != nil { + return err + } + printCluster(result.Cluster) + return nil +} + +func init() { + GetCommand.Flags().StringVarP(&getOptions.namespace, "namespace", "n", "", "The namespace of the resource") + GetCommand.Flags().StringVarP(&getOptions.cluster, "cluster", "c", "", "The cluster of the resource") +} diff --git a/cmd/client/command/helper.go b/cmd/client/command/helper.go new file mode 100644 index 00000000..1d076774 --- /dev/null +++ b/cmd/client/command/helper.go @@ -0,0 +1,62 @@ +/* + * 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 command + +import ( + "fmt" + "os" + "strings" + + "github.com/fatih/color" + + "github.com/olekukonko/tablewriter" + + "github.com/apache/kvrocks-controller/store" +) + +func printLine(format string, a ...interface{}) { + boldColor := color.New(color.Bold) + _, _ = fmt.Fprintln(os.Stdout, boldColor.Sprintf(format, a...)) +} + +func printCluster(cluster *store.Cluster) { + writer := tablewriter.NewWriter(os.Stdout) + printLine("") + printLine("cluster: %s", cluster.Name) + printLine("version: %d\n", cluster.Version.Load()) + writer.SetHeader([]string{"SHARD", "NODE_ID", "ADDRESS", "ROLE", "MIGRATING"}) + writer.SetCenterSeparator("|") + for i, shard := range cluster.Shards { + for _, node := range shard.Nodes { + role := strings.ToUpper(store.RoleSlave) + if node.IsMaster() { + role = strings.ToUpper(store.RoleMaster) + } + migratingStatus := "NO" + if shard.MigratingSlot != -1 { + migratingStatus = fmt.Sprintf("%d --> %d", shard.MigratingSlot, shard.TargetShardIndex) + } + columns := []string{fmt.Sprintf("%d", i), node.ID(), node.Addr(), role, migratingStatus} + writer.Append(columns) + } + } + writer.Render() +} diff --git a/cmd/client/command/import.go b/cmd/client/command/import.go new file mode 100644 index 00000000..51107e6f --- /dev/null +++ b/cmd/client/command/import.go @@ -0,0 +1,101 @@ +/* + * 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 command + +import ( + "errors" + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +type ImportOptions struct { + namespace string + cluster string + nodes []string + password string +} + +var importOptions ImportOptions + +var ImportCommand = &cobra.Command{ + Use: "import", + Short: "Import data from a cluster", + Example: ` +# Import a cluster from nodes +kvctl import cluster --nodes 127.0.0.1:6379,127.0.0.1:6380 +`, + PreRunE: importPreRun, + RunE: func(cmd *cobra.Command, args []string) error { + host, _ := cmd.Flags().GetString("host") + client := newClient(host) + resource := strings.ToLower(args[0]) + switch resource { + case ResourceCluster: + importOptions.cluster = args[1] + return importCluster(client, &importOptions) + default: + return fmt.Errorf("unsupported resource type: %s", resource) + } + }, + SilenceUsage: true, + SilenceErrors: true, +} + +func importPreRun(_ *cobra.Command, args []string) error { + if len(args) < 2 { + return errors.New("missing resource name") + } + if importOptions.namespace == "" { + return errors.New("missing namespace, please specify with -n or --namespace") + } + if len(importOptions.nodes) == 0 { + return errors.New("missing nodes") + } + return nil +} + +func importCluster(client *client, options *ImportOptions) error { + rsp, err := client.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetPathParam("cluster", options.cluster). + SetBody(map[string]interface{}{ + "nodes": options.nodes, + "password": options.password, + }). + Post("/namespaces/{namespace}/clusters/{cluster}/import") + if err != nil { + return err + } + if rsp.IsError() { + return errors.New(rsp.String()) + } + printLine("import cluster: %s successfully.", options.cluster) + return nil +} + +func init() { + ImportCommand.Flags().StringVarP(&importOptions.namespace, "namespace", "n", "", "The namespace of the cluster") + ImportCommand.Flags().StringVarP(&importOptions.cluster, "cluster", "c", "", "The cluster name") + ImportCommand.Flags().StringSliceVarP(&importOptions.nodes, "nodes", "", nil, "The nodes to import from") + ImportCommand.Flags().StringVarP(&importOptions.password, "password", "p", "", "The password of the cluster") +} diff --git a/cmd/client/command/list.go b/cmd/client/command/list.go new file mode 100644 index 00000000..e7f628d2 --- /dev/null +++ b/cmd/client/command/list.go @@ -0,0 +1,133 @@ +/* + * 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 command + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +var listOptions struct { + namespace string + cluster string +} + +var ListCommand = &cobra.Command{ + Use: "list", + Short: "Display all resources", + Example: ` +# Display all namespaces +kvctl list namespaces + +# Display all clusters in the namespace +kvctl list clusters -n + +# Display all nodes in the cluster +kvctl list nodes -n -c +`, + ValidArgs: []string{"namespaces", "clusters", "shards", "nodes"}, + RunE: func(cmd *cobra.Command, args []string) error { + host, _ := cmd.Flags().GetString("host") + client := newClient(host) + switch strings.ToLower(args[0]) { + case "namespaces": + return listNamespace(client) + case "clusters": + return listClusters(client) + default: + return fmt.Errorf("unsupported resource type %s", args[0]) + } + }, + PreRunE: listPreRun, + SilenceUsage: true, + SilenceErrors: true, +} + +func listPreRun(_ *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("missing resource type, please specify one of [namespaces, clusters, nodes]") + } + + resource := strings.ToLower(args[0]) + if resource == "namespaces" { + return nil + } + if listOptions.namespace == "" { + return fmt.Errorf("missing namespace, please specify the namespace via -n or --namespace option") + } + if resource == "nodes" && listOptions.cluster == "" { + return fmt.Errorf("missing cluster, please specify the cluster via -c or --cluster option") + } + return nil +} + +func listNamespace(cli *client) error { + rsp, err := cli.restyCli.R().Get("/namespaces") + if err != nil { + return err + } + + var result struct { + Namespaces []string `json:"namespaces"` + } + if err := unmarshalData(rsp.Body(), &result); err != nil { + return err + } + if len(result.Namespaces) == 0 { + printLine("no namespace found.") + return nil + } + for _, ns := range result.Namespaces { + printLine(ns) + } + return nil +} + +func listClusters(cli *client) error { + rsp, err := cli.restyCli.R(). + SetPathParam("namespace", listOptions.namespace). + Get("/namespaces/{namespace}/clusters") + if err != nil { + return err + } + + var result struct { + Clusters []string `json:"clusters"` + } + if err := unmarshalData(rsp.Body(), &result); err != nil { + return err + } + if len(result.Clusters) == 0 { + printLine("no cluster found.") + return nil + } + for _, cluster := range result.Clusters { + printLine(cluster) + } + return nil +} + +func init() { + ListCommand.Flags().StringVarP(&listOptions.namespace, "namespace", "n", "", "The namespace") + ListCommand.Flags().StringVarP(&listOptions.cluster, "cluster", "c", "", "The cluster") +} diff --git a/cmd/client/command/migrate.go b/cmd/client/command/migrate.go new file mode 100644 index 00000000..c8ccbac3 --- /dev/null +++ b/cmd/client/command/migrate.go @@ -0,0 +1,119 @@ +/* + * 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 command + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" +) + +type MigrationOptions struct { + namespace string + cluster string + slot int + target int + slotOnly bool +} + +var migrateOptions MigrationOptions + +var MigrateCommand = &cobra.Command{ + Use: "migrate", + Short: "Migrate slot to another node", + Example: ` +# Migrate slot between cluster shards +kvctl migrate slot --target -n -c +`, + PreRunE: migrationPreRun, + RunE: func(cmd *cobra.Command, args []string) error { + host, _ := cmd.Flags().GetString("host") + client := newClient(host) + resource := strings.ToLower(args[0]) + switch resource { + case "slot": + return migrateSlot(client, &migrateOptions) + default: + return fmt.Errorf("unsupported resource type: %s", resource) + } + }, + SilenceUsage: true, + SilenceErrors: true, +} + +func migrationPreRun(_ *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("resource type should be specified") + } + if len(args) < 2 { + return fmt.Errorf("the slot number should be specified") + } + slot, err := strconv.Atoi(args[1]) + if err != nil { + return fmt.Errorf("invalid slot number: %s", args[1]) + } + if slot < 0 || slot > 16383 { + return errors.New("slot number should be in range [0, 16383]") + } + migrateOptions.slot = slot + + if migrateOptions.namespace == "" { + return fmt.Errorf("namespace is required, please specify with -n or --namespace") + } + if migrateOptions.cluster == "" { + return fmt.Errorf("cluster is required, please specify with -c or --cluster") + } + if migrateOptions.target < 0 { + return fmt.Errorf("target is required, please specify with --target") + } + return nil +} + +func migrateSlot(client *client, options *MigrationOptions) error { + rsp, err := client.restyCli.R(). + SetPathParam("namespace", options.namespace). + SetPathParam("cluster", options.cluster). + SetBody(map[string]interface{}{ + "slot": options.slot, + "target": options.target, + "slotOnly": strconv.FormatBool(options.slotOnly), + }). + Post("/namespaces/{namespace}/clusters/{cluster}/migrate") + if err != nil { + return err + } + if rsp.IsError() { + return errors.New(rsp.String()) + } + printLine("migrate slot[%d] task is submitted successfully.", options.slot) + return nil +} + +func init() { + MigrateCommand.Flags().IntVar(&migrateOptions.slot, "slot", -1, "The slot to migrate") + MigrateCommand.Flags().IntVar(&migrateOptions.target, "target", -1, "The target node") + MigrateCommand.Flags().StringVarP(&migrateOptions.namespace, "namespace", "n", "", "The namespace") + MigrateCommand.Flags().StringVarP(&migrateOptions.cluster, "cluster", "c", "", "The cluster") + MigrateCommand.Flags().BoolVar(&migrateOptions.slotOnly, "slot-only", false, "Only migrate slot and ignore the existing data") +} diff --git a/cmd/client/main.go b/cmd/client/main.go index bac6575d..93e22516 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -20,5 +20,45 @@ package main +import ( + "fmt" + "os" + + "github.com/fatih/color" + + "github.com/spf13/cobra" + + "github.com/apache/kvrocks-controller/cmd/client/command" +) + +var rootCommand = &cobra.Command{ + Use: "kvctl", + Short: "kvctl is a command line tool for the Kvrocks controller service", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println(cmd.PersistentFlags().GetString("host")) + _, _ = color.New(color.Bold).Println("Run 'kvctl --help' for usage.") + os.Exit(0) + }, +} + func main() { + if err := rootCommand.Execute(); err != nil { + color.Red("error: %v", err) + os.Exit(1) + } +} + +func init() { + rootCommand.PersistentFlags().StringP("host", "H", + "http://127.0.0.1:9379", "The host of the Kvrocks controller service") + + rootCommand.AddCommand(command.ListCommand) + rootCommand.AddCommand(command.CreateCommand) + rootCommand.AddCommand(command.GetCommand) + rootCommand.AddCommand(command.DeleteCommand) + rootCommand.AddCommand(command.ImportCommand) + rootCommand.AddCommand(command.MigrateCommand) + + rootCommand.SilenceUsage = true + rootCommand.SilenceErrors = true } diff --git a/consts/context_key.go b/consts/context_key.go index f77ab014..d0d20809 100644 --- a/consts/context_key.go +++ b/consts/context_key.go @@ -23,9 +23,10 @@ const ( ContextKeyStore = "_context_key_storage" ContextKeyCluster = "_context_key_cluster" ContextKeyClusterShard = "_context_key_cluster_shard" + ContextKeyHost = "_context_key_host" ) const ( - HeaderIsRedirect = "X-Is-Redirect" - HeaderDontDetectHost = "X-Dont-Detect-Host" + HeaderIsRedirect = "X-Is-Redirect" + HeaderDontCheckClusterMode = "X-Dont-Check-Cluster-Mode" ) diff --git a/consts/errors.go b/consts/errors.go index a44c0328..8ad1034a 100644 --- a/consts/errors.go +++ b/consts/errors.go @@ -25,6 +25,7 @@ import "errors" var ( ErrInvalidArgument = errors.New("invalid argument") ErrNotFound = errors.New("not found") + ErrForbidden = errors.New("forbidden") ErrAlreadyExists = errors.New("already exists") ErrIndexOutOfRange = errors.New("index out of range") ErrShardIsSame = errors.New("source and target shard is same") @@ -36,6 +37,4 @@ var ( ErrShardIsServicing = errors.New("shard is servicing") ErrShardSlotIsMigrating = errors.New("shard slot is migrating") ErrShardNoMatchNewMaster = errors.New("no match new master in shard") - ErrMismatchMigrateSlot = errors.New("mismatch migrate slot") - ErrMigrateSlotFailed = errors.New("migrate slot failed") ) diff --git a/controller/controller.go b/controller/controller.go index 933553d2..cd302d7f 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -126,6 +126,7 @@ func (c *Controller) syncLoop(ctx context.Context) { prevTermLeader := "" if c.clusterStore.IsLeader() { c.becomeLeader(ctx, prevTermLeader) + prevTermLeader = c.clusterStore.ID() } c.readyCh <- struct{}{} @@ -133,8 +134,10 @@ func (c *Controller) syncLoop(ctx context.Context) { select { case <-c.clusterStore.LeaderChange(): if c.clusterStore.IsLeader() { - c.becomeLeader(ctx, prevTermLeader) - prevTermLeader = c.clusterStore.ID() + if prevTermLeader != c.clusterStore.ID() { + c.becomeLeader(ctx, prevTermLeader) + prevTermLeader = c.clusterStore.ID() + } } else { if prevTermLeader != c.clusterStore.ID() { continue diff --git a/go.mod b/go.mod index 2cbf015c..d03e7a5a 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,27 @@ module github.com/apache/kvrocks-controller go 1.19 require ( - github.com/c-bata/go-prompt v0.2.6 - github.com/gin-gonic/gin v1.7.4 - github.com/go-playground/validator/v10 v10.9.0 + github.com/gin-gonic/gin v1.9.1 + github.com/go-playground/validator/v10 v10.14.0 github.com/go-redis/redis/v8 v8.11.5 - github.com/go-resty/resty/v2 v2.7.0 + github.com/go-resty/resty/v2 v2.12.0 github.com/go-zookeeper/zk v1.0.3 - github.com/google/uuid v1.3.0 - github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/olekukonko/tablewriter v0.0.5 github.com/prometheus/client_golang v1.11.1 - github.com/steinfletcher/apitest v1.5.15 - github.com/stretchr/testify v1.7.4 + github.com/spf13/cobra v1.8.0 + github.com/stretchr/testify v1.8.3 go.etcd.io/etcd v3.3.27+incompatible go.etcd.io/etcd/client/v3 v3.5.4 go.uber.org/atomic v1.7.0 go.uber.org/zap v1.21.0 - golang.org/x/net v0.0.0-20211029224645-99673261e6eb gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 ) require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.9.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/coreos/etcd v3.3.27+incompatible // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect @@ -32,36 +31,42 @@ require ( github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/json-iterator/go v1.1.11 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/mattn/go-colorable v0.1.7 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mattn/go-tty v0.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/pkg/term v1.2.0-beta.2 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/ugorji/go/codec v1.1.7 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect go.etcd.io/etcd/api/v3 v3.5.4 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect go.uber.org/multierr v1.6.0 // indirect - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect google.golang.org/grpc v1.38.0 // indirect - google.golang.org/protobuf v1.26.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 31d49c96..89003fbc 100644 --- a/go.sum +++ b/go.sum @@ -13,12 +13,16 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= -github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -32,7 +36,7 @@ github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzA github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23 h1:SrdboTJZnOqc2r4cT4wQCzQJjGYwkclLwx2sPrDsx7g= github.com/coreos/pkg v0.0.0-20230327231512-ba87abf18a23/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -44,36 +48,38 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= -github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= -github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= -github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-resty/resty/v2 v2.12.0 h1:rsVL8P90LFvkUYq/V5BTVe203WfRIU4gvcf+yfzJzGA= +github.com/go-resty/resty/v2 v2.12.0/go.mod h1:o0yGPrkS3lOe1+eFajk6kBW8ScXzwU3hD69/gt2yB/0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -105,70 +111,64 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= -github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= -github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= -github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= -github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -190,35 +190,38 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -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/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/steinfletcher/apitest v1.5.15 h1:AAdTN0yMbf0VMH/PMt9uB2I7jljepO6i+5uhm1PjH3c= -github.com/steinfletcher/apitest v1.5.15/go.mod h1:mF+KnYaIkuHM0C4JgGzkIIOJAEjo+EA5tTjJ+bHXnQc= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= -github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/etcd v3.3.27+incompatible h1:5hMrpf6REqTHV2LW2OclNpRtxI0k9ZplMemJsMSWju0= go.etcd.io/etcd v3.3.27+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= @@ -236,12 +239,17 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -252,6 +260,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -267,8 +277,12 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM= -golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -280,43 +294,53 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -328,6 +352,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -358,15 +384,14 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= @@ -385,4 +410,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/server/api/cluster.go b/server/api/cluster.go index 1a073278..3d1e8967 100644 --- a/server/api/cluster.go +++ b/server/api/cluster.go @@ -17,10 +17,12 @@ * under the License. * */ + package api import ( "errors" + "strings" "github.com/gin-gonic/gin" @@ -36,8 +38,8 @@ type MigrateSlotRequest struct { } type CreateClusterRequest struct { - Name string `json:"name"` - Nodes []string `json:"nodes"` + Name string `json:"name" validate:"required"` + Nodes []string `json:"nodes" validate:"required"` Password string `json:"password"` Replicas int `json:"replicas"` } @@ -49,7 +51,7 @@ type ClusterHandler struct { func (handler *ClusterHandler) List(c *gin.Context) { namespace := c.Param("namespace") clusters, err := handler.s.ListCluster(c, namespace) - if err != nil { + if err != nil && !errors.Is(err, consts.ErrNotFound) { helper.ResponseError(c, err) return } @@ -69,13 +71,35 @@ func (handler *ClusterHandler) Create(c *gin.Context) { return } + clusterStore := handler.s + if err := clusterStore.CheckNewNodes(c, req.Nodes); err != nil { + helper.ResponseError(c, err) + return + } + cluster, err := store.NewCluster(req.Name, req.Nodes, req.Replicas) if err != nil { helper.ResponseBadRequest(c, err) return } cluster.SetPassword(req.Password) - if err := handler.s.CreateCluster(c, namespace, cluster); err != nil { + checkClusterMode := strings.ToLower(c.GetHeader(consts.HeaderDontCheckClusterMode)) == "yes" + for _, node := range cluster.GetNodes() { + if !checkClusterMode { + break + } + version, err := node.CheckClusterMode(c) + if err != nil { + helper.ResponseError(c, err) + return + } + if version != -1 { + helper.ResponseBadRequest(c, errors.New("node is already in cluster mode")) + return + } + } + + if err := clusterStore.CreateCluster(c, namespace, cluster); err != nil { helper.ResponseError(c, err) return } @@ -126,7 +150,7 @@ func (handler *ClusterHandler) Import(c *gin.Context) { namespace := c.Param("namespace") clusterName := c.Param("cluster") var req struct { - Nodes []string `json:"nodes"` + Nodes []string `json:"nodes" validate:"required"` Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { @@ -151,6 +175,15 @@ func (handler *ClusterHandler) Import(c *gin.Context) { } cluster.SetPassword(req.Password) + newNodes := make([]string, 0) + for _, node := range cluster.GetNodes() { + newNodes = append(newNodes, node.Addr()) + } + if err := handler.s.CheckNewNodes(c, newNodes); err != nil { + helper.ResponseError(c, err) + return + } + cluster.Name = clusterName if err := handler.s.CreateCluster(c, namespace, cluster); err != nil { helper.ResponseError(c, err) diff --git a/server/api/cluster_test.go b/server/api/cluster_test.go index 784128bb..005ba4b0 100644 --- a/server/api/cluster_test.go +++ b/server/api/cluster_test.go @@ -52,6 +52,7 @@ func TestClusterBasics(t *testing.T) { body, err := json.Marshal(testCreateRequest) require.NoError(t, err) + ctx.Header(consts.HeaderDontCheckClusterMode, "yes") ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body)) ctx.Params = []gin.Param{{Key: "namespace", Value: ns}} diff --git a/server/api/namespace.go b/server/api/namespace.go index 505b979a..05418857 100644 --- a/server/api/namespace.go +++ b/server/api/namespace.go @@ -17,6 +17,7 @@ * under the License. * */ + package api import ( @@ -60,7 +61,7 @@ func (handler *NamespaceHandler) Exists(c *gin.Context) { func (handler *NamespaceHandler) Create(c *gin.Context) { var request struct { - Namespace string `json:"namespace"` + Namespace string `json:"namespace" validate:"required"` } if err := c.BindJSON(&request); err != nil { helper.ResponseBadRequest(c, err) diff --git a/server/api/node.go b/server/api/node.go index 47d89e3a..878798b1 100644 --- a/server/api/node.go +++ b/server/api/node.go @@ -17,6 +17,7 @@ * under the License. * */ + package api import ( @@ -43,13 +44,16 @@ func (handler *NodeHandler) Create(c *gin.Context) { cluster, _ := c.MustGet(consts.ContextKeyCluster).(*store.Cluster) var req struct { Addr string `json:"addr" binding:"required"` - Role string `json:"role" binding:"required"` + Role string `json:"role"` Password string `json:"password"` } if err := c.ShouldBindJSON(&req); err != nil { helper.ResponseBadRequest(c, err) return } + if req.Role == "" { + req.Role = store.RoleSlave + } shardIndex, _ := strconv.Atoi(c.Param("shard")) err := cluster.AddNode(shardIndex, req.Addr, req.Role, req.Password) if err != nil { diff --git a/server/api/shard.go b/server/api/shard.go index 9b36ced9..9ccf258d 100644 --- a/server/api/shard.go +++ b/server/api/shard.go @@ -56,7 +56,7 @@ func (handler *ShardHandler) Get(c *gin.Context) { func (handler *ShardHandler) Create(c *gin.Context) { ns := c.Param("namespace") var req struct { - Nodes []string `json:"nodes"` + Nodes []string `json:"nodes" validate:"required"` Password string `json:"password"` } if err := c.BindJSON(&req); err != nil { diff --git a/server/helper/helper.go b/server/helper/helper.go index 22851e24..3f1eb4b3 100644 --- a/server/helper/helper.go +++ b/server/helper/helper.go @@ -69,6 +69,8 @@ func ResponseError(c *gin.Context, err error) { code = http.StatusBadRequest } else if errors.Is(err, consts.ErrAlreadyExists) { code = http.StatusConflict + } else if errors.Is(err, consts.ErrForbidden) { + code = http.StatusForbidden } else if errors.Is(err, consts.ErrInvalidArgument) { code = http.StatusBadRequest } diff --git a/server/middleware/middleware.go b/server/middleware/middleware.go index 7955825a..9ccfbe3e 100644 --- a/server/middleware/middleware.go +++ b/server/middleware/middleware.go @@ -25,14 +25,13 @@ import ( "strconv" "time" - "github.com/apache/kvrocks-controller/server/helper" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" "github.com/apache/kvrocks-controller/consts" "github.com/apache/kvrocks-controller/metrics" + "github.com/apache/kvrocks-controller/server/helper" "github.com/apache/kvrocks-controller/store" - - "github.com/gin-gonic/gin" - "github.com/prometheus/client_golang/prometheus" ) func CollectMetrics(c *gin.Context) { @@ -88,9 +87,10 @@ func RequiredNamespace(c *gin.Context) { } if !ok { helper.ResponseBadRequest(c, errors.New("namespace not found")) - return + c.Abort() + } else { + c.Next() } - c.Next() } func RequiredCluster(c *gin.Context) { diff --git a/server/route.go b/server/route.go index ba946a9c..90d04555 100644 --- a/server/route.go +++ b/server/route.go @@ -23,6 +23,8 @@ import ( "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/apache/kvrocks-controller/server/helper" + "github.com/apache/kvrocks-controller/consts" "github.com/apache/kvrocks-controller/server/api" "github.com/apache/kvrocks-controller/server/middleware" @@ -38,6 +40,10 @@ func (srv *Server) initHandlers() { engine.Any("/debug/pprof/*profile", PProf) engine.GET("/metrics", gin.WrapH(promhttp.Handler())) + engine.NoRoute(func(c *gin.Context) { + helper.ResponseError(c, consts.ErrNotFound) + c.Abort() + }) apiV1 := engine.Group("/api/v1/") { @@ -53,9 +59,9 @@ func (srv *Server) initHandlers() { { clusters.GET("", middleware.RequiredNamespace, handler.Cluster.List) clusters.POST("", middleware.RequiredNamespace, handler.Cluster.Create) + clusters.POST("/:cluster/import", middleware.RequiredNamespace, handler.Cluster.Import) clusters.GET("/:cluster", middleware.RequiredCluster, handler.Cluster.Get) - clusters.POST("/:cluster/import", handler.Cluster.Import) - clusters.DELETE("/:cluster", handler.Cluster.Remove) + clusters.DELETE("/:cluster", middleware.RequiredCluster, handler.Cluster.Remove) clusters.POST("/:cluster/migrate", middleware.RequiredCluster, handler.Cluster.MigrateSlot) } diff --git a/store/cluster.go b/store/cluster.go index ec725861..fe8240e1 100644 --- a/store/cluster.go +++ b/store/cluster.go @@ -143,6 +143,14 @@ func (cluster *Cluster) SyncToNodes(ctx context.Context) error { return nil } +func (cluster *Cluster) GetNodes() []Node { + nodes := make([]Node, 0) + for i := 0; i < len(cluster.Shards); i++ { + nodes = append(nodes, cluster.Shards[i].Nodes...) + } + return nodes +} + func (cluster *Cluster) Reset(ctx context.Context) error { for i := 0; i < len(cluster.Shards); i++ { for _, node := range cluster.Shards[i].Nodes { diff --git a/store/cluster_node.go b/store/cluster_node.go index 5ee9520c..997a0892 100644 --- a/store/cluster_node.go +++ b/store/cluster_node.go @@ -64,6 +64,7 @@ type Node interface { GetClusterNodeInfo(ctx context.Context) (*ClusterNodeInfo, error) GetClusterInfo(ctx context.Context) (*ClusterInfo, error) SyncClusterInfo(ctx context.Context, cluster *Cluster) error + CheckClusterMode(ctx context.Context) (int64, error) MigrateSlot(ctx context.Context, slot int, NodeID string) error MarshalJSON() ([]byte, error) @@ -167,7 +168,7 @@ func (n *ClusterNode) CheckClusterMode(ctx context.Context) (int64, error) { if strings.Contains(err.Error(), "cluster is not initialized") { return -1, nil } - return -1, fmt.Errorf("error while checking node(%s) cluster mode: %w", n.addr, err) + return -1, fmt.Errorf("error while checking node cluster mode: %w", err) } return clusterInfo.CurrentEpoch, nil } diff --git a/store/store.go b/store/store.go index 84dea95e..5afeeef4 100644 --- a/store/store.go +++ b/store/store.go @@ -22,7 +22,7 @@ package store import ( "context" "encoding/json" - "errors" + "fmt" "github.com/apache/kvrocks-controller/consts" "github.com/apache/kvrocks-controller/store/engine" @@ -42,6 +42,8 @@ type Store interface { CreateCluster(ctx context.Context, ns string, cluster *Cluster) error UpdateCluster(ctx context.Context, ns string, cluster *Cluster) error SetCluster(ctx context.Context, ns string, clusterInfo *Cluster) error + + CheckNewNodes(ctx context.Context, nodes []string) error } var _ Store = (*ClusterStore)(nil) @@ -109,7 +111,7 @@ func (s *ClusterStore) RemoveNamespace(ctx context.Context, ns string) error { return err } if len(clusters) != 0 { - return errors.New("namespace wasn't empty, please remove clusters first") + return fmt.Errorf("%w: please delete clusters first", consts.ErrForbidden) } if err := s.e.Delete(ctx, appendPrefix(ns)); err != nil { return err @@ -142,11 +144,11 @@ func (s *ClusterStore) existsCluster(ctx context.Context, ns, cluster string) (b func (s *ClusterStore) GetCluster(ctx context.Context, ns, cluster string) (*Cluster, error) { value, err := s.e.Get(ctx, buildClusterKey(ns, cluster)) if err != nil { - return nil, err + return nil, fmt.Errorf("cluster: %w", err) } var clusterInfo Cluster if err = json.Unmarshal(value, &clusterInfo); err != nil { - return nil, err + return nil, fmt.Errorf("cluster: %w", err) } return &clusterInfo, nil } @@ -155,7 +157,7 @@ func (s *ClusterStore) GetCluster(ctx context.Context, ns, cluster string) (*Clu func (s *ClusterStore) UpdateCluster(ctx context.Context, ns string, clusterInfo *Cluster) error { clusterInfo.Version.Inc() if err := s.SetCluster(ctx, ns, clusterInfo); err != nil { - return err + return fmt.Errorf("cluster: %w", err) } s.EmitEvent(EventPayload{ Namespace: ns, @@ -168,18 +170,18 @@ func (s *ClusterStore) UpdateCluster(ctx context.Context, ns string, clusterInfo func (s *ClusterStore) SetCluster(ctx context.Context, ns string, clusterInfo *Cluster) error { if len(clusterInfo.Shards) == 0 { - return errors.New("required at least one shard") + return fmt.Errorf("%w: required at least one shard", consts.ErrInvalidArgument) } value, err := json.Marshal(clusterInfo) if err != nil { - return err + return fmt.Errorf("cluster: %w", err) } return s.e.Set(ctx, buildClusterKey(ns, clusterInfo.Name), value) } func (s *ClusterStore) CreateCluster(ctx context.Context, ns string, clusterInfo *Cluster) error { if exists, _ := s.existsCluster(ctx, ns, clusterInfo.Name); exists { - return consts.ErrAlreadyExists + return fmt.Errorf("cluster: %w", consts.ErrAlreadyExists) } if err := s.SetCluster(ctx, ns, clusterInfo); err != nil { return err @@ -209,6 +211,40 @@ func (s *ClusterStore) RemoveCluster(ctx context.Context, ns, cluster string) er return nil } +func (s *ClusterStore) CheckNewNodes(ctx context.Context, nodes []string) error { + newNodes := make(map[string]bool, 0) + for _, node := range nodes { + newNodes[node] = true + } + + namespaces, err := s.ListNamespace(ctx) + if err != nil { + return err + } + existingNodes := make([]string, 0) + for _, ns := range namespaces { + clusters, err := s.ListCluster(ctx, ns) + if err != nil { + return err + } + for _, cluster := range clusters { + c, err := s.GetCluster(ctx, ns, cluster) + if err != nil { + return err + } + for _, existingNode := range c.GetNodes() { + if _, ok := newNodes[existingNode.Addr()]; ok { + existingNodes = append(existingNodes, existingNode.Addr()) + } + } + } + } + if len(existingNodes) > 0 { + return fmt.Errorf("node: %w: %v", consts.ErrAlreadyExists, existingNodes) + } + return nil +} + func (s *ClusterStore) Notify() <-chan EventPayload { return s.eventNotifyCh } diff --git a/store/store_test.go b/store/store_test.go index 496a0130..59b0fdc4 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -94,4 +94,15 @@ func TestClusterStore(t *testing.T) { require.ErrorIs(t, err, consts.ErrNotFound) } }) + + t.Run("check nodes", func(t *testing.T) { + testCluster, err := NewCluster("test-cluster-another", + []string{"127.0.0.1:1111", "127.0.0.1:2222", "127.0.0.1:3333"}, 1) + require.NoError(t, err) + + require.NoError(t, store.CreateCluster(ctx, "test-ns", testCluster)) + require.NoError(t, store.CheckNewNodes(ctx, []string{"127.0.0.1:4444", "127.0.0.1:5555"})) + require.NotNil(t, store.CheckNewNodes(ctx, []string{"127.0.0.1:3333", "127.0.0.1:4444"})) + require.NotNil(t, store.CheckNewNodes(ctx, []string{"127.0.0.1:2222", "127.0.0.1:3333"})) + }) } From 68a22985195b5b3d4fd1653d5fe2e6c33948b22e Mon Sep 17 00:00:00 2001 From: git-hulk Date: Thu, 2 May 2024 21:36:44 +0800 Subject: [PATCH 2/3] Fix lint error --- cmd/client/command/create.go | 8 ++++---- cmd/client/command/delete.go | 6 +++--- cmd/client/command/get.go | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cmd/client/command/create.go b/cmd/client/command/create.go index 514079fd..fb5fcf91 100644 --- a/cmd/client/command/create.go +++ b/cmd/client/command/create.go @@ -92,22 +92,22 @@ func createPreRun(_ *cobra.Command, args []string) error { return errors.New("missing resource type, please specify one of [namespace, cluster, shard, node]") } resource := strings.ToLower(args[0]) - if resource == "namespace" { + if resource == ResourceNamespace { return nil } if createOptions.namespace == "" { return errors.New("missing namespace, please specify the namespace via -n or --namespace option") } - if resource != "node" && createOptions.nodes == nil { + if resource != ResourceNode && createOptions.nodes == nil { return errors.New("missing nodes, please specify the nodes via --nodes option") } - if resource == "cluster" { + if resource == ResourceCluster { return nil } if createOptions.cluster == "" { return errors.New("missing cluster, please specify the cluster via -c or --cluster option") } - if resource == "shard" { + if resource == ResourceShard { return nil } if createOptions.shard == -1 { diff --git a/cmd/client/command/delete.go b/cmd/client/command/delete.go index bcfdca0a..377cf6a9 100644 --- a/cmd/client/command/delete.go +++ b/cmd/client/command/delete.go @@ -91,19 +91,19 @@ func deletePreRun(_ *cobra.Command, args []string) error { } resource := strings.ToLower(args[0]) - if resource == "namespace" { + if resource == ResourceNamespace { return nil } if deleteOptions.namespace == "" { return fmt.Errorf("missing namespace, please specify the namespace via -n or --namespace option") } - if resource == "cluster" { + if resource == ResourceCluster { return nil } if deleteOptions.cluster == "" { return fmt.Errorf("missing cluster, please specify the cluster via -c or --cluster option") } - if resource == "shard" { + if resource == ResourceShard { return nil } if deleteOptions.shard == -1 { diff --git a/cmd/client/command/get.go b/cmd/client/command/get.go index 73a51b3b..f2e965be 100644 --- a/cmd/client/command/get.go +++ b/cmd/client/command/get.go @@ -32,7 +32,6 @@ import ( type GetOptions struct { namespace string cluster string - shard int } var getOptions GetOptions From 31fc0a53aa6ef6a3b63318b40f62e38501c08882 Mon Sep 17 00:00:00 2001 From: git-hulk Date: Thu, 2 May 2024 21:42:38 +0800 Subject: [PATCH 3/3] Fix lint error --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a65dbde1..08ba3709 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. # -FROM golang:1.17 as build +FROM golang:1.20 as build WORKDIR /kvctl @@ -32,7 +32,7 @@ FROM ubuntu:focal WORKDIR /kvctl COPY --from=build /kvctl/_build/kvctl-server ./bin/ -COPY --from=build /kvctl/_build/kvctl-client ./bin/ +COPY --from=build /kvctl/_build/kvctl ./bin/ VOLUME /var/lib/kvctl