Merge pull request #983 from docker/refactor_progress_events

Refactor progress events
This commit is contained in:
Guillaume Tardif 2020-11-30 10:58:30 +01:00 committed by GitHub
commit 96d21fe448
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 145 additions and 176 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,
@ -120,21 +116,14 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex
groupDefinition,
)
if err != nil {
w.Event(progress.ErrorEvent(groupDisplay))
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 +134,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,15 +84,17 @@ 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 {
w.Event(progress.ErrorEvent(opts.Account))
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) {
w.Event(progress.ErrorEvent(opts.Account))
return volumes.Volume{}, err
} else {
result, err := accountClient.CheckNameAvailability(ctx, storage.AccountCheckNameAvailabilityParameters{
@ -100,32 +102,34 @@ func (cs *aciVolumeService) Create(ctx context.Context, name string, options int
Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
})
if err != nil {
w.Event(progress.ErrorEvent(opts.Account))
return volumes.Volume{}, err
}
if !*result.NameAvailable {
w.Event(progress.ErrorEvent(opts.Account))
return volumes.Volume{}, errors.New("error: " + *result.Message)
}
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))
return volumes.Volume{}, err
}
if err := future.WaitForCompletionRef(ctx, accountClient.Client); err != nil {
w.Event(errorEvent(opts.Account))
w.Event(progress.ErrorEvent(opts.Account))
return volumes.Volume{}, err
}
account, err = future.Result(accountClient)
if err != nil {
w.Event(errorEvent(opts.Account))
w.Event(progress.ErrorEvent(opts.Account))
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 +137,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))
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))
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))
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))
return err
}
w.Event(progress.RemovedEvent(r.LogicalID))
return nil
}
}

View File

@ -101,11 +101,7 @@ func (b *ecsAPIService) WaitStackCompletion(ctx context.Context, name string, op
}
}
}
w.Event(progress.Event{
ID: resource,
Status: progressStatus,
StatusText: fmt.Sprintf("%s %s", toCamelCase(status), reason),
})
w.Event(progress.NewEvent(resource, progressStatus, fmt.Sprintf("%s %s", toCamelCase(status), reason)))
}
if operation != stackCreate || stackErr != nil {
continue
@ -116,11 +112,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.ErrorMessageEvent(name, err.Error()))
}
}

View File

@ -117,29 +117,19 @@ 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 {
w.Event(progress.ErrorMessageEvent(getContainerName(container), "Error while Stopping"))
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 {
w.Event(progress.ErrorMessageEvent(getContainerName(container), "Error while Removing"))
return err
}
w.Event(progress.Event{
ID: getContainerName(container),
Text: "Removed",
Status: progress.Done,
})
w.Event(progress.RemovedEvent(getContainerName(container)))
return nil
})
}
@ -591,20 +581,14 @@ 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 {
w.Event(progress.ErrorEvent(networkEventName))
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 +598,15 @@ 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,
})
return errors.Wrapf(err, msg)
w.Event(progress.ErrorEvent(eventName))
return errors.Wrapf(err, fmt.Sprintf("failed to create network %s", networkID))
}
w.Event(progress.Event{
ID: fmt.Sprintf("Network %q", networkName),
Status: progress.Done,
StatusText: "Deleted",
})
w.Event(progress.RemovedEvent(eventName))
return nil
}
@ -643,12 +615,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,14 +625,11 @@ 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",
})
if err != nil {
w.Event(progress.ErrorEvent(eventName))
return err
}
w.Event(progress.CreatedEvent(eventName))
}
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
}

87
progress/event.go Normal file
View File

@ -0,0 +1,87 @@
/*
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
}
// ErrorMessageEvent creates a new Error Event with message
func ErrorMessageEvent(ID string, msg string) Event {
return NewEvent(ID, Error, msg)
}
// ErrorEvent creates a new Error Event
func ErrorEvent(ID string) Event {
return NewEvent(ID, Error, "Error")
}
// 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")
}
// RemovingEvent creates a new Removing in progress Event
func RemovingEvent(ID string) Event {
return NewEvent(ID, Working, "Removing")
}
// RemovedEvent creates a new removed (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