Helper methods to create progress events, more homogeneous display for “creating”, “Created”, …

Signed-off-by: Guillame Tardif <guillaume.tardif@gmail.com>
This commit is contained in:
Guillame Tardif 2020-11-27 17:39:22 +01:00
parent f1565a52b2
commit 7ddd8e5e97
8 changed files with 129 additions and 169 deletions

View File

@ -107,11 +107,7 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
}
groupDisplay := "Group " + *groupDefinition.Name
w.Event(progress.Event{
ID: groupDisplay,
Status: progress.Working,
StatusText: "Waiting",
})
w.Event(progress.CreatingEvent(groupDisplay))
future, err := containerGroupsClient.CreateOrUpdate(
ctx,
@ -123,18 +119,10 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
return err
}
w.Event(progress.Event{
ID: groupDisplay,
Status: progress.Done,
StatusText: "Created",
})
w.Event(progress.CreatedEvent(groupDisplay))
for _, c := range *groupDefinition.Containers {
if c.Name != nil && *c.Name != convert.ComposeDNSSidecarName {
w.Event(progress.Event{
ID: *c.Name,
Status: progress.Working,
StatusText: "Waiting",
})
w.Event(progress.CreatingEvent(*c.Name))
}
}
@ -145,11 +133,7 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
for _, c := range *groupDefinition.Containers {
if c.Name != nil && *c.Name != convert.ComposeDNSSidecarName {
w.Event(progress.Event{
ID: *c.Name,
Status: progress.Done,
StatusText: "Done",
})
w.Event(progress.CreatedEvent(*c.Name))
}
}

View File

@ -84,14 +84,14 @@ func (cs *aciVolumeService) Create(ctx context.Context, name string, options int
return volumes.Volume{}, errors.New("could not read Azure VolumeCreateOptions struct from generic parameter")
}
w := progress.ContextWriter(ctx)
w.Event(event(opts.Account, progress.Working, "Validating"))
w.Event(progress.NewEvent(opts.Account, progress.Working, "Validating"))
accountClient, err := login.NewStorageAccountsClient(cs.aciContext.SubscriptionID)
if err != nil {
return volumes.Volume{}, err
}
account, err := accountClient.GetProperties(ctx, cs.aciContext.ResourceGroup, opts.Account, "")
if err == nil {
w.Event(event(opts.Account, progress.Done, "Use existing"))
w.Event(progress.NewEvent(opts.Account, progress.Done, "Use existing"))
} else if !account.HasHTTPStatus(http.StatusNotFound) {
return volumes.Volume{}, err
} else {
@ -107,25 +107,25 @@ func (cs *aciVolumeService) Create(ctx context.Context, name string, options int
}
parameters := defaultStorageAccountParams(cs.aciContext)
w.Event(event(opts.Account, progress.Working, "Creating"))
w.Event(progress.CreatingEvent(opts.Account))
future, err := accountClient.Create(ctx, cs.aciContext.ResourceGroup, opts.Account, parameters)
if err != nil {
w.Event(errorEvent(opts.Account))
w.Event(progress.ErrorEvent(opts.Account, "Error"))
return volumes.Volume{}, err
}
if err := future.WaitForCompletionRef(ctx, accountClient.Client); err != nil {
w.Event(errorEvent(opts.Account))
w.Event(progress.ErrorEvent(opts.Account, "Error"))
return volumes.Volume{}, err
}
account, err = future.Result(accountClient)
if err != nil {
w.Event(errorEvent(opts.Account))
w.Event(progress.ErrorEvent(opts.Account, "Error"))
return volumes.Volume{}, err
}
w.Event(event(opts.Account, progress.Done, "Created"))
w.Event(progress.CreatedEvent(opts.Account))
}
w.Event(event(name, progress.Working, "Creating"))
w.Event(progress.CreatingEvent(name))
fileShareClient, err := login.NewFileShareClient(cs.aciContext.SubscriptionID)
if err != nil {
return volumes.Volume{}, err
@ -133,38 +133,22 @@ func (cs *aciVolumeService) Create(ctx context.Context, name string, options int
fileShare, err := fileShareClient.Get(ctx, cs.aciContext.ResourceGroup, *account.Name, name, "")
if err == nil {
w.Event(errorEvent(name))
w.Event(progress.ErrorEvent(name, "Error"))
return volumes.Volume{}, errors.Wrapf(errdefs.ErrAlreadyExists, "Azure fileshare %q already exists", name)
}
if !fileShare.HasHTTPStatus(http.StatusNotFound) {
w.Event(errorEvent(name))
w.Event(progress.ErrorEvent(name, "Error"))
return volumes.Volume{}, err
}
fileShare, err = fileShareClient.Create(ctx, cs.aciContext.ResourceGroup, *account.Name, name, storage.FileShare{})
if err != nil {
w.Event(errorEvent(name))
w.Event(progress.ErrorEvent(name, "Error"))
return volumes.Volume{}, err
}
w.Event(event(name, progress.Done, "Created"))
w.Event(progress.CreatedEvent(name))
return toVolume(*account.Name, *fileShare.Name), nil
}
func event(resource string, status progress.EventStatus, text string) progress.Event {
return progress.Event{
ID: resource,
Status: status,
StatusText: text,
}
}
func errorEvent(resource string) progress.Event {
return progress.Event{
ID: resource,
Status: progress.Error,
StatusText: "Error",
}
}
func checkVolumeUsage(ctx context.Context, aciContext store.AciContext, id string) error {
containerGroups, err := getACIContainerGroups(ctx, aciContext.SubscriptionID, aciContext.ResourceGroup)
if err != nil {

View File

@ -65,11 +65,13 @@ func (b *ecsAPIService) previousStackEvents(ctx context.Context, project string)
func doDelete(ctx context.Context, delete func(ctx context.Context, arn string) error) func(r stackResource) error {
return func(r stackResource) error {
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: r.LogicalID,
Status: progress.Working,
StatusText: "DeleteInProgress",
})
return delete(ctx, r.ARN)
w.Event(progress.RemovingEvent(r.LogicalID))
err := delete(ctx, r.ARN)
if err != nil {
w.Event(progress.ErrorEvent(r.LogicalID, "Error"))
return err
}
w.Event(progress.RemovedEvent(r.LogicalID))
return nil
}
}

View File

@ -116,11 +116,7 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
}
stackErr = err
operation = stackDelete
w.Event(progress.Event{
ID: name,
Status: progress.Error,
StatusText: err.Error(),
})
w.Event(progress.ErrorEvent(name, err.Error()))
}
}

