Bläddra i källkod

Create services in dependency order

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 år sedan
förälder
incheckning
8310bb2a91
3 ändrade filer med 227 tillägg och 100 borttagningar
  1. 110 100
      local/compose.go
  2. 60 0
      local/dependencies.go
  3. 57 0
      local/dependencies_test.go

+ 110 - 100
local/compose.go

@@ -28,7 +28,6 @@ import (
 	"sync"
 
 	"github.com/opencontainers/go-digest"
-	"golang.org/x/sync/errgroup"
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/compose-cli/api/compose"
@@ -71,113 +70,115 @@ func (s *local) Up(ctx context.Context, project *types.Project, detach bool) err
 		}
 	}
 
-	w := progress.ContextWriter(ctx)
-	eg, ctx := errgroup.WithContext(ctx)
 	for _, service := range project.Services {
-		service := service
-		eg.Go(func() error {
-			err := s.applyPullPolicy(ctx, service)
-			if err != nil {
-				return err
-			}
-
-			actual, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
-				Filters: filters.NewArgs(
-					filters.Arg("label", "com.docker.compose.project="+project.Name),
-					filters.Arg("label", "com.docker.compose.service="+service.Name),
-				),
-			})
-			if err != nil {
-				return err
-			}
+		err := s.applyPullPolicy(ctx, service)
+		if err != nil {
+			return err
+		}
+	}
 
-			expected, err := jsonHash(s)
-			if err != nil {
-				return err
-			}
+	err := inDependencyOrder(ctx, project, func(service types.ServiceConfig) error {
+		return s.ensureService(ctx, project, service)
+	})
+	return err
+}
 
-			if len(actual) == 0 {
-				w.Event(progress.Event{
-					ID:         fmt.Sprintf("Service %q", service.Name),
-					Status:     progress.Working,
-					StatusText: "Create",
-					Done:       false,
-				})
-				name := fmt.Sprintf("%s_%s", project.Name, service.Name)
-				err = s.runContainer(ctx, project, service, name, nil)
-				if err != nil {
-					return err
-				}
-				w.Event(progress.Event{
-					ID:         fmt.Sprintf("Service %q", service.Name),
-					Status:     progress.Done,
-					StatusText: "Created",
-					Done:       true,
-				})
-				return nil
-			}
+func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
+	actual, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+		Filters: filters.NewArgs(
+			filters.Arg("label", "com.docker.compose.project="+project.Name),
+			filters.Arg("label", "com.docker.compose.service="+service.Name),
+		),
+	})
+	if err != nil {
+		return err
+	}
 
-			container := actual[0]
-			diverged := container.Labels["com.docker.compose.config-hash"] != expected
-			if diverged {
-				w.Event(progress.Event{
-					ID:         fmt.Sprintf("Service %q", service.Name),
-					Status:     progress.Working,
-					StatusText: "Recreate",
-					Done:       false,
-				})
-				err := s.containerService.Stop(ctx, container.ID, nil)
-				if err != nil {
-					return err
-				}
-				name := getContainerName(container)
-				tmpName := fmt.Sprintf("%s_%s", container.ID[:12], name)
-				err = s.containerService.apiClient.ContainerRename(ctx, container.ID, tmpName)
-				if err != nil {
-					return err
-				}
-				err = s.runContainer(ctx, project, service, name, &container)
-				if err != nil {
-					return err
-				}
-				err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
-				if err != nil {
-					return err
-				}
-				w.Event(progress.Event{
-					ID:         fmt.Sprintf("Service %q", service.Name),
-					Status:     progress.Done,
-					StatusText: "Recreated",
-					Done:       true,
-				})
-				return nil
-			}
+	expected, err := jsonHash(s)
+	if err != nil {
+		return err
+	}
 
-			if container.State == "running" {
-				// already running, skip
-				return nil
-			}
+	w := progress.ContextWriter(ctx)
+	if len(actual) == 0 {
+		w.Event(progress.Event{
+			ID:         fmt.Sprintf("Service %q", service.Name),
+			Status:     progress.Working,
+			StatusText: "Create",
+			Done:       false,
+		})
+		name := fmt.Sprintf("%s_%s", project.Name, service.Name)
+		err = s.runContainer(ctx, project, service, name, nil)
+		if err != nil {
+			return err
+		}
+		w.Event(progress.Event{
+			ID:         fmt.Sprintf("Service %q", service.Name),
+			Status:     progress.Done,
+			StatusText: "Created",
+			Done:       true,
+		})
+		return nil
+	}
 
