mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-20 15:10:28 -04: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
5 changed files with 175 additions and 4 deletions
|
@ -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)
|
state, err := creator.Create(cmd.Context(), provider, config, flags.name, instanceType, flags.controllerCount, flags.workerCount)
|
||||||
|
spinner.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
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{
|
req := &initproto.InitRequest{
|
||||||
MasterSecret: masterSecret.Key,
|
MasterSecret: masterSecret.Key,
|
||||||
Salt: masterSecret.Salt,
|
Salt: masterSecret.Salt,
|
||||||
|
@ -141,6 +142,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
||||||
ConformanceMode: flags.conformance,
|
ConformanceMode: flags.conformance,
|
||||||
}
|
}
|
||||||
resp, err := initCall(cmd.Context(), newDialer(validator), flags.endpoint, req)
|
resp, err := initCall(cmd.Context(), newDialer(validator), flags.endpoint, req)
|
||||||
|
spinner.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
return fmt.Errorf("reading Constellation state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Println("Terminating ...")
|
spinner := newSpinner(cmd, "Terminating ", true)
|
||||||
|
spinner.Start()
|
||||||
if err := terminator.Terminate(cmd.Context(), stat); err != nil {
|
err := terminator.Terminate(cmd.Context(), stat)
|
||||||
|
spinner.Stop()
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("terminating Constellation cluster: %w", err)
|
return fmt.Errorf("terminating Constellation cluster: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue