Browse Source

Merge pull request #983 from docker/refactor_progress_events

Refactor progress events
Guillaume Tardif 4 years ago
parent
commit
96d21fe448
8 changed files with 145 additions and 176 deletions
  1. 5 20
      aci/aci.go
  2. 16 28
      aci/volumes.go
  3. 8 6
      ecs/down.go
  4. 2 10
      ecs/wait.go
  5. 18 52
      local/compose.go
  6. 9 30
      local/convergence.go
  7. 87 0
      progress/event.go
  8. 0 30
      progress/writer.go

+ 5 - 20
aci/aci.go

@@ -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))
 		}
 	}
 

+ 16 - 28
aci/volumes.go

@@ -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 {

+ 8 - 6
ecs/down.go

@@ -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
 	}
 }

+ 2 - 10
ecs/wait.go

@@ -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()))
 		}
 	}
 

+ 18 - 52
local/compose.go

@@ -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
 	}

+ 9 - 30
local/convergence.go

@@ -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 - 0
progress/event.go

@@ -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()
+}

+ 0 - 30
progress/writer.go

@@ -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