View File

@ -117,29 +117,17 @@ func (s *composeService) Down(ctx context.Context, projectName string) error {
for _, c := range list {
container := c
eg.Go(func() error {
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Stopping",
Status: progress.Working,
})
w.Event(progress.NewEvent(getContainerName(container), progress.Working, "Stopping"))
err := s.apiClient.ContainerStop(ctx, container.ID, nil)
if err != nil {
return err
}
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Removing",
Status: progress.Working,
})
w.Event(progress.RemovingEvent(getContainerName(container)))
err = s.apiClient.ContainerRemove(ctx, container.ID, moby.ContainerRemoveOptions{})
if err != nil {
return err
}
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Removed",
Status: progress.Done,
})
w.Event(progress.RemovedEvent(getContainerName(container)))
return nil
})
}
@ -591,20 +579,13 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
}
createOpts.IPAM.Config = append(createOpts.IPAM.Config, config)
}
networkEventName := fmt.Sprintf("Network %q", n.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", n.Name),
Status: progress.Working,
StatusText: "Create",
})
w.Event(progress.CreatingEvent(networkEventName))
if _, err := s.apiClient.NetworkCreate(ctx, n.Name, createOpts); err != nil {
return errors.Wrapf(err, "failed to create network %s", n.Name)
}
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", n.Name),
Status: progress.Done,
StatusText: "Created",
})
w.Event(progress.CreatedEvent(networkEventName))
return nil
}
return err
@ -614,27 +595,16 @@ func (s *composeService) ensureNetwork(ctx context.Context, n types.NetworkConfi
func (s *composeService) ensureNetworkDown(ctx context.Context, networkID string, networkName string) error {
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", networkName),
Status: progress.Working,
StatusText: "Delete",
})
eventName := fmt.Sprintf("Network %q", networkName)
w.Event(progress.RemovingEvent(eventName))
if err := s.apiClient.NetworkRemove(ctx, networkID); err != nil {
msg := fmt.Sprintf("failed to create network %s", networkID)
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", networkName),
Status: progress.Error,
StatusText: "Error: " + msg,
})
w.Event(progress.ErrorEvent(eventName, "Error: "+msg))
return errors.Wrapf(err, msg)
}
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", networkName),
Status: progress.Done,
StatusText: "Deleted",
})
w.Event(progress.RemovedEvent(eventName))
return nil
}
@ -643,12 +613,9 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
_, err := s.apiClient.VolumeInspect(ctx, volume.Name)
if err != nil {
if errdefs.IsNotFound(err) {
eventName := fmt.Sprintf("Volume %q", volume.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: fmt.Sprintf("Volume %q", volume.Name),
Status: progress.Working,
StatusText: "Create",
})
w.Event(progress.CreatingEvent(eventName))
// TODO we miss support for driver_opts and labels
_, err := s.apiClient.VolumeCreate(ctx, mobyvolume.VolumeCreateBody{
Labels: volume.Labels,
@ -656,11 +623,7 @@ func (s *composeService) ensureVolume(ctx context.Context, volume types.VolumeCo
Driver: volume.Driver,
DriverOpts: volume.DriverOpts,
})
w.Event(progress.Event{
ID: fmt.Sprintf("Volume %q", volume.Name),
Status: progress.Done,
StatusText: "Created",
})
w.Event(progress.CreatedEvent(eventName))
if err != nil {
return err
}

View File

@ -163,31 +163,21 @@ func getScale(config types.ServiceConfig) int {
}
func (s *composeService) createContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, name string, number int) error {
eventName := fmt.Sprintf("Service %q", service.Name)
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
Status: progress.Working,
StatusText: "Create",
})
w.Event(progress.CreatingEvent(eventName))
err := s.runContainer(ctx, project, service, name, number, nil)
if err != nil {
return err
}
w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
Status: progress.Done,
StatusText: "Created",
})
w.Event(progress.CreatedEvent(eventName))
return nil
}
func (s *composeService) recreateContainer(ctx context.Context, project *types.Project, service types.ServiceConfig, container moby.Container) error {
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
Status: progress.Working,
StatusText: "Recreate",
})
eventName := fmt.Sprintf("Service %q", service.Name)
w.Event(progress.NewEvent(eventName, progress.Working, "Recreate"))
err := s.apiClient.ContainerStop(ctx, container.ID, nil)
if err != nil {
return err
@ -210,11 +200,7 @@ func (s *composeService) recreateContainer(ctx context.Context, project *types.P
if err != nil {
return err
}
w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
Status: progress.Done,
StatusText: "Recreated",
})
w.Event(progress.NewEvent(eventName, progress.Done, "Recreated"))
setDependentLifecycle(project, service.Name, forceRecreate)
return nil
}
@ -234,20 +220,13 @@ func setDependentLifecycle(project *types.Project, service string, strategy stri
func (s *composeService) restartContainer(ctx context.Context, service types.ServiceConfig, container moby.Container) error {
w := progress.ContextWriter(ctx)
w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
Status: progress.Working,
StatusText: "Restart",
})
eventName := fmt.Sprintf("Service %q", service.Name)
w.Event(progress.NewEvent(eventName, progress.Working, "Restart"))
err := s.apiClient.ContainerStart(ctx, container.ID, moby.ContainerStartOptions{})
if err != nil {
return err
}
w.Event(progress.Event{
ID: fmt.Sprintf("Service %q", service.Name),
Status: progress.Done,
StatusText: "Restarted",
})
w.Event(progress.NewEvent(eventName, progress.Done, "Restarted"))
return nil
}

