Skip to content

Commit 857b0fd

Browse files
authored
feat: add/improve spinner for build and deploy (#322)
This commit modifies the progress meter so that, by default there is no step counter. It also modifies the responsibility for calling `Done()`, making it the job of the command rather than the client. This is because the client does not know how many commands will be executed and therefore cannot know when the progress bar is done. This commit also adds String() to the progress bar, and moves logging responsibility out of the deployer itself and fully into the client. Deployer#Deploy() now returns a DeploymentResult. Fixes: #296 Signed-off-by: Lance Ball <lball@redhat.com>
1 parent 5feb0e2 commit 857b0fd

File tree

8 files changed

+126
-45
lines changed

8 files changed

+126
-45
lines changed

buildpacks/builder.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,11 @@ func (builder *Builder) Build(ctx context.Context, f bosonFunc.Function) (err er
8888

8989
// Build based using the given builder.
9090
if err = packClient.Build(ctx, packOpts); err != nil {
91-
// If the builder was not showing logs, embed the full logs in the error.
92-
if !builder.Verbose {
91+
if ctx.Err() != nil {
92+
// received SIGINT
93+
return
94+
} else if !builder.Verbose {
95+
// If the builder was not showing logs, embed the full logs in the error.
9396
err = fmt.Errorf("%v\noutput: %s\n", err, logWriter.(*bytes.Buffer).String())
9497
}
9598
}

client.go

+30-8
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,23 @@ type Pusher interface {
5050
Push(ctx context.Context, f Function) (string, error)
5151
}
5252

53+
type Status int
54+
55+
const (
56+
Failed Status = iota
57+
Deployed
58+
Updated
59+
)
60+
61+
type DeploymentResult struct {
62+
Status Status
63+
URL string
64+
}
65+
5366
// Deployer of Function source to running status.
5467
type Deployer interface {
5568
// Deploy a Function of given name, using given backing image.
56-
Deploy(context.Context, Function) error
69+
Deploy(context.Context, Function) (DeploymentResult, error)
5770
}
5871

5972
// Runner runs the Function locally.
@@ -370,8 +383,7 @@ func (c *Client) Create(cfg Function) (err error) {
370383
// Build the Function at path. Errors if the Function is either unloadable or does
371384
// not contain a populated Image.
372385
func (c *Client) Build(ctx context.Context, path string) (err error) {
373-
374-
fmt.Println("Building function image")
386+
c.progressListener.Increment("Building function image")
375387

376388
f, err := NewFunction(path)
377389
if err != nil {
@@ -395,7 +407,8 @@ func (c *Client) Build(ctx context.Context, path string) (err error) {
395407

396408
// TODO: create a statu structure and return it here for optional
397409
// use by the cli for user echo (rather than rely on verbose mode here)
398-
fmt.Printf("Function image has been built, image: %v\n", f.Image)
410+
message := fmt.Sprintf("🙌 Function image built: %v", f.Image)
411+
c.progressListener.Increment(message)
399412

400413
return
401414
}
@@ -415,7 +428,7 @@ func (c *Client) Deploy(ctx context.Context, path string) (err error) {
415428
}
416429

417430
// Push the image for the named service to the configured registry
418-
fmt.Println("Pushing function image to the registry")
431+
c.progressListener.Increment("Pushing function image to the registry")
419432
imageDigest, err := c.pusher.Push(ctx, f)
420433
if err != nil {
421434
return
@@ -428,8 +441,15 @@ func (c *Client) Deploy(ctx context.Context, path string) (err error) {
428441
}
429442

430443
// Deploy a new or Update the previously-deployed Function
431-
fmt.Println("Deploying function to the cluster")
432-
return c.deployer.Deploy(ctx, f)
444+
c.progressListener.Increment("Deploying function to the cluster")
445+
result, err := c.deployer.Deploy(ctx, f)
446+
if result.Status == Deployed {
447+
c.progressListener.Increment(fmt.Sprintf("Function deployed at URL: %v", result.URL))
448+
} else if result.Status == Updated {
449+
c.progressListener.Increment(fmt.Sprintf("Function updated at URL: %v", result.URL))
450+
}
451+
452+
return err
433453
}
434454

435455
func (c *Client) Route(path string) (err error) {
@@ -527,7 +547,9 @@ func (n *noopPusher) Push(ctx context.Context, f Function) (string, error) { ret
527547

528548
type noopDeployer struct{ output io.Writer }
529549

530-
func (n *noopDeployer) Deploy(ctx context.Context, _ Function) error { return nil }
550+
func (n *noopDeployer) Deploy(ctx context.Context, _ Function) (DeploymentResult, error) {
551+
return DeploymentResult{}, nil
552+
}
531553

532554
type noopRunner struct{ output io.Writer }
533555

cmd/build.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
bosonFunc "github.com/boson-project/func"
1010
"github.com/boson-project/func/buildpacks"
11+
"github.com/boson-project/func/progress"
1112
"github.com/boson-project/func/prompt"
1213
)
1314

@@ -95,12 +96,23 @@ func runBuild(cmd *cobra.Command, _ []string) (err error) {
9596
builder := buildpacks.NewBuilder()
9697
builder.Verbose = config.Verbose
9798

99+
listener := progress.New()
100+
listener.Verbose = config.Verbose
101+
defer listener.Done()
102+
103+
context := cmd.Context()
104+
go func() {
105+
<-context.Done()
106+
listener.Done()
107+
}()
108+
98109
client := bosonFunc.New(
99110
bosonFunc.WithVerbose(config.Verbose),
100111
bosonFunc.WithRegistry(config.Registry), // for deriving image name when --image not provided explicitly.
101-
bosonFunc.WithBuilder(builder))
112+
bosonFunc.WithBuilder(builder),
113+
bosonFunc.WithProgressListener(listener))
102114

103-
return client.Build(cmd.Context(), config.Path)
115+
return client.Build(context, config.Path)
104116
}
105117

106118
type buildConfig struct {

cmd/deploy.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@ func runDeploy(cmd *cobra.Command, _ []string) (err error) {
121121
}
122122

123123
listener := progress.New()
124+
defer listener.Done()
124125

125126
deployer.Verbose = config.Verbose
127+
listener.Verbose = config.Verbose
128+
129+
context := cmd.Context()
130+
go func() {
131+
<-context.Done()
132+
listener.Done()
133+
}()
126134

127135
client := bosonFunc.New(
128136
bosonFunc.WithVerbose(config.Verbose),
@@ -133,12 +141,12 @@ func runDeploy(cmd *cobra.Command, _ []string) (err error) {
133141
bosonFunc.WithProgressListener(listener))
134142

135143
if config.Build {
136-
if err := client.Build(cmd.Context(), config.Path); err != nil {
144+
if err := client.Build(context, config.Path); err != nil {
137145
return err
138146
}
139147
}
140148

141-
return client.Deploy(cmd.Context(), config.Path)
149+
return client.Deploy(context, config.Path)
142150

143151
// NOTE: Namespace is optional, default is that used by k8s client
144152
// (for example kubectl usually uses ~/.kube/config)

cmd/root.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import (
77
"path/filepath"
88
"strings"
99

10-
"github.com/pkg/errors"
11-
1210
"github.com/mitchellh/go-homedir"
1311
"github.com/ory/viper"
1412
"github.com/spf13/cobra"
@@ -79,7 +77,7 @@ func Execute(ctx context.Context) {
7977
root.Version = version.String()
8078
// Execute the root of the command tree.
8179
if err := root.ExecuteContext(ctx); err != nil {
82-
if errors.Cause(err) == context.Canceled {
80+
if ctx.Err() != nil {
8381
os.Exit(130)
8482
return
8583
}

knative/deployer.go

+19-17
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
1717
v1 "knative.dev/serving/pkg/apis/serving/v1"
1818

19-
bosonFunc "github.com/boson-project/func"
19+
fn "github.com/boson-project/func"
2020
)
2121

2222
type Deployer struct {
@@ -37,30 +37,26 @@ func NewDeployer(namespaceOverride string) (deployer *Deployer, err error) {
3737
return
3838
}
3939

40-
func (d *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) (err error) {
40+
func (d *Deployer) Deploy(ctx context.Context, f fn.Function) (result fn.DeploymentResult, err error) {
4141

4242
client, err := NewServingClient(d.Namespace)
4343
if err != nil {
44-
return
44+
return fn.DeploymentResult{}, err
4545
}
4646

4747
_, err = client.GetService(f.Name)
4848
if err != nil {
4949
if errors.IsNotFound(err) {
5050

51-
// Let's create a new Service
52-
if d.Verbose {
53-
fmt.Printf("Creating Knative Service: %v\n", f.Name)
54-
}
5551
service, err := generateNewService(f.Name, f.ImageWithDigest(), f.Runtime, f.Env, f.Annotations)
5652
if err != nil {
5753
err = fmt.Errorf("knative deployer failed to generate the service: %v", err)
58-
return err
54+
return fn.DeploymentResult{}, err
5955
}
6056
err = client.CreateService(service)
6157
if err != nil {
6258
err = fmt.Errorf("knative deployer failed to deploy the service: %v", err)
63-
return err
59+
return fn.DeploymentResult{}, err
6460
}
6561

6662
if d.Verbose {
@@ -69,39 +65,45 @@ func (d *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) (err error)
6965
err, _ = client.WaitForService(f.Name, DefaultWaitingTimeout, wait.NoopMessageCallback())
7066
if err != nil {
7167
err = fmt.Errorf("knative deployer failed to wait for the service to become ready: %v", err)
72-
return err
68+
return fn.DeploymentResult{}, err
7369
}
7470

7571
route, err := client.GetRoute(f.Name)
7672
if err != nil {
7773
err = fmt.Errorf("knative deployer failed to get the route: %v", err)
78-
return err
74+
return fn.DeploymentResult{}, err
7975
}
8076

8177
fmt.Println("Function deployed at URL: " + route.Status.URL.String())
78+
return fn.DeploymentResult{
79+
Status: fn.Deployed,
80+
URL: route.Status.URL.String(),
81+
}, nil
82+
// fmt.Sprintf("Function deployed at URL: %v", route.Status.URL.String()), nil
8283

8384
} else {
8485
err = fmt.Errorf("knative deployer failed to get the service: %v", err)
85-
return err
86+
return fn.DeploymentResult{}, err
8687
}
8788
} else {
8889
// Update the existing Service
8990
err = client.UpdateServiceWithRetry(f.Name, updateService(f.ImageWithDigest(), f.Env, f.Annotations), 3)
9091
if err != nil {
9192
err = fmt.Errorf("knative deployer failed to update the service: %v", err)
92-
return err
93+
return fn.DeploymentResult{}, err
9394
}
9495

9596
route, err := client.GetRoute(f.Name)
9697
if err != nil {
9798
err = fmt.Errorf("knative deployer failed to get the route: %v", err)
98-
return err
99+
return fn.DeploymentResult{}, err
99100
}
100101

101-
fmt.Println("Function updated at URL: " + route.Status.URL.String())
102+
return fn.DeploymentResult{
103+
Status: fn.Updated,
104+
URL: route.Status.URL.String(),
105+
}, nil
102106
}
103-
104-
return nil
105107
}
106108

107109
func probeFor(url string) *corev1.Probe {

mock/deployer.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mock
22

33
import (
44
"context"
5+
56
bosonFunc "github.com/boson-project/func"
67
)
78

@@ -16,7 +17,7 @@ func NewDeployer() *Deployer {
1617
}
1718
}
1819

19-
func (i *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) error {
20+
func (i *Deployer) Deploy(ctx context.Context, f bosonFunc.Function) (bosonFunc.DeploymentResult, error) {
2021
i.DeployInvoked = true
21-
return i.DeployFn(f)
22+
return bosonFunc.DeploymentResult{}, i.DeployFn(f)
2223
}

0 commit comments

Comments
 (0)