-			w.Event(progress.Event{
-				ID:         fmt.Sprintf("Service %q", service.Name),
-				Status:     progress.Working,
-				StatusText: "Restart",
-				Done:       false,
-			})
-			err = s.containerService.Start(ctx, container.ID)
-			if err != nil {
-				return err
-			}
-			w.Event(progress.Event{
-				ID:         fmt.Sprintf("Service %q", service.Name),
-				Status:     progress.Done,
-				StatusText: "Restarted",
-				Done:       true,
-			})
-			return nil
+	container := actual[0]
+	diverged := container.Labels["com.docker.compose.config-hash"] != expected
+	if diverged {
+		w.Event(progress.Event{
+			ID:         fmt.Sprintf("Service %q", service.Name),
+			Status:     progress.Working,
+			StatusText: "Recreate",
+			Done:       false,
 		})
+		err := s.containerService.Stop(ctx, container.ID, nil)
+		if err != nil {
+			return err
+		}
+		name := getContainerName(container)
+		tmpName := fmt.Sprintf("%s_%s", container.ID[:12], name)
+		err = s.containerService.apiClient.ContainerRename(ctx, container.ID, tmpName)
+		if err != nil {
+			return err
+		}
+		err = s.runContainer(ctx, project, service, name, &container)
+		if err != nil {
+			return err
+		}
+		err = s.containerService.Delete(ctx, container.ID, containers.DeleteRequest{})
+		if err != nil {
+			return err
+		}
+		w.Event(progress.Event{
+			ID:         fmt.Sprintf("Service %q", service.Name),
+			Status:     progress.Done,
+			StatusText: "Recreated",
+			Done:       true,
+		})
+		return nil
 	}
-	return eg.Wait()
+
+	if container.State == "running" {
+		// already running, skip
+		return nil
+	}
+
+	w.Event(progress.Event{
+		ID:         fmt.Sprintf("Service %q", service.Name),
+		Status:     progress.Working,
+		StatusText: "Restart",
+		Done:       false,
+	})
+	err = s.containerService.Start(ctx, container.ID)
+	if err != nil {
+		return err
+	}
+	w.Event(progress.Event{
+		ID:         fmt.Sprintf("Service %q", service.Name),
+		Status:     progress.Done,
+		StatusText: "Restarted",
+		Done:       true,
+	})
+	return nil
 }
 
 func getContainerName(c moby.Container) string {
@@ -711,3 +712,12 @@ func contains(slice []string, item string) bool {
 	}
 	return false
 }
+
+func containsAll(slice []string, items []string) bool {
+	for _, i := range items {
+		if !contains(slice, i) {
+			return false
+		}
+	}
+	return true
+}

+ 60 - 0
local/dependencies.go

@@ -0,0 +1,60 @@
+// +build local
+
+/*
+   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 local
+
+import (
+	"context"
+	"github.com/compose-spec/compose-go/types"
+	"golang.org/x/sync/errgroup"
+)
+
+func inDependencyOrder(ctx context.Context, project *types.Project, fn func(types.ServiceConfig) error) error {
+	eg, ctx := errgroup.WithContext(ctx)
+	var (
+		scheduled []string
+		ready []string
+	)
+	results := make(chan string)
+	for len(ready) < len(project.Services) {
+		for _, service := range project.Services {
+			if contains(scheduled, service.Name) {
+				continue
+			}
+			if containsAll(ready, service.GetDependencies()) {
+				service := service
+				scheduled = append(scheduled, service.Name)
+				eg.Go(func() error {
+					err := fn(service)
+					if err != nil {
+						close(results)
+						return err
+					}
+					results <- service.Name
+					return nil
+				})
+			}
+		}
+		result, ok := <-results
+		if !ok {
+			break
+		}
+		ready = append(ready, result)
+	}
+	return eg.Wait()
+}

+ 57 - 0
local/dependencies_test.go

@@ -0,0 +1,57 @@
+// +build local
+
+/*
+   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 local
+
+import (
+	"context"
+	"gotest.tools/v3/assert"
+	"testing"
+
+	"github.com/compose-spec/compose-go/types"
+)
+
+func TestInDependencyOrder(t *testing.T) {
+	order := make(chan string)
+	project := types.Project{
+		Services: []types.ServiceConfig{
+			{
+				Name:            "test1",
+				DependsOn: map[string]types.ServiceDependency{
+					"test2": {},
+				},
+			},
+			{
+				Name:            "test2",
+				DependsOn: map[string]types.ServiceDependency{
+					"test3": {},
+				},
+			},
+			{
+				Name:            "test3",
+			},
+		},
+	}
+	go inDependencyOrder(context.TODO(), &project, func(config types.ServiceConfig) error {
+		order <- config.Name
+		return nil
+	})
+	assert.Equal(t, <- order, "test3")
+	assert.Equal(t, <- order, "test2")
+	assert.Equal(t, <- order, "test1")
+}