mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-30 09:56:21 -05:00
Activity indicator for init command (#207)
* first version of spinner - implemented class with basic method - covered with dummy test - integrated with init command * Style and license remarks * fixed review remarks * fixed typo + integration of spinner with terminate command * integration of spinner with create command
This commit is contained in:
parent
acdcb535c0
commit
abe40de3e5
@ -114,7 +114,10 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
spinner := newSpinner(cmd, "Loading ", true)
|
||||
spinner.Start()
|
||||
state, err := creator.Create(cmd.Context(), provider, config, flags.name, instanceType, flags.controllerCount, flags.workerCount)
|
||||
spinner.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -124,7 +124,8 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
||||
return fmt.Errorf("parsing or generating master secret from file %s: %w", flags.masterSecretPath, err)
|
||||
}
|
||||
|
||||
cmd.Println("Initializing cluster ...")
|
||||
spinner := newSpinner(cmd, "Initializing cluster ", true)
|
||||
spinner.Start()
|
||||
req := &initproto.InitRequest{
|
||||
MasterSecret: masterSecret.Key,
|
||||
Salt: masterSecret.Salt,
|
||||
@ -141,6 +142,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
||||
ConformanceMode: flags.conformance,
|
||||
}
|
||||
resp, err := initCall(cmd.Context(), newDialer(validator), flags.endpoint, req)
|
||||
spinner.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
73
cli/internal/cmd/spinner.go
Normal file
73
cli/internal/cmd/spinner.go
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
spinnerStates = []string{"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"}
|
||||
dotsStates = []string{".", "..", "..."}
|
||||
)
|
||||
|
||||
type spinner struct {
|
||||
out *cobra.Command
|
||||
text string
|
||||
showDots bool
|
||||
delay time.Duration
|
||||
wg *sync.WaitGroup
|
||||
stop int32
|
||||
}
|
||||
|
||||
func newSpinner(c *cobra.Command, text string, showDots bool) *spinner {
|
||||
return &spinner{
|
||||
out: c,
|
||||
text: text,
|
||||
showDots: showDots,
|
||||
wg: &sync.WaitGroup{},
|
||||
delay: 100 * time.Millisecond,
|
||||
stop: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *spinner) Start() {
|
||||
s.wg.Add(1)
|
||||
go func() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for i := 0; ; i = (i + 1) % len(spinnerStates) {
|
||||
if atomic.LoadInt32(&s.stop) != 0 {
|
||||
break
|
||||
}
|
||||
dotsState := ""
|
||||
if s.showDots {
|
||||
dotsState = dotsStates[i%len(dotsStates)]
|
||||
}
|
||||
state := fmt.Sprintf("\r%s %s%s", spinnerStates[i], s.text, dotsState)
|
||||
s.out.Print(state)
|
||||
time.Sleep(s.delay)
|
||||
}
|
||||
|
||||
dotsState := ""
|
||||
if s.showDots {
|
||||
dotsState = dotsStates[len(dotsStates)-1]
|
||||
}
|
||||
finalState := fmt.Sprintf("\r%s%s ", s.text, dotsState)
|
||||
s.out.Println(finalState)
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *spinner) Stop() {
|
||||
atomic.StoreInt32(&s.stop, 1)
|
||||
s.wg.Wait()
|
||||
}
|
91
cli/internal/cmd/spinner_test.go
Normal file
91
cli/internal/cmd/spinner_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
baseWait = 1
|
||||
baseText = "Loading"
|
||||
)
|
||||
|
||||
func TestSpinnerInitialState(t *testing.T) {
|
||||
cmd := NewInitCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
var errOut bytes.Buffer
|
||||
cmd.SetErr(&errOut)
|
||||
|
||||
s := newSpinner(cmd, baseText, true)
|
||||
s.Start()
|
||||
time.Sleep(baseWait * time.Second)
|
||||
s.Stop()
|
||||
require.True(t, out.Len() > 0)
|
||||
require.True(t, errOut.Len() == 0)
|
||||
|
||||
outStr := out.String()
|
||||
require.True(t, strings.HasPrefix(outStr, generateAllStatesAsString(baseText, true)))
|
||||
}
|
||||
|
||||
func TestSpinnerFinalState(t *testing.T) {
|
||||
cmd := NewInitCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
var errOut bytes.Buffer
|
||||
cmd.SetErr(&errOut)
|
||||
|
||||
s := newSpinner(cmd, baseText, true)
|
||||
s.Start()
|
||||
time.Sleep(baseWait * time.Second)
|
||||
s.Stop()
|
||||
require.True(t, out.Len() > 0)
|
||||
require.True(t, errOut.Len() == 0)
|
||||
|
||||
outStr := out.String()
|
||||
require.True(t, strings.HasSuffix(outStr, baseText+"... \n"))
|
||||
}
|
||||
|
||||
func TestSpinnerDisabledShowDotsFlag(t *testing.T) {
|
||||
cmd := NewInitCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
var errOut bytes.Buffer
|
||||
cmd.SetErr(&errOut)
|
||||
|
||||
s := newSpinner(cmd, baseText, false)
|
||||
s.Start()
|
||||
time.Sleep(baseWait * time.Second)
|
||||
s.Stop()
|
||||
require.True(t, out.Len() > 0)
|
||||
require.True(t, errOut.Len() == 0)
|
||||
|
||||
outStr := out.String()
|
||||
require.True(t, strings.HasPrefix(outStr, generateAllStatesAsString(baseText, false)))
|
||||
require.True(t, strings.HasSuffix(outStr, baseText+" \n"))
|
||||
}
|
||||
|
||||
func generateAllStatesAsString(text string, showDots bool) string {
|
||||
var builder strings.Builder
|
||||
|
||||
for i := 0; i < len(spinnerStates); i++ {
|
||||
dotsState := ""
|
||||
if showDots {
|
||||
dotsState = dotsStates[i%len(dotsStates)]
|
||||
}
|
||||
builder.WriteString(fmt.Sprintf("\r%s %s%s", spinnerStates[i], text, dotsState))
|
||||
}
|
||||
|
||||
return builder.String()
|
||||
}
|
@ -47,9 +47,11 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.
|
||||
return fmt.Errorf("reading Constellation state: %w", err)
|
||||
}
|
||||
|
||||
cmd.Println("Terminating ...")
|
||||
|
||||
if err := terminator.Terminate(cmd.Context(), stat); err != nil {
|
||||
spinner := newSpinner(cmd, "Terminating ", true)
|
||||
spinner.Start()
|
||||
err := terminator.Terminate(cmd.Context(), stat)
|
||||
spinner.Stop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("terminating Constellation cluster: %w", err)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user