Skip to content

Commit

Permalink
[feat]: show etcd path via linctl
Browse files Browse the repository at this point in the history
  • Loading branch information
CodingCrush committed Sep 20, 2021
1 parent 4c60b87 commit 045d078
Show file tree
Hide file tree
Showing 25 changed files with 453 additions and 314 deletions.
9 changes: 5 additions & 4 deletions Makefile
Expand Up @@ -22,8 +22,10 @@ build: clean-build build-frontend build-lind ## Build executable files.
build-all: clean-frontend-build build-frontend clean-build build-lind ## Build executable files with front-end files inside.

build-lind: ## build lindb binary
env GOOS=darwin GOARCH=$(GOARCH) go build -o 'bin/lind-darwin' $(LD_FLAGS) ./cmd/
env GOOS=linux GOARCH=$(GOARCH) go build -o 'bin/lind-linux' $(LD_FLAGS) ./cmd/
env GOOS=darwin GOARCH=$(GOARCH) go build -o 'bin/lind-darwin' $(LD_FLAGS) ./cmd/lind
env GOOS=linux GOARCH=$(GOARCH) go build -o 'bin/lind-linux' $(LD_FLAGS) ./cmd/lind
env GOOS=darwin GOARCH=$(GOARCH) go build -o 'bin/lindcli-darwin' $(LD_FLAGS) ./cmd/cli
env GOOS=linux GOARCH=$(GOARCH) go build -o 'bin/lindcli-linux' $(LD_FLAGS) ./cmd/cli


GOLANGCI_LINT_VERSION ?= "v1.28.3"
Expand Down Expand Up @@ -65,8 +67,7 @@ clean-mock: ## remove all mock files
find ./ -name "*_mock.go" | xargs rm

clean-build:
rm -f bin/lind-darwin
rm -f bin/lind-linux
rm -f bin/lin*