82
progress/event.go Normal file
View File

@ -0,0 +1,82 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package progress
import "time"
// EventStatus indicates the status of an action
type EventStatus int
const (
// Working means that the current task is working
Working EventStatus = iota
// Done means that the current task is done
Done
// Error means that the current task has errored
Error
)
// Event represents a progress event.
type Event struct {
ID string
Text string
Status EventStatus
StatusText string
startTime time.Time
endTime time.Time
spinner *spinner
}
// ErrorEvent creates a new Error Event
func ErrorEvent(ID string, msg string) Event {
return NewEvent(ID, Error, msg)
}
// CreatingEvent creates a new Create in progress Event
func CreatingEvent(ID string) Event {
return NewEvent(ID, Working, "Creating...")
}
// CreatedEvent creates a new Created (done) Event
func CreatedEvent(ID string) Event {
return NewEvent(ID, Done, "Created")
}
// CreatingEvent creates a new Create in progress Event
func RemovingEvent(ID string) Event {
return NewEvent(ID, Working, "Removing...")
}
// CreatedEvent creates a new Created (done) Event
func RemovedEvent(ID string) Event {
return NewEvent(ID, Done, "Removed")
}
// NewEvent new event
func NewEvent(ID string, status EventStatus, statusText string) Event {
return Event{
ID: ID,
Status: status,
StatusText: statusText,
}
}
func (e *Event) stop() {
e.endTime = time.Now()
e.spinner.Stop()
}

View File

@ -20,42 +20,12 @@ import (
"context"
"os"
"sync"
"time"
"github.com/containerd/console"
"github.com/moby/term"
"golang.org/x/sync/errgroup"
)
// EventStatus indicates the status of an action
type EventStatus int
const (
// Working means that the current task is working
Working EventStatus = iota
// Done means that the current task is done
Done
// Error means that the current task has errored
Error
)
// Event represents a progress event.
type Event struct {
ID string
Text string
Status EventStatus
StatusText string
startTime time.Time
endTime time.Time
spinner *spinner
}
func (e *Event) stop() {
e.endTime = time.Now()
e.spinner.Stop()
}
// Writer can write multiple progress events
type Writer interface {
Start(context.Context) error