diff --git a/app/app.go b/app/app.go index 6031a9fd..5adfcc70 100644 --- a/app/app.go +++ b/app/app.go @@ -8,5 +8,5 @@ type application struct { // App (Application) interface var App application = application{ Name: "odin", - Version: "1.2.0-beta.3", + Version: "1.2.0", } diff --git a/go.mod b/go.mod index 14149cfd..8906b654 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( require github.com/olekukonko/tablewriter v0.0.5 require ( + github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5 // indirect github.com/briandowns/spinner v1.18.1 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/stretchr/testify v1.7.0 // indirect diff --git a/go.sum b/go.sum index c8cfcade..989d26f9 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5 h1:VYqcjykqpcq262cDxBAkAelSdg6HETkxgwzQRTS40Aw= +github.com/apoorvam/goterminal v0.0.0-20180523175556-614d345c47e5/go.mod h1:E7x8aDc3AQzDKjEoIZCt+XYheHk2OkP+p2UgeNjecH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= diff --git a/internal/backend/serviceset.go b/internal/backend/serviceset.go index a4d348c6..734cbf5f 100644 --- a/internal/backend/serviceset.go +++ b/internal/backend/serviceset.go @@ -54,8 +54,8 @@ func (s *ServiceSet) DeleteServiceSet(serviceSetName string) { response.Process(true) } -func (s *ServiceSet) DeployServiceSet(serviceSetName, env, configStoreNamespace string, forceDeployServices []serviceset.ListEnvService, force bool) ([]serviceset.ServiceSetDeploy, error) { - client := newApiClient() +func (s *ServiceSet) DeployServiceSet(serviceSetName, env, configStoreNamespace string, forceDeployServices []serviceset.ListEnvService, force bool) { + client := newStreamingApiClient() client.QueryParams["env_name"] = env client.QueryParams["force"] = fmt.Sprintf("%v", force) client.QueryParams["config_store_namespace"] = configStoreNamespace @@ -64,13 +64,9 @@ func (s *ServiceSet) DeployServiceSet(serviceSetName, env, configStoreNamespace "forceDeployServices": forceDeployServices, } - response := client.actionWithRetry(path.Join(serviceSetEntity, "deploy", serviceSetName, "env", env)+"/", "POST", data) + response := client.streamWithRetry(path.Join(serviceSetEntity, "deploy", serviceSetName, "env", env)+"/", "POST", data) response.Process(true) - var serviceResponse serviceset.ServiceSetDeployResponse - err := json.Unmarshal(response.Body, &serviceResponse) - - return serviceResponse.Response, err } func (s ServiceSet) ListEnvServices(serviceSetName, env, filterBy string) ([]serviceset.ListEnvService, error) { @@ -86,8 +82,8 @@ func (s ServiceSet) ListEnvServices(serviceSetName, env, filterBy string) ([]ser return serviceResponse.Response, err } -func (s *ServiceSet) UndeployServiceSet(serviceSetName, env string, forceUndeployServices []serviceset.ListEnvService, force bool) ([]serviceset.ServiceSetDeploy, error) { - client := newApiClient() +func (s *ServiceSet) UndeployServiceSet(serviceSetName, env string, forceUndeployServices []serviceset.ListEnvService, force bool) { + client := newStreamingApiClient() client.QueryParams["env_name"] = env client.QueryParams["force"] = fmt.Sprintf("%v", force) @@ -95,13 +91,8 @@ func (s *ServiceSet) UndeployServiceSet(serviceSetName, env string, forceUndeplo "forceUndeployServices": forceUndeployServices, } - response := client.actionWithRetry(path.Join(serviceSetEntity, "undeploy", serviceSetName, "env", env)+"/", "DELETE", data) + response := client.streamWithRetry(path.Join(serviceSetEntity, "undeploy", serviceSetName, "env", env)+"/", "DELETE", data) response.Process(true) - - var serviceResponse serviceset.ServiceSetDeployResponse - err := json.Unmarshal(response.Body, &serviceResponse) - - return serviceResponse.Response, err } func (s *ServiceSet) UpdateServiceSet(serviceSetName string, serviceSet interface{}) { diff --git a/internal/command/commands/serviceset.go b/internal/command/commands/serviceset.go index 83d03347..197c07c0 100644 --- a/internal/command/commands/serviceset.go +++ b/internal/command/commands/serviceset.go @@ -90,7 +90,7 @@ func (s *ServiceSet) Run(args []string) int { table.Write(tableHeaders, tableData) s.Logger.Output("\nCommand to describe serviceset") - s.Logger.ItalicEmphasize("odin describe serviceset --name ") + s.Logger.ItalicEmphasize("odin describe service-set --name ") return 0 } @@ -177,31 +177,7 @@ func (s *ServiceSet) Run(args []string) int { /*deploy service-set*/ s.Logger.Debug("Deploying service-set: " + *serviceSetName + " in " + *envName) - serviceSetList, err := serviceSetClient.DeployServiceSet(*serviceSetName, *envName, *configStoreNamespace, forceDeployServices, *force) - if err != nil { - s.Logger.Error(err.Error()) - return 1 - } - - tableHeaders := []string{"Name", "Version", "Status"} - var tableData [][]interface{} - - for _, serviceSet := range serviceSetList { - status := "STARTED" - - if len(serviceSet.Error) > 0 { - status = "FAILURE: " + serviceSet.Error - } - - tableData = append(tableData, []interface{}{ - serviceSet.Name, - serviceSet.Version, - status, - }) - } - - s.Logger.Success(fmt.Sprintf("Deployment of service-set %s is started on env %s\n", *serviceSetName, *envName)) - table.Write(tableHeaders, tableData) + serviceSetClient.DeployServiceSet(*serviceSetName, *envName, *configStoreNamespace, forceDeployServices, *force) return 0 } @@ -246,32 +222,8 @@ func (s *ServiceSet) Run(args []string) int { } /*deploy service-set*/ - s.Logger.Info("Undeploying service-set: " + *serviceSetName + " in Env:" + *envName) - serviceSetList, err := serviceSetClient.UndeployServiceSet(*serviceSetName, *envName, forceUndeployServices, *force) - if err != nil { - s.Logger.Error(err.Error()) - return 1 - } - - tableHeaders := []string{"Name", "Version", "ExecutorUrl", "Error"} - var tableData [][]interface{} - - for _, serviceSet := range serviceSetList { - status := "STARTED" - - if len(serviceSet.Error) > 0 { - status = "FAILURE: " + serviceSet.Error - } - - tableData = append(tableData, []interface{}{ - serviceSet.Name, - serviceSet.Version, - status, - }) - } - - s.Logger.Success(fmt.Sprintf("Undeployment of service-set %s is started on env %s\n", *serviceSetName, *envName)) - table.Write(tableHeaders, tableData) + s.Logger.Debug("Undeploying service-set: " + *serviceSetName + " in Env:" + *envName) + serviceSetClient.UndeployServiceSet(*serviceSetName, *envName, forceUndeployServices, *force) return 0 } diff --git a/internal/ui/formatting.go b/internal/ui/formatting.go index 149686d9..c6422b52 100644 --- a/internal/ui/formatting.go +++ b/internal/ui/formatting.go @@ -7,3 +7,4 @@ import ( var BoldCyanHeading = color.New(color.FgHiCyan, color.Bold).SprintFunc() var SPINNER = ":spinner:" +var MULTISPINNER = ":multispinner:" diff --git a/internal/ui/multi_spinner.go b/internal/ui/multi_spinner.go new file mode 100644 index 00000000..c5667b73 --- /dev/null +++ b/internal/ui/multi_spinner.go @@ -0,0 +1,91 @@ +package ui + +import ( + "encoding/json" + "fmt" + "strings" + "sync" + "time" + + "github.com/apoorvam/goterminal" + "github.com/briandowns/spinner" +) + +type MultiSpinner struct { + mu *sync.Mutex + Delay time.Duration // Delay is the speed of the indicator + chars []string // chars holds the chosen character set + Data string // Suffix is the text appended to the indicator + FinalMSG string // string displayed after Stop() is called + Writer *goterminal.Writer // to make testing better, exported so users have access. Use `WithWriter` to update after initialization. + active bool // active holds the state of the spinner + stopChan chan struct{} + spinnerType int // stopChan is a channel used to stop the indicator // hideCursor determines if the cursor is visible // will be triggered after every spinner update +} + +func New(cs []string, d time.Duration, writer *goterminal.Writer, sType int) *MultiSpinner { + s := &MultiSpinner{ + Delay: d, + chars: cs, + mu: &sync.Mutex{}, + Writer: writer, + stopChan: make(chan struct{}, 1), + active: false, + spinnerType: sType, + } + + return s +} + +func (s *MultiSpinner) Start() { + s.mu.Lock() + s.Data = strings.Replace(s.Data, MULTISPINNER, "", -1) + var arr []string + _ = json.Unmarshal([]byte(s.Data), &arr) + s.Data = strings.Join(arr, "\n\n") + spinnerChars := spinner.CharSets[s.spinnerType] + if s.active { + s.mu.Unlock() + return + } + s.active = true + s.mu.Unlock() + go func() { + for { + for i := 0; i < 10; i++ { + + select { + case <-s.stopChan: + //s.Writer.Clear() + return + default: + s.mu.Lock() + if !s.active { + s.mu.Unlock() + return + } + s.Writer.Clear() + + output := strings.Replace(s.Data, SPINNER, spinnerChars[i], -1) + "\n" + + fmt.Fprint(s.Writer, output) + //fmt.Println(spinnerChars[i]) + s.Writer.Print() + s.mu.Unlock() + time.Sleep(s.Delay) + } + } + } + }() +} + +func (s *MultiSpinner) Stop() { + s.mu.Lock() + defer s.mu.Unlock() + if s.active { + s.active = false + s.stopChan <- struct{}{} + s.Writer.Clear() + } + s.Writer.Reset() +} diff --git a/pkg/sse/stream_events.go b/pkg/sse/stream_events.go index 2baa2f40..e1d1857d 100644 --- a/pkg/sse/stream_events.go +++ b/pkg/sse/stream_events.go @@ -6,9 +6,11 @@ import ( "encoding/json" "fmt" "net/http" + "os" "strings" "time" + "github.com/apoorvam/goterminal" "github.com/briandowns/spinner" "github.com/dream11/odin/internal/ui" "github.com/dream11/odin/pkg/request" @@ -67,6 +69,9 @@ func (sr *StreamRequest) Stream() StreamResponse { return StreamResponse{Error: err} } + writer := goterminal.New(os.Stdout) + ms := ui.New(spinner.CharSets[SPINNER_TYPE], SPINNER_DELAY, writer, SPINNER_TYPE) + data := bufio.NewScanner(resp.Body) s := spinner.New(spinner.CharSets[SPINNER_TYPE], SPINNER_DELAY) for data.Scan() { @@ -74,7 +79,11 @@ func (sr *StreamRequest) Stream() StreamResponse { if line == "" { continue } - if strings.Contains(line, ui.SPINNER) { + if strings.Contains(line, ui.MULTISPINNER) { + ms.Data = line + s.Stop() + ms.Start() + } else if strings.Contains(line, ui.SPINNER) { parts := strings.Split(line, ui.SPINNER) s.Prefix = parts[0] s.Suffix = parts[1] @@ -83,9 +92,11 @@ func (sr *StreamRequest) Stream() StreamResponse { if err != nil { logger.Error(err.Error()) } + ms.Stop() s.Start() } else { s.Stop() + ms.Stop() logger.Output(line + "\n") } }