clean-frontend-build:
cd web/ && make web_clean
Expand Down
5 changes: 2 additions & 3 deletions app/broker/api/admin/database.go
Expand Up @@ -151,9 +151,8 @@ func (d *DatabaseAPI) ListDataBase() ([]*models.Database, error) {
db := &models.Database{}
err = encoding.JSONUnmarshal(val.Value, db)
if err != nil {
logger.GetLogger("broker", "DatabaseAPI").
Warn("unmarshal data error",
logger.String("data", string(val.Value)))
d.logger.Warn("unmarshal data error",
logger.String("data", string(val.Value)))
} else {
db.Desc = db.String()
result = append(result, db)
Expand Down
4 changes: 2 additions & 2 deletions app/broker/api/login.go
Expand Up @@ -51,8 +51,8 @@ func (l *LoginAPI) Register(route gin.IRoutes) {
}

// Login responses unique token
// if use name or password is empty will responses error msg
// if use name or password is error also will responses error msg
// empty use name or password will responses error msg
// invalid use name or password will responses error msg
func (l *LoginAPI) Login(c *gin.Context) {
user := config.User{}
err := c.ShouldBind(&user)
Expand Down
20 changes: 0 additions & 20 deletions app/cli/doc.go

This file was deleted.

81 changes: 81 additions & 0 deletions cmd/cli/database.go
@@ -0,0 +1,81 @@
// Licensed to LinDB under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. LinDB licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package main

import (
"errors"
"fmt"
"os"
"strconv"

"github.com/c-bata/go-prompt"

"github.com/lindb/lindb/config"
"github.com/lindb/lindb/internal/bootstrap"
"github.com/lindb/lindb/models"
"github.com/lindb/lindb/pkg/ltoml"
"github.com/lindb/lindb/pkg/option"
)

func emptyCompleter(document prompt.Document) []prompt.Suggest { return nil }

func createDatabase() {
database := prompt.Input("database name?: ", emptyCompleter)
if len(database) == 0 {
_, _ = fmt.Fprint(os.Stderr, errors.New("database name is empty"))
os.Exit(1)
}
numOfShardsString := prompt.Input("number of shards?: ", emptyCompleter)
numOfShards, err := strconv.ParseInt(numOfShardsString, 10, 64)
if err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
replicaFactorString := prompt.Input("replica factor?: ", emptyCompleter)
replicaFactor, err := strconv.ParseInt(replicaFactorString, 10, 64)
if err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
intervalString := prompt.Input("write interval?: ", emptyCompleter)
var d ltoml.Duration
if err := d.UnmarshalText([]byte(intervalString)); err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
initializer := bootstrap.NewClusterInitializer(brokerEndpoint)
if err := initializer.InitStorageCluster(config.StorageCluster{
Name: database,
Config: cfg.Coordinator},
); err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
if err := initializer.InitInternalDatabase(models.Database{
Name: database,
Storage: database,
NumOfShard: int(numOfShards),
ReplicaFactor: int(replicaFactor),
Option: option.DatabaseOption{
Interval: intervalString,
},
}); err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
}
214 changes: 214 additions & 0 deletions cmd/cli/etcd.go
@@ -0,0 +1,214 @@
// Licensed to LinDB under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. LinDB licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package main

import (
"context"
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"github.com/c-bata/go-prompt"
"github.com/xlab/treeprint"
etcdcliv3 "go.etcd.io/etcd/clientv3"
)

type etcdContext struct {
path []string
}

var etcdCtx *etcdContext

func etcdLivePrefix() (string, bool) {
if len(etcdCtx.path) == 0 {
return "", false
}
return filepath.Join(etcdCtx.path...) + ">", true
}

func etcdExecutor(in string) {
in = strings.TrimSpace(in)

blocks := strings.Split(in, " ")
switch blocks[0] {
case "exit":
fmt.Println("Bye!")
os.Exit(0)
case "pwd":
fmt.Println(filepath.Join(etcdCtx.path...))
case "tree":
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
resp, err := etcdClient.Get(ctx, cfg.Coordinator.Namespace, etcdcliv3.WithPrefix())
if err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
buildEtcdTree(resp)
case "cd":
if len(blocks) < 2 {
return
}
switch blocks[1] {
case "..":
if len(etcdCtx.path) > 2 {
etcdCtx.path = etcdCtx.path[:len(etcdCtx.path)-1]
}
case "...":
if len(etcdCtx.path) > 3 {
etcdCtx.path = etcdCtx.path[:len(etcdCtx.path)-2]
}
default:
etcdCtx.path = append(etcdCtx.path, blocks[1])
}
case "ls":
lsEtcd()
case "cat":
if len(blocks) != 2 {
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

p := filepath.Join(etcdCtx.path...) + "/" + blocks[1]
resp, err := etcdClient.Get(ctx, p)
if err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
if len(resp.Kvs) >= 1 {
fmt.Println(string(resp.Kvs[0].Value))
}
}
}

func etcdCompleter(in prompt.Document) []prompt.Suggest {
w := in.GetWordBeforeCursor()
if w == "" {
return []prompt.Suggest{
{Text: "cd", Description: "Change path"},
{Text: "exit", Description: "Exit linctl"},
{Text: "tree", Description: "Display directory tree of etcd"},
{Text: "pwd", Description: "Show Current Path"},
{Text: "cat", Description: "Cat content"},
{Text: "ls", Description: "Show content"},
}
}
return prompt.FilterHasPrefix(getSuggestions(), w, true)
}

func getSuggestions() []prompt.Suggest {
key := filepath.Join(etcdCtx.path...)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var suggestions []prompt.Suggest

resp, err := etcdClient.Get(ctx, key, etcdcliv3.WithPrefix())
if err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
} else {
for _, kv := range resp.Kvs {
suggestions = append(suggestions, prompt.Suggest{
Text: string(kv.Key),
Description: string(kv.Value),
})
}
}
return suggestions
}

func lsEtcd() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

resp, err := etcdClient.Get(ctx, strings.Join(etcdCtx.path, "/"), etcdcliv3.WithPrefix())
if err != nil {
_, _ = fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
var m = make(map[string]struct{})
var keys []string
for _, kv := range resp.Kvs {
parts := strings.Split(string(kv.Key), "/")
idx := len(etcdCtx.path) + 1
if idx >= len(parts) {
continue
}
if idx == len(parts)-1 {
m[parts[idx]+"[f]"] = struct{}{}
} else {
m[parts[idx]+"[d]"] = struct{}{}
}
}
for key := range m {
keys = append(keys, key)
}
sort.Strings(keys)
root := treeprint.New()
tree := root
for _, p := range etcdCtx.path {
tree = tree.AddBranch(p)
}
for _, key := range keys {
_ = tree.AddNode(key)
}
fmt.Println(root.String())
}

func buildEtcdTree(resp *etcdcliv3.GetResponse) {
tree := treeprint.New()

var m = make(map[string]treeprint.Tree)
for _, kv := range resp.Kvs {
keys := strings.Split(string(kv.Key), "/")
var (
dst []string
)
for _, key := range keys {
if key == "" {
continue
}
dst = append(dst, key)
}
keys = dst

for idx := 0; idx < len(keys); idx++ {
switch idx {
case 0:
t, ok := m[keys[0]]
if !ok {
t = tree.AddBranch(keys[0])
m[keys[0]] = t
}
case len(keys) - 1:
t := m[strings.Join(keys[:idx], "/")]
_ = t.AddNode(keys[idx])
default:
t := m[strings.Join(keys[:idx], "/")]
nextKey := strings.Join(keys[:idx+1], "/")
if _, ok := m[nextKey]; !ok {
t2 := t.AddBranch(keys[idx])
m[nextKey] = t2
}
}
}
}
fmt.Println(tree.String())
}

0 comments on commit 045d078

Please sign in to comment.