diff options
author | Martin Polden <mpolden@mpolden.no> | 2021-08-26 11:46:20 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2021-08-26 11:46:20 +0200 |
commit | fee0f6489a6aa734e008b10ef275e464e66bfc17 (patch) | |
tree | 80bc9272a7b09d22f762c392130608c872246126 /client | |
parent | 58224ccdae4531dac1b0634d27cf894a6a61a779 (diff) |
Cleanup printing and colorization
Diffstat (limited to 'client')
-rw-r--r-- | client/go/cmd/activate.go | 3 | ||||
-rw-r--r-- | client/go/cmd/cert.go | 31 | ||||
-rw-r--r-- | client/go/cmd/command_tester.go | 10 | ||||
-rw-r--r-- | client/go/cmd/config.go | 6 | ||||
-rw-r--r-- | client/go/cmd/deploy.go | 21 | ||||
-rw-r--r-- | client/go/cmd/deploy_test.go | 10 | ||||
-rw-r--r-- | client/go/cmd/document.go | 25 | ||||
-rw-r--r-- | client/go/cmd/document_test.go | 10 | ||||
-rw-r--r-- | client/go/cmd/init.go | 45 | ||||
-rw-r--r-- | client/go/cmd/init_test.go | 2 | ||||
-rw-r--r-- | client/go/cmd/prepare.go | 7 | ||||
-rw-r--r-- | client/go/cmd/query.go | 21 | ||||
-rw-r--r-- | client/go/cmd/query_test.go | 4 | ||||
-rw-r--r-- | client/go/cmd/root.go | 20 | ||||
-rw-r--r-- | client/go/cmd/status.go | 13 | ||||
-rw-r--r-- | client/go/cmd/status_test.go | 10 | ||||
-rw-r--r-- | client/go/cmd/target.go | 4 | ||||
-rw-r--r-- | client/go/go.mod | 3 | ||||
-rw-r--r-- | client/go/go.sum | 4 | ||||
-rw-r--r-- | client/go/util/http.go | 21 | ||||
-rw-r--r-- | client/go/util/http_test.go | 9 | ||||
-rw-r--r-- | client/go/util/io.go | 18 | ||||
-rw-r--r-- | client/go/util/print.go | 78 | ||||
-rw-r--r-- | client/go/vespa/deploy.go | 38 |
24 files changed, 192 insertions, 221 deletions
diff --git a/client/go/cmd/activate.go b/client/go/cmd/activate.go index bb75fff2a49..b7b3fa22a05 100644 --- a/client/go/cmd/activate.go +++ b/client/go/cmd/activate.go @@ -6,7 +6,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/vespa-engine/vespa/vespa" ) func init() { @@ -20,6 +19,6 @@ var activateCmd = &cobra.Command{ Short: "Activates (deploys) the previously prepared application package", Long: `TODO`, Run: func(cmd *cobra.Command, args []string) { - vespa.Deploy(true, "", deployTarget()) + deploy(true, nil) }, } diff --git a/client/go/cmd/cert.go b/client/go/cmd/cert.go index bb1480ddb37..88d489fd229 100644 --- a/client/go/cmd/cert.go +++ b/client/go/cmd/cert.go @@ -4,12 +4,11 @@ package cmd import ( - "fmt" + "log" "os" "path/filepath" "github.com/spf13/cobra" - "github.com/vespa-engine/vespa/util" "github.com/vespa-engine/vespa/vespa" ) @@ -33,13 +32,13 @@ var certCmd = &cobra.Command{ } else { var err error path, err = os.Getwd() - util.FatalIfErr(err) + fatalIfErr(err) } pkg, err := vespa.FindApplicationPackage(path) - util.FatalIfErr(err) + fatalIfErr(err) if pkg.HasCertificate() && !overwriteCertificate { - util.Print("Certificate already exists") + log.Print("Certificate already exists. Use -f option to recreate") return } @@ -51,20 +50,26 @@ var certCmd = &cobra.Command{ pkgCertificateFile := filepath.Join(securityDir, "clients.pem") keyPair, err := vespa.CreateKeyPair() - util.FatalIfErr(err) + fatalIfErr(err) err = os.MkdirAll(securityDir, 0755) - util.FatalIfErr(err) + fatalIfErr(err) err = keyPair.WriteCertificateFile(pkgCertificateFile, overwriteCertificate) - util.FatalIfErr(err) + fatalIfErr(err) err = keyPair.WriteCertificateFile(certificateFile, overwriteCertificate) - util.FatalIfErr(err) + fatalIfErr(err) err = keyPair.WritePrivateKeyFile(privateKeyFile, overwriteCertificate) - util.FatalIfErr(err) + fatalIfErr(err) // TODO: Just use log package, which has Printf - util.Print(fmt.Sprintf("Certificate written to %s", pkgCertificateFile)) - util.Print(fmt.Sprintf("Certificate written to %s", certificateFile)) - util.Print(fmt.Sprintf("Private key written to %s", privateKeyFile)) + log.Printf("Certificate written to %s", color.Green(pkgCertificateFile)) + log.Printf("Certificate written to %s", color.Green(certificateFile)) + log.Printf("Private key written to %s", color.Green(privateKeyFile)) }, } + +func fatalIfErr(err error) { + if err != nil { + log.Fatal(err) + } +} diff --git a/client/go/cmd/command_tester.go b/client/go/cmd/command_tester.go index 50684d87a1b..6078167df40 100644 --- a/client/go/cmd/command_tester.go +++ b/client/go/cmd/command_tester.go @@ -6,25 +6,27 @@ package cmd import ( "bytes" - "github.com/stretchr/testify/assert" - "github.com/vespa-engine/vespa/util" "io/ioutil" + "log" "net/http" "strconv" "testing" "time" + + "github.com/stretchr/testify/assert" + "github.com/vespa-engine/vespa/util" ) func executeCommand(t *testing.T, client *mockHttpClient, args []string, moreArgs []string) (standardout string) { util.ActiveHttpClient = client // Reset - persistent flags in Cobra persists over tests - util.Out = bytes.NewBufferString("") + log.SetOutput(bytes.NewBufferString("")) rootCmd.SetArgs([]string{"status", "-t", ""}) rootCmd.Execute() b := bytes.NewBufferString("") - util.Out = b + log.SetOutput(b) rootCmd.SetArgs(append(args, moreArgs...)) rootCmd.Execute() out, err := ioutil.ReadAll(b) diff --git a/client/go/cmd/config.go b/client/go/cmd/config.go index 9435a97d999..15f6e623a33 100644 --- a/client/go/cmd/config.go +++ b/client/go/cmd/config.go @@ -5,12 +5,12 @@ package cmd import ( + "log" "os" "path/filepath" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/vespa-engine/vespa/util" ) func init() { @@ -54,12 +54,12 @@ func writeConfig() { _, statErr := os.Stat(configPath) if !os.IsExist(statErr) { if _, createErr := os.Create(configPath); createErr != nil { - util.Error("Warning: Can not remember flag parameters: " + createErr.Error()) + log.Printf("Warning: Can not remember flag parameters: %s", color.Red(createErr)) } } writeErr := viper.WriteConfig() if writeErr != nil { - util.Error("Could not write config:", writeErr.Error()) + log.Printf("Could not write config: %s", color.Red(writeErr)) } } diff --git a/client/go/cmd/deploy.go b/client/go/cmd/deploy.go index 6377abda00c..7a180deb03b 100644 --- a/client/go/cmd/deploy.go +++ b/client/go/cmd/deploy.go @@ -5,6 +5,8 @@ package cmd import ( + "log" + "github.com/spf13/cobra" "github.com/vespa-engine/vespa/vespa" ) @@ -18,10 +20,19 @@ var deployCmd = &cobra.Command{ Short: "Deploys (prepares and activates) an application package", Long: `TODO`, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - vespa.Deploy(false, "", deployTarget()) - } else { - vespa.Deploy(false, args[0], deployTarget()) - } + deploy(false, args) }, } + +func deploy(prepare bool, args []string) { + var application string + if len(args) > 0 { + application = args[0] + } + path, err := vespa.Deploy(false, application, deployTarget()) + if err != nil { + log.Print(color.Red(err)) + } else { + log.Print("Deployed ", color.Green(path), " successfully") + } +} diff --git a/client/go/cmd/deploy_test.go b/client/go/cmd/deploy_test.go index 4a40739ec32..4c031f964bd 100644 --- a/client/go/cmd/deploy_test.go +++ b/client/go/cmd/deploy_test.go @@ -22,7 +22,7 @@ func TestDeployZipWithURLTargetArgument(t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32mDeployed "+applicationPackage+"\x1b[0m\n", + "Deployed "+applicationPackage+" successfully\n", executeCommand(t, client, arguments, []string{})) assertDeployRequestMade("http://target:19071", client, t) } @@ -50,7 +50,7 @@ func TestDeployApplicationDirectoryWithPomAndTarget(t *testing.T) { func TestDeployApplicationDirectoryWithPomAndEmptyTarget(t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[31mpom.xml exists but no target/application.zip. Run mvn package first\x1b[0m\n", + "pom.xml exists but no target/application.zip. Run mvn package first\n", executeCommand(t, client, []string{"deploy", "testdata/applications/withEmptyTarget"}, []string{})) } @@ -65,7 +65,7 @@ func TestDeployError(t *testing.T) { func assertDeploy(applicationPackage string, arguments []string, t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32mDeployed "+applicationPackage+"\x1b[0m\n", + "Deployed "+applicationPackage+" successfully\n", executeCommand(t, client, arguments, []string{})) assertDeployRequestMade("http://127.0.0.1:19071", client, t) } @@ -84,13 +84,13 @@ func assertDeployRequestMade(target string, client *mockHttpClient, t *testing.T func assertApplicationPackageError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{nextStatus: status, nextBody: errorMessage} assert.Equal(t, - "\x1b[31mInvalid application package (Status "+strconv.Itoa(status)+"):\x1b[0m\n"+errorMessage+"\n", + "Invalid application package (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", executeCommand(t, client, []string{"deploy", "testdata/applications/withTarget/target/application.zip"}, []string{})) } func assertDeployServerError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{nextStatus: status, nextBody: errorMessage} assert.Equal(t, - "\x1b[31mError from deploy service at 127.0.0.1:19071 (Status "+strconv.Itoa(status)+"):\x1b[0m\n"+errorMessage+"\n", + "Error from deploy service at 127.0.0.1:19071 (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", executeCommand(t, client, []string{"deploy", "testdata/applications/withTarget/target/application.zip"}, []string{})) } diff --git a/client/go/cmd/document.go b/client/go/cmd/document.go index 68e583d4c29..41f8c8f3320 100644 --- a/client/go/cmd/document.go +++ b/client/go/cmd/document.go @@ -7,14 +7,16 @@ package cmd import ( "bytes" "encoding/json" - "github.com/spf13/cobra" - "github.com/vespa-engine/vespa/util" "io/ioutil" + "log" "net/http" "net/url" "os" "strings" "time" + + "github.com/spf13/cobra" + "github.com/vespa-engine/vespa/util" ) func init() { @@ -67,8 +69,7 @@ func post(documentId string, jsonFile string) { fileReader, fileError := os.Open(jsonFile) if fileError != nil { - util.Error("Could not open file at " + jsonFile) - util.Detail(fileError.Error()) + log.Printf("Could not open file at %s: %s", color.Cyan(jsonFile), fileError) return } @@ -82,7 +83,7 @@ func post(documentId string, jsonFile string) { } else if doc["put"] != nil { documentId = doc["put"].(string) // document feeder format } else { - util.Error("No document id given neither as argument or an 'id' key in the json file") + log.Print("No document id given neither as argument or an 'id' key in the json file") return } } @@ -96,19 +97,19 @@ func post(documentId string, jsonFile string) { Body: ioutil.NopCloser(bytes.NewReader(documentData)), } serviceDescription := "Container (document API)" - response := util.HttpDo(request, time.Second*60, serviceDescription) + response, err := util.HttpDo(request, time.Second*60, serviceDescription) if response == nil { - return + log.Print("Request failed: ", color.Red(err)) } defer response.Body.Close() if response.StatusCode == 200 { - util.Success(documentId) + log.Print(color.Green(documentId)) } else if response.StatusCode/100 == 4 { - util.Error("Invalid document (" + response.Status + "):") - util.PrintReader(response.Body) + log.Printf("Invalid document (%s):", color.Red(response.Status)) + log.Print(util.ReaderToJSON(response.Body)) } else { - util.Error("Error from", strings.ToLower(serviceDescription), "at", request.URL.Host, "("+response.Status+"):") - util.PrintReader(response.Body) + log.Printf("Error from %s at %s (%s):", color.Cyan(strings.ToLower(serviceDescription)), color.Cyan(request.URL.Host), color.Red(response.Status)) + log.Print(util.ReaderToJSON(response.Body)) } } diff --git a/client/go/cmd/document_test.go b/client/go/cmd/document_test.go index f1e4bc007cb..93532cdd69e 100644 --- a/client/go/cmd/document_test.go +++ b/client/go/cmd/document_test.go @@ -37,7 +37,7 @@ func TestDocumentIdNotSpecified(t *testing.T) { arguments := []string{"document", "post", "testdata/A-Head-Full-of-Dreams.json"} client := &mockHttpClient{} assert.Equal(t, - "\x1b[31mNo document id given neither as argument or an 'id' key in the json file\x1b[0m\n", + "No document id given neither as argument or an 'id' key in the json file\n", executeCommand(t, client, arguments, []string{})) } @@ -52,7 +52,7 @@ func TestDocumentPostServerError(t *testing.T) { func assertDocumentPost(arguments []string, documentId string, jsonFile string, t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32m"+documentId+"\x1b[0m\n", + documentId+"\n", executeCommand(t, client, arguments, []string{})) target := getTarget(documentContext).document assert.Equal(t, target+"/document/v1/"+documentId, client.lastRequest.URL.String()) @@ -66,7 +66,7 @@ func assertDocumentPost(arguments []string, documentId string, jsonFile string, func assertDocumentPostShortForm(documentId string, jsonFile string, t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32mSuccess\n", + "Success\n", executeCommand(t, client, []string{"document", jsonFile}, []string{})) target := getTarget(documentContext).document assert.Equal(t, target+"/document/v1/"+documentId, client.lastRequest.URL.String()) @@ -75,7 +75,7 @@ func assertDocumentPostShortForm(documentId string, jsonFile string, t *testing. func assertDocumentError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{nextStatus: status, nextBody: errorMessage} assert.Equal(t, - "\x1b[31mInvalid document (Status "+strconv.Itoa(status)+"):\x1b[0m\n"+errorMessage+"\n", + "Invalid document (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", executeCommand(t, client, []string{"document", "post", "mynamespace/music/docid/1", "testdata/A-Head-Full-of-Dreams.json"}, []string{})) @@ -84,7 +84,7 @@ func assertDocumentError(t *testing.T, status int, errorMessage string) { func assertDocumentServerError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{nextStatus: status, nextBody: errorMessage} assert.Equal(t, - "\x1b[31mError from container (document api) at 127.0.0.1:8080 (Status "+strconv.Itoa(status)+"):\x1b[0m\n"+errorMessage+"\n", + "Error from container (document api) at 127.0.0.1:8080 (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", executeCommand(t, client, []string{"document", "post", "mynamespace/music/docid/1", "testdata/A-Head-Full-of-Dreams.json"}, []string{})) diff --git a/client/go/cmd/init.go b/client/go/cmd/init.go index bae5aa1908b..8cbaf163336 100644 --- a/client/go/cmd/init.go +++ b/client/go/cmd/init.go @@ -7,16 +7,18 @@ package cmd import ( "archive/zip" "errors" - "github.com/spf13/cobra" - "github.com/vespa-engine/vespa/util" "io" "io/ioutil" + "log" "net/http" "net/url" "os" "path/filepath" "strings" "time" + + "github.com/spf13/cobra" + "github.com/vespa-engine/vespa/util" ) // Set this to test without downloading this file from github @@ -54,15 +56,15 @@ func initApplication(name string, source string) { createErr := os.Mkdir(name, 0755) if createErr != nil { - util.Error("Could not create directory '" + name + "'") - util.Detail(createErr.Error()) + log.Print("Could not create directory '", color.Cyan(name), "'") + log.Print(createErr) return } zipReader, zipOpenError := zip.OpenReader(zipFile.Name()) if zipOpenError != nil { - util.Error("Could not open sample apps zip '" + zipFile.Name() + "'") - util.Detail(zipOpenError.Error()) + log.Print("Could not open sample apps zip '", color.Cyan(zipFile.Name()), "'") + log.Print(zipOpenError) } defer zipReader.Close() @@ -73,16 +75,16 @@ func initApplication(name string, source string) { found = true copyError := copy(f, name, zipEntryPrefix) if copyError != nil { - util.Error("Could not copy zip entry '" + f.Name + "' to " + name) - util.Detail(copyError.Error()) + log.Print("Could not copy zip entry '", color.Cyan(f.Name), "' to ", color.Cyan(name)) + log.Print(copyError) return } } } if !found { - util.Error("Could not find source application '" + source + "'") + log.Print("Could not find source application '", color.Cyan(source), "'") } else { - util.Success("Created " + name) + log.Print("Created ", color.Green(name)) } } @@ -90,38 +92,41 @@ func getSampleAppsZip() *os.File { if existingSampleAppsZip != "" { existing, openExistingError := os.Open(existingSampleAppsZip) if openExistingError != nil { - util.Error("Could not open existing sample apps zip file '" + existingSampleAppsZip + "'") - util.Detail(openExistingError.Error()) + log.Print("Could not open existing sample apps zip file '", color.Cyan(existingSampleAppsZip), "'") + log.Print(openExistingError) } return existing } // TODO: Cache it? - util.Detail("Downloading sample apps ...") // TODO: Spawn thread to indicate progress + log.Print("Downloading sample apps ...") // TODO: Spawn thread to indicate progress zipUrl, _ := url.Parse("https://github.com/vespa-engine/sample-apps/archive/refs/heads/master.zip") request := &http.Request{ URL: zipUrl, Method: "GET", } - response := util.HttpDo(request, time.Minute*60, "GitHub") + response, reqErr := util.HttpDo(request, time.Minute*60, "GitHub") + if reqErr != nil { + log.Print("Request failed: ", color.Red(reqErr)) + return nil + } defer response.Body.Close() if response.StatusCode != 200 { - util.Error("Could not download sample apps from github") - util.Detail(response.Status) + log.Printf("Could not download sample apps from github (%s)", color.Red(response.StatusCode)) return nil } destination, tempFileError := ioutil.TempFile("", "prefix") if tempFileError != nil { - util.Error("Could not create a temp file to hold sample apps") - util.Detail(tempFileError.Error()) + log.Print("Could not create a temp file to hold sample apps") + log.Print(tempFileError) } // destination, _ := os.Create("./" + name + "/sample-apps.zip") // defer destination.Close() _, err := io.Copy(destination, response.Body) if err != nil { - util.Error("Could not download sample apps from GitHub") - util.Detail(err.Error()) + log.Print("Could not download sample apps from GitHub") + log.Print(err) return nil } return destination diff --git a/client/go/cmd/init_test.go b/client/go/cmd/init_test.go index d855acb08f3..8bca3c96fb7 100644 --- a/client/go/cmd/init_test.go +++ b/client/go/cmd/init_test.go @@ -21,7 +21,7 @@ func assertCreated(app string, sampleAppName string, t *testing.T) { existingSampleAppsZip = "testdata/sample-apps-master.zip" standardOut := executeCommand(t, &mockHttpClient{}, []string{"init", app, sampleAppName}, []string{}) defer os.RemoveAll(app) - assert.Equal(t, "\x1b[32mCreated "+app+"\x1b[0m\n", standardOut) + assert.Equal(t, "Created "+app+"\n", standardOut) assert.True(t, util.PathExists(filepath.Join(app, "README.md"))) assert.True(t, util.PathExists(filepath.Join(app, "src", "main", "application"))) assert.True(t, util.IsDirectory(filepath.Join(app, "src", "main", "application"))) diff --git a/client/go/cmd/prepare.go b/client/go/cmd/prepare.go index 7dfa5f92c33..1790629ca2c 100644 --- a/client/go/cmd/prepare.go +++ b/client/go/cmd/prepare.go @@ -6,7 +6,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/vespa-engine/vespa/vespa" ) func init() { @@ -20,10 +19,6 @@ var prepareCmd = &cobra.Command{ Short: "Prepares an application package for activation", Long: `TODO`, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - vespa.Deploy(true, "", deployTarget()) - } else { - vespa.Deploy(true, args[0], deployTarget()) - } + deploy(true, args) }, } diff --git a/client/go/cmd/query.go b/client/go/cmd/query.go index 7a6a569eb70..dea7e70b19e 100644 --- a/client/go/cmd/query.go +++ b/client/go/cmd/query.go @@ -6,12 +6,14 @@ package cmd import ( "errors" - "github.com/spf13/cobra" - "github.com/vespa-engine/vespa/util" + "log" "net/http" "net/url" "strings" "time" + + "github.com/spf13/cobra" + "github.com/vespa-engine/vespa/util" ) func init() { @@ -43,20 +45,21 @@ func query(arguments []string) { } url.RawQuery = urlQuery.Encode() - response := util.HttpDo(&http.Request{URL: url}, time.Second*10, "Container") - if response == nil { + response, err := util.HttpDo(&http.Request{URL: url}, time.Second*10, "Container") + if err != nil { + log.Print("Request failed: ", color.Red(err)) return } defer response.Body.Close() if response.StatusCode == 200 { - util.PrintReader(response.Body) + log.Print(util.ReaderToJSON(response.Body)) } else if response.StatusCode/100 == 4 { - util.Error("Invalid query (" + response.Status + "):") - util.PrintReader(response.Body) + log.Printf("Invalid query (%s):", color.Red(response.Status)) + log.Print(util.ReaderToJSON(response.Body)) } else { - util.Error("Error from container at", url.Host, "("+response.Status+"):") - util.PrintReader(response.Body) + log.Printf("Error from container at %s (%s):", color.Cyan(url.Host), color.Red(response.Status)) + log.Print(util.ReaderToJSON(response.Body)) } } diff --git a/client/go/cmd/query_test.go b/client/go/cmd/query_test.go index 5524c95ec0b..0ab7a110be9 100644 --- a/client/go/cmd/query_test.go +++ b/client/go/cmd/query_test.go @@ -64,7 +64,7 @@ func assertQueryNonJsonResult(t *testing.T, expectedQuery string, query ...strin func assertQueryError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{nextStatus: status, nextBody: errorMessage} assert.Equal(t, - "\x1b[31mInvalid query (Status "+strconv.Itoa(status)+"):\x1b[0m\n"+errorMessage+"\n", + "Invalid query (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", executeCommand(t, client, []string{"query"}, []string{"yql=select from sources * where title contains 'foo'"}), "error output") } @@ -72,7 +72,7 @@ func assertQueryError(t *testing.T, status int, errorMessage string) { func assertQueryServiceError(t *testing.T, status int, errorMessage string) { client := &mockHttpClient{nextStatus: status, nextBody: errorMessage} assert.Equal(t, - "\x1b[31mError from container at 127.0.0.1:8080 (Status "+strconv.Itoa(status)+"):\x1b[0m\n"+errorMessage+"\n", + "Error from container at 127.0.0.1:8080 (Status "+strconv.Itoa(status)+"):\n"+errorMessage+"\n", executeCommand(t, client, []string{"query"}, []string{"yql=select from sources * where title contains 'foo'"}), "error output") } diff --git a/client/go/cmd/root.go b/client/go/cmd/root.go index b3306bbe617..bb74e9b5539 100644 --- a/client/go/cmd/root.go +++ b/client/go/cmd/root.go @@ -5,6 +5,12 @@ package cmd import ( + "log" + "os" + + "github.com/logrusorgru/aurora" + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" "github.com/spf13/cobra" ) @@ -20,15 +26,21 @@ var ( Long: `TO DO`, } + + color aurora.Aurora ) +func configureLogger() { + color = aurora.NewAurora(isatty.IsTerminal(os.Stdout.Fd())) + log.SetFlags(0) // No timestamps + log.SetOutput(colorable.NewColorableStdout()) +} + func init() { + configureLogger() cobra.OnInitialize(readConfig) rootCmd.PersistentFlags().StringVarP(&targetArgument, "target", "t", "local", "The name or URL of the recipient of this command") } // Execute executes the root command. -func Execute() error { - err := rootCmd.Execute() - return err -} +func Execute() error { return rootCmd.Execute() } diff --git a/client/go/cmd/status.go b/client/go/cmd/status.go index f1cbc0f9d2d..fa70aec302f 100644 --- a/client/go/cmd/status.go +++ b/client/go/cmd/status.go @@ -5,6 +5,8 @@ package cmd import ( + "log" + "github.com/spf13/cobra" "github.com/vespa-engine/vespa/util" ) @@ -54,16 +56,17 @@ var statusDeployCmd = &cobra.Command{ func status(target string, description string) { path := "/ApplicationStatus" - response := util.HttpGet(target, path, description) - if response == nil { + response, err := util.HttpGet(target, path, description) + if err != nil { + log.Print("Request failed: ", color.Red(err)) return } defer response.Body.Close() if response.StatusCode != 200 { - util.Error(description, "at", target, "is not ready") - util.Detail(response.Status) + log.Print(description, " at ", color.Cyan(target), " is ", color.Yellow("not ready")) + log.Print(response.Status) } else { - util.Success(description, "at", target, "is ready") + log.Print(description, " at ", color.Cyan(target), " is ", color.Green("ready")) } } diff --git a/client/go/cmd/status_test.go b/client/go/cmd/status_test.go index 9fb9091b0bf..848dab3db26 100644 --- a/client/go/cmd/status_test.go +++ b/client/go/cmd/status_test.go @@ -45,7 +45,7 @@ func TestStatusErrorResponse(t *testing.T) { func assertDeployStatus(target string, args []string, t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32mDeploy API at "+target+" is ready\x1b[0m\n", + "Deploy API at "+target+" is ready\n", executeCommand(t, client, []string{"status", "deploy"}, args), "vespa status config-server") assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String()) @@ -54,13 +54,13 @@ func assertDeployStatus(target string, args []string, t *testing.T) { func assertQueryStatus(target string, args []string, t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32mQuery API at "+target+" is ready\x1b[0m\n", + "Query API at "+target+" is ready\n", executeCommand(t, client, []string{"status", "query"}, args), "vespa status container") assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String()) assert.Equal(t, - "\x1b[32mQuery API at "+target+" is ready\x1b[0m\n", + "Query API at "+target+" is ready\n", executeCommand(t, client, []string{"status"}, args), "vespa status (the default)") assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String()) @@ -69,7 +69,7 @@ func assertQueryStatus(target string, args []string, t *testing.T) { func assertDocumentStatus(target string, args []string, t *testing.T) { client := &mockHttpClient{} assert.Equal(t, - "\x1b[32mDocument API at "+target+" is ready\x1b[0m\n", + "Document API at "+target+" is ready\n", executeCommand(t, client, []string{"status", "document"}, args), "vespa status container") assert.Equal(t, target+"/ApplicationStatus", client.lastRequest.URL.String()) @@ -78,7 +78,7 @@ func assertDocumentStatus(target string, args []string, t *testing.T) { func assertQueryStatusError(target string, args []string, t *testing.T) { client := &mockHttpClient{nextStatus: 500} assert.Equal(t, - "\x1b[31mQuery API at "+target+" is not ready\x1b[0m\n\x1b[33mStatus 500\x1b[0m\n", + "Query API at "+target+" is not ready\nStatus 500\n", executeCommand(t, client, []string{"status", "container"}, args), "vespa status container") } diff --git a/client/go/cmd/target.go b/client/go/cmd/target.go index bf6487bf790..09e63bc1d3b 100644 --- a/client/go/cmd/target.go +++ b/client/go/cmd/target.go @@ -5,7 +5,7 @@ package cmd import ( - "github.com/vespa-engine/vespa/util" + "log" "strings" ) @@ -68,6 +68,6 @@ func getTarget(targetContext context) *target { return nil // TODO } - util.Error("Unknown target argument '" + targetArgument + ": Use 'local', 'cloud' or an URL") + log.Printf("Unknown target '%s': Use %s, %s or an URL", color.Red(targetArgument), color.Cyan("local"), color.Cyan("cloud")) return nil } diff --git a/client/go/go.mod b/client/go/go.mod index 40499f09e04..ff444215332 100644 --- a/client/go/go.mod +++ b/client/go/go.mod @@ -3,6 +3,9 @@ module github.com/vespa-engine/vespa go 1.16 require ( + github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/mattn/go-colorable v0.0.9 + github.com/mattn/go-isatty v0.0.3 github.com/spf13/cobra v1.2.1 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.7.0 diff --git a/client/go/go.sum b/client/go/go.sum index d6ff538bc63..cec273d1506 100644 --- a/client/go/go.sum +++ b/client/go/go.sum @@ -177,9 +177,13 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= diff --git a/client/go/util/http.go b/client/go/util/http.go index 6a4bab6c108..38224d2a842 100644 --- a/client/go/util/http.go +++ b/client/go/util/http.go @@ -5,6 +5,7 @@ package util import ( + "fmt" "net/http" "net/url" "strings" @@ -36,20 +37,18 @@ func CreateClient(timeout time.Duration) HttpClient { } // Convenience function for doing a HTTP GET -func HttpGet(host string, path string, description string) *http.Response { - url, urlError := url.Parse(host + path) - if urlError != nil { - Error("Invalid target url '" + host + path + "'") - return nil +func HttpGet(host string, path string, description string) (*http.Response, error) { + url, err := url.Parse(host + path) + if err != nil { + return nil, fmt.Errorf("Invalid target URL: %s: %w", host+path, err) } return HttpDo(&http.Request{URL: url}, time.Second*10, description) } -func HttpDo(request *http.Request, timeout time.Duration, description string) *http.Response { - response, error := ActiveHttpClient.Do(request, timeout) - if error != nil { - Error("Could not connect to", strings.ToLower(description), "at", request.URL.Host) - Detail(error.Error()) +func HttpDo(request *http.Request, timeout time.Duration, description string) (*http.Response, error) { + response, err := ActiveHttpClient.Do(request, timeout) + if err != nil { + return nil, fmt.Errorf("Could not connect to %s at %s: %w", strings.ToLower(description), request.URL.Host, err) } - return response + return response, nil } diff --git a/client/go/util/http_test.go b/client/go/util/http_test.go index 731fc935a1d..54114aefb64 100644 --- a/client/go/util/http_test.go +++ b/client/go/util/http_test.go @@ -6,11 +6,12 @@ package util import ( "bytes" - "github.com/stretchr/testify/assert" "io/ioutil" "net/http" "testing" "time" + + "github.com/stretchr/testify/assert" ) type mockHttpClient struct{} @@ -37,9 +38,11 @@ func (c mockHttpClient) Do(request *http.Request, timeout time.Duration) (respon func TestHttpRequest(t *testing.T) { ActiveHttpClient = mockHttpClient{} - response := HttpGet("http://host", "/okpath", "description") + response, err := HttpGet("http://host", "/okpath", "description") + assert.Nil(t, err) assert.Equal(t, 200, response.StatusCode) - response = HttpGet("http://host", "/otherpath", "description") + response, err = HttpGet("http://host", "/otherpath", "description") + assert.Nil(t, err) assert.Equal(t, 500, response.StatusCode) } diff --git a/client/go/util/io.go b/client/go/util/io.go index cbde22ad0eb..5ce9708ed7a 100644 --- a/client/go/util/io.go +++ b/client/go/util/io.go @@ -6,6 +6,7 @@ package util import ( "bytes" + "encoding/json" "errors" "io" "os" @@ -26,14 +27,25 @@ func IsDirectory(path string) bool { // Returns the content of a reader as a string func ReaderToString(reader io.Reader) string { - buffer := new(strings.Builder) - io.Copy(buffer, reader) + var buffer strings.Builder + io.Copy(&buffer, reader) return buffer.String() } // Returns the content of a reader as a byte array func ReaderToBytes(reader io.Reader) []byte { - buffer := new(bytes.Buffer) + var buffer bytes.Buffer buffer.ReadFrom(reader) return buffer.Bytes() } + +// Returns the contents of reader as indented JSON +func ReaderToJSON(reader io.Reader) string { + bodyBytes := ReaderToBytes(reader) + var prettyJSON bytes.Buffer + parseError := json.Indent(&prettyJSON, bodyBytes, "", " ") + if parseError != nil { // Not JSON: Print plainly + return string(bodyBytes) + } + return string(prettyJSON.Bytes()) +} diff --git a/client/go/util/print.go b/client/go/util/print.go deleted file mode 100644 index ce116378222..00000000000 --- a/client/go/util/print.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -// Print functions for color-coded text. -// Author: bratseth - -package util - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - "strings" -) - -// Set this to have output written somewhere else than os.Stdout -var Out io.Writer - -func init() { - Out = os.Stdout -} - -// Prints in default color -func Print(messages ...string) { - print("", messages) -} - -// Prints in a color appropriate for errors -func Error(messages ...string) { - print("\033[31m", messages) -} - -// FatalIfError prints error and exists if given err is non-nill. -func FatalIfErr(err error) { - if err != nil { - Error(err.Error()) - os.Exit(1) - } -} - -// Prints in a color appropriate for success messages -func Success(messages ...string) { - print("\033[32m", messages) -} - -// Prints in a color appropriate for detail messages -func Detail(messages ...string) { - print("\033[33m", messages) -} - -// Prints all the text of the given reader -func PrintReader(reader io.Reader) { - bodyBytes := ReaderToBytes(reader) - var prettyJSON bytes.Buffer - parseError := json.Indent(&prettyJSON, bodyBytes, "", " ") - if parseError != nil { // Not JSON: Print plainly - Print(string(bodyBytes)) - } else { - Print(string(prettyJSON.Bytes())) - } -} - -func print(prefix string, messages []string) { - fmt.Fprint(Out, prefix) - for i := 0; i < len(messages); i++ { - fmt.Fprint(Out, messages[i]) - if i < len(messages)-1 { - fmt.Fprint(Out, " ") - } - } - // TODO: Use "log" instead of this package and something like https://github.com/logrusorgru/aurora for colorization - // Since automatic colorisation needs to support Windows, we probably need github.com/mattn/go-isatty to - // detect TTY - if strings.HasPrefix(prefix, "\033") { - fmt.Fprint(Out, "\033[0m") // Terminate colors - } - fmt.Fprintln(Out, "") -} diff --git a/client/go/vespa/deploy.go b/client/go/vespa/deploy.go index badc97aa44b..9973d3fd490 100644 --- a/client/go/vespa/deploy.go +++ b/client/go/vespa/deploy.go @@ -7,6 +7,7 @@ package vespa import ( "archive/zip" "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -56,26 +57,22 @@ func (ap *ApplicationPackage) HasCertificate() bool { func isZip(filename string) bool { return filepath.Ext(filename) == ".zip" } -func Deploy(prepare bool, application string, target string) { +func Deploy(prepare bool, application string, target string) (string, error) { pkg, noSourceError := FindApplicationPackage(application) if noSourceError != nil { - util.Error(noSourceError.Error()) - return + return "", noSourceError } zippedSource := pkg.Path if !pkg.IsZip() { // create zip tempZip, error := ioutil.TempFile("", "application.zip") if error != nil { - util.Error("Could not create a temporary zip file for the application package") - util.Detail(error.Error()) - return + return "", fmt.Errorf("Could not create a temporary zip file for the application package: %w", error) } error = zipDir(pkg.Path, tempZip.Name()) if error != nil { - util.Error(error.Error()) - return + return "", error } defer os.Remove(tempZip.Name()) zippedSource = tempZip.Name() @@ -83,9 +80,7 @@ func Deploy(prepare bool, application string, target string) { zipFileReader, zipFileError := os.Open(zippedSource) if zipFileError != nil { - util.Error("Could not open application package at " + pkg.Path) - util.Detail(zipFileError.Error()) - return + return "", fmt.Errorf("Could not open application package at %s: %w", pkg.Path, zipFileError) } var deployUrl *url.URL @@ -106,21 +101,18 @@ func Deploy(prepare bool, application string, target string) { Body: ioutil.NopCloser(zipFileReader), } serviceDescription := "Deploy service" - response := util.HttpDo(request, time.Minute*10, serviceDescription) - if response == nil { - return + response, err := util.HttpDo(request, time.Minute*10, serviceDescription) + if err != nil { + return "", err } - defer response.Body.Close() - if response.StatusCode == 200 { - util.Success("Deployed", pkg.Path) - } else if response.StatusCode/100 == 4 { - util.Error("Invalid application package", "("+response.Status+"):") - util.PrintReader(response.Body) - } else { - util.Error("Error from", strings.ToLower(serviceDescription), "at", request.URL.Host, "("+response.Status+"):") - util.PrintReader(response.Body) + + if response.StatusCode/100 == 4 { + return "", fmt.Errorf("Invalid application package (%s):\n%s", response.Status, util.ReaderToJSON(response.Body)) + } else if response.StatusCode != 200 { + return "", fmt.Errorf("Error from %s at %s (%s):\n%s", strings.ToLower(serviceDescription), request.URL.Host, response.Status, util.ReaderToJSON(response.Body)) } + return pkg.Path, nil } func zipDir(dir string, destination string) error { |