parent
edb84f040e
commit
194b2c4965
|
@ -1,11 +1,11 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -26,6 +26,7 @@ const (
|
|||
noTokenMsg = "Visit https://enterprise.influxdata.com to register for updates, InfluxDB server management, and monitoring.\n"
|
||||
)
|
||||
|
||||
// CommandLine holds CLI configuration and state
|
||||
type CommandLine struct {
|
||||
Client *client.Client
|
||||
Line *liner.State
|
||||
|
@ -48,12 +49,19 @@ type CommandLine struct {
|
|||
PPS int // Controls how many points per second the import will allow via throttling
|
||||
Path string
|
||||
Compressed bool
|
||||
Quit chan struct{}
|
||||
historyFile *os.File
|
||||
}
|
||||
|
||||
// New returns an instance of CommandLine
|
||||
func New(version string) *CommandLine {
|
||||
return &CommandLine{ClientVersion: version}
|
||||
return &CommandLine{
|
||||
ClientVersion: version,
|
||||
Quit: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes the CLI
|
||||
func (c *CommandLine) Run() {
|
||||
var promptForPassword bool
|
||||
// determine if they set the password flag but provided no value
|
||||
|
@ -139,38 +147,47 @@ func (c *CommandLine) Run() {
|
|||
|
||||
c.Version()
|
||||
|
||||
var historyFile string
|
||||
var historyFilePath string
|
||||
usr, err := user.Current()
|
||||
// Only load history if we can get the user
|
||||
// Only load/write history if we can get the user
|
||||
if err == nil {
|
||||
historyFile = filepath.Join(usr.HomeDir, ".influx_history")
|
||||
|
||||
if f, err := os.Open(historyFile); err == nil {
|
||||
c.Line.ReadHistory(f)
|
||||
f.Close()
|
||||
historyFilePath = filepath.Join(usr.HomeDir, ".influx_history")
|
||||
if c.historyFile, err = os.OpenFile(historyFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0640); err == nil {
|
||||
defer c.historyFile.Close()
|
||||
c.Line.ReadHistory(c.historyFile)
|
||||
}
|
||||
}
|
||||
|
||||
Loop:
|
||||
// read from prompt until exit is run
|
||||
for {
|
||||
l, e := c.Line.Prompt("> ")
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
if c.ParseCommand(l) {
|
||||
// write out the history
|
||||
if len(historyFile) > 0 {
|
||||
select {
|
||||
case <-c.Quit:
|
||||
// write to history file
|
||||
_, err := c.Line.WriteHistory(c.historyFile)
|
||||
if err != nil {
|
||||
fmt.Printf("There was an error writing history file: %s\n", err)
|
||||
}
|
||||
// exit CLI
|
||||
break Loop
|
||||
default:
|
||||
l, e := c.Line.Prompt("> ")
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
// write valid commands only to in-memory history
|
||||
if c.ParseCommand(l) {
|
||||
c.Line.AppendHistory(l)
|
||||
if f, err := os.Create(historyFile); err == nil {
|
||||
c.Line.WriteHistory(f)
|
||||
f.Close()
|
||||
_, err := c.Line.WriteHistory(c.historyFile)
|
||||
if err != nil {
|
||||
fmt.Printf("There was an error writing history file: %s\n", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break // exit main loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ParseCommand parses an instruction and calls related method, if any
|
||||
func (c *CommandLine) ParseCommand(cmd string) bool {
|
||||
lcmd := strings.TrimSpace(strings.ToLower(cmd))
|
||||
|
||||
|
@ -184,11 +201,9 @@ func (c *CommandLine) ParseCommand(cmd string) bool {
|
|||
|
||||
if len(tokens) > 0 {
|
||||
switch tokens[0] {
|
||||
case "":
|
||||
break
|
||||
case "exit":
|
||||
// signal the program to exit
|
||||
return false
|
||||
close(c.Quit)
|
||||
case "gopher":
|
||||
c.gopher()
|
||||
case "connect":
|
||||
|
@ -221,8 +236,10 @@ func (c *CommandLine) ParseCommand(cmd string) bool {
|
|||
default:
|
||||
c.ExecuteQuery(cmd)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// Connect connects client to a server
|
||||
|
@ -255,17 +272,17 @@ func (c *CommandLine) Connect(cmd string) error {
|
|||
return fmt.Errorf("Could not create client %s", err)
|
||||
}
|
||||
c.Client = cl
|
||||
if _, v, e := c.Client.Ping(); e != nil {
|
||||
return fmt.Errorf("Failed to connect to %s\n", c.Client.Addr())
|
||||
} else {
|
||||
c.ServerVersion = v
|
||||
}
|
||||
|
||||
_, c.ServerVersion, _ = c.Client.Ping()
|
||||
var v string
|
||||
if _, v, e = c.Client.Ping(); e != nil {
|
||||
return fmt.Errorf("Failed to connect to %s\n", c.Client.Addr())
|
||||
}
|
||||
c.ServerVersion = v
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAuth sets client authentication credentials
|
||||
func (c *CommandLine) SetAuth(cmd string) {
|
||||
// If they pass in the entire command, we should parse it
|
||||
// auth <username> <password>
|
||||
|
@ -309,6 +326,7 @@ func (c *CommandLine) use(cmd string) {
|
|||
fmt.Printf("Using database %s\n", d)
|
||||
}
|
||||
|
||||
// SetPrecision sets client precision
|
||||
func (c *CommandLine) SetPrecision(cmd string) {
|
||||
// Remove the "precision" keyword if it exists
|
||||
cmd = strings.TrimSpace(strings.Replace(cmd, "precision", "", -1))
|
||||
|
@ -327,6 +345,7 @@ func (c *CommandLine) SetPrecision(cmd string) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetFormat sets output format
|
||||
func (c *CommandLine) SetFormat(cmd string) {
|
||||
// Remove the "format" keyword if it exists
|
||||
cmd = strings.TrimSpace(strings.Replace(cmd, "format", "", -1))
|
||||
|
@ -341,6 +360,7 @@ func (c *CommandLine) SetFormat(cmd string) {
|
|||
}
|
||||
}
|
||||
|
||||
// SetWriteConsistency sets cluster consistency level
|
||||
func (c *CommandLine) SetWriteConsistency(cmd string) {
|
||||
// Remove the "consistency" keyword if it exists
|
||||
cmd = strings.TrimSpace(strings.Replace(cmd, "consistency", "", -1))
|
||||
|
@ -425,6 +445,7 @@ func (c *CommandLine) parseInto(stmt string) string {
|
|||
return stmt
|
||||
}
|
||||
|
||||
// Insert runs an INSERT statement
|
||||
func (c *CommandLine) Insert(stmt string) error {
|
||||
i, point := parseNextIdentifier(stmt)
|
||||
if !strings.EqualFold(i, "insert") {
|
||||
|
@ -455,6 +476,7 @@ func (c *CommandLine) Insert(stmt string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ExecuteQuery runs any query statement
|
||||
func (c *CommandLine) ExecuteQuery(query string) error {
|
||||
response, err := c.Client.Query(client.Query{Command: query, Database: c.Database})
|
||||
if err != nil {
|
||||
|
@ -473,6 +495,7 @@ func (c *CommandLine) ExecuteQuery(query string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DatabaseToken retrieves database token
|
||||
func (c *CommandLine) DatabaseToken() (string, error) {
|
||||
response, err := c.Client.Query(client.Query{Command: "SHOW DIAGNOSTICS for 'registration'"})
|
||||
if err != nil {
|
||||
|
@ -491,6 +514,7 @@ func (c *CommandLine) DatabaseToken() (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// FormatResponse formats output to previsouly chosen format
|
||||
func (c *CommandLine) FormatResponse(response *client.Response, w io.Writer) {
|
||||
switch c.Format {
|
||||
case "json":
|
||||
|
@ -644,6 +668,7 @@ func interfaceToString(v interface{}) string {
|
|||
}
|
||||
}
|
||||
|
||||
// Settings prints current settings
|
||||
func (c *CommandLine) Settings() {
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(os.Stdout, 0, 8, 1, '\t', 0)
|
||||
|
@ -685,14 +710,9 @@ func (c *CommandLine) help() {
|
|||
}
|
||||
|
||||
func (c *CommandLine) history() {
|
||||
usr, err := user.Current()
|
||||
// Only load history if we can get the user
|
||||
if err == nil {
|
||||
historyFile := filepath.Join(usr.HomeDir, ".influx_history")
|
||||
if history, err := ioutil.ReadFile(historyFile); err == nil {
|
||||
fmt.Print(string(history))
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
c.Line.WriteHistory(&buf)
|
||||
fmt.Print(buf.String())
|
||||
}
|
||||
|
||||
func (c *CommandLine) gopher() {
|
||||
|
@ -752,6 +772,7 @@ func (c *CommandLine) gopher() {
|
|||
`)
|
||||
}
|
||||
|
||||
// Version prints CLI version
|
||||
func (c *CommandLine) Version() {
|
||||
fmt.Println("InfluxDB shell " + c.ClientVersion)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cli_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -9,6 +11,7 @@ import (
|
|||
|
||||
"github.com/influxdb/influxdb/client"
|
||||
"github.com/influxdb/influxdb/cmd/influx/cli"
|
||||
"github.com/peterh/liner"
|
||||
)
|
||||
|
||||
func TestParseCommand_CommandsExist(t *testing.T) {
|
||||
|
@ -22,7 +25,6 @@ func TestParseCommand_CommandsExist(t *testing.T) {
|
|||
{cmd: "help"},
|
||||
{cmd: "pretty"},
|
||||
{cmd: "use"},
|
||||
{cmd: ""}, // test that a blank command just returns
|
||||
}
|
||||
for _, test := range tests {
|
||||
if !c.ParseCommand(test.cmd) {
|
||||
|
@ -31,6 +33,21 @@ func TestParseCommand_CommandsExist(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseCommand_BlankCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := cli.CommandLine{}
|
||||
tests := []struct {
|
||||
cmd string
|
||||
}{
|
||||
{cmd: ""}, // test that a blank command doesn't work
|
||||
}
|
||||
for _, test := range tests {
|
||||
if c.ParseCommand(test.cmd) {
|
||||
t.Fatalf(`Command failed for %q.`, test.cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCommand_TogglePretty(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := cli.CommandLine{}
|
||||
|
@ -49,7 +66,6 @@ func TestParseCommand_TogglePretty(t *testing.T) {
|
|||
|
||||
func TestParseCommand_Exit(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := cli.CommandLine{}
|
||||
tests := []struct {
|
||||
cmd string
|
||||
}{
|
||||
|
@ -60,7 +76,10 @@ func TestParseCommand_Exit(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if c.ParseCommand(test.cmd) {
|
||||
c := cli.CommandLine{Quit: make(chan struct{}, 1)}
|
||||
c.ParseCommand(test.cmd)
|
||||
// channel should be closed
|
||||
if _, ok := <-c.Quit; ok {
|
||||
t.Fatalf(`Command "exit" failed for %q.`, test.cmd)
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +239,12 @@ func TestParseCommand_InsertInto(t *testing.T) {
|
|||
|
||||
func TestParseCommand_History(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := cli.CommandLine{}
|
||||
c := cli.CommandLine{Line: liner.NewLiner()}
|
||||
defer c.Line.Close()
|
||||
|
||||
// append one entry to history
|
||||
c.Line.AppendHistory("abc")
|
||||
|
||||
tests := []struct {
|
||||
cmd string
|
||||
}{
|
||||
|
@ -235,4 +259,46 @@ func TestParseCommand_History(t *testing.T) {
|
|||
t.Fatalf(`Command "history" failed for %q.`, test.cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// buf size should be at least 1
|
||||
var buf bytes.Buffer
|
||||
c.Line.WriteHistory(&buf)
|
||||
if buf.Len() < 1 {
|
||||
t.Fatal("History is borked")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCommand_HistoryWithBlankCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := cli.CommandLine{Line: liner.NewLiner()}
|
||||
defer c.Line.Close()
|
||||
|
||||
// append one entry to history
|
||||
c.Line.AppendHistory("x")
|
||||
|
||||
tests := []struct {
|
||||
cmd string
|
||||
}{
|
||||
{cmd: "history"},
|
||||
{cmd: " history"},
|
||||
{cmd: "history "},
|
||||
{cmd: "History "},
|
||||
{cmd: ""}, // shouldn't be persisted in history
|
||||
{cmd: " "}, // shouldn't be persisted in history
|
||||
}
|
||||
|
||||
// don't validate because blank commands are never executed
|
||||
for _, test := range tests {
|
||||
c.ParseCommand(test.cmd)
|
||||
}
|
||||
|
||||
// buf shall not contain empty commands
|
||||
var buf bytes.Buffer
|
||||
c.Line.WriteHistory(&buf)
|
||||
scanner := bufio.NewScanner(&buf)
|
||||
for scanner.Scan() {
|
||||
if scanner.Text() == "" || scanner.Text() == " " {
|
||||
t.Fatal("Empty commands should not be persisted in history.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue