Просмотр исходного кода

fix race: poll for service completion. Using waitContainer might happend after container exit

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 4 лет назад
Родитель
Сommit
c83c46293f
4 измененных файлов с 45 добавлено и 57 удалено
  1. 1 1
      go.mod
  2. 2 2
      go.sum
  3. 6 22
      local/compose/build.go
  4. 36 32
      local/compose/convergence.go

+ 1 - 1
go.mod

@@ -17,7 +17,7 @@ require (
 	github.com/awslabs/goformation/v4 v4.15.6
 	github.com/awslabs/goformation/v4 v4.15.6
 	github.com/buger/goterm v1.0.0
 	github.com/buger/goterm v1.0.0
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
 	github.com/cnabio/cnab-to-oci v0.3.1-beta1
-	github.com/compose-spec/compose-go v0.0.0-20210412113016-e35895260882
+	github.com/compose-spec/compose-go v0.0.0-20210421142148-66b18e67d2c0
 	github.com/containerd/console v1.0.1
 	github.com/containerd/console v1.0.1
 	github.com/containerd/containerd v1.4.3
 	github.com/containerd/containerd v1.4.3
 	github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect
 	github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a // indirect

+ 2 - 2
go.sum

@@ -308,8 +308,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
-github.com/compose-spec/compose-go v0.0.0-20210412113016-e35895260882 h1:vLKOiHY9QPS1eiBvGfsEI7wR23B1bGNvwOF5aVl25Fs=
-github.com/compose-spec/compose-go v0.0.0-20210412113016-e35895260882/go.mod h1:6eIT9U2OgdHmkRD6szmqatCrWWEEUSwl/j2iJYH4jLo=
+github.com/compose-spec/compose-go v0.0.0-20210421142148-66b18e67d2c0 h1:aoUSQGFmWi8dn1OfT4H5Mx48u5wHt+n07nLFw5j1JXs=
+github.com/compose-spec/compose-go v0.0.0-20210421142148-66b18e67d2c0/go.mod h1:6eIT9U2OgdHmkRD6szmqatCrWWEEUSwl/j2iJYH4jLo=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
 github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
 github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
 github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw=
 github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 h1:9atoWyI9RtXFwf7UDbme/6M8Ud0rFrx+Q3ZWgSnsxtw=

+ 6 - 22
local/compose/build.go

@@ -19,9 +19,7 @@ package compose
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
-	"net/url"
 	"os"
 	"os"
-	"path"
 	"strings"
 	"strings"
 
 
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
@@ -50,7 +48,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
 		if service.Build != nil {
 		if service.Build != nil {
 			imageName := getImageName(service, project.Name)
 			imageName := getImageName(service, project.Name)
 			imagesToBuild = append(imagesToBuild, imageName)
 			imagesToBuild = append(imagesToBuild, imageName)
-			buildOptions, err := s.toBuildOptions(service, project.WorkingDir, imageName)
+			buildOptions, err := s.toBuildOptions(service, imageName)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
@@ -132,7 +130,7 @@ func (s *composeService) getBuildOptions(project *types.Project, images map[stri
 				continue
 				continue
 			}
 			}
 			imagesToBuild = append(imagesToBuild, imageName)
 			imagesToBuild = append(imagesToBuild, imageName)
-			opt, err := s.toBuildOptions(service, project.WorkingDir, imageName)
+			opt, err := s.toBuildOptions(service, imageName)
 			if err != nil {
 			if err != nil {
 				return nil, nil, err
 				return nil, nil, err
 			}
 			}
@@ -257,13 +255,10 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opts
 	return imagesBuilt, err
 	return imagesBuilt, err
 }
 }
 
 
-func (s *composeService) toBuildOptions(service types.ServiceConfig, contextPath string, imageTag string) (build.Options, error) {
+func (s *composeService) toBuildOptions(service types.ServiceConfig, imageTag string) (build.Options, error) {
 	var tags []string
 	var tags []string
 	tags = append(tags, imageTag)
 	tags = append(tags, imageTag)
 
 
-	if service.Build.Dockerfile == "" {
-		service.Build.Dockerfile = "Dockerfile"
-	}
 	var buildArgs map[string]string
 	var buildArgs map[string]string
 
 
 	var plats []specs.Platform
 	var plats []specs.Platform
@@ -275,22 +270,11 @@ func (s *composeService) toBuildOptions(service types.ServiceConfig, contextPath
 		plats = append(plats, p)
 		plats = append(plats, p)
 	}
 	}
 
 
-	var input build.Inputs
-	_, err := url.ParseRequestURI(service.Build.Context)
-	if err == nil {
-		input = build.Inputs{
+	return build.Options{
+		Inputs: build.Inputs{
 			ContextPath:    service.Build.Context,
 			ContextPath:    service.Build.Context,
 			DockerfilePath: service.Build.Dockerfile,
 			DockerfilePath: service.Build.Dockerfile,
-		}
-	} else {
-		input = build.Inputs{
-			ContextPath:    path.Join(contextPath, service.Build.Context),
-			DockerfilePath: path.Join(contextPath, service.Build.Context, service.Build.Dockerfile),
-		}
-	}
-
-	return build.Options{
-		Inputs:    input,
+		},
 		BuildArgs: flatten(mergeArgs(service.Build.Args, buildArgs)),
 		BuildArgs: flatten(mergeArgs(service.Build.Args, buildArgs)),
 		Tags:      tags,
 		Tags:      tags,
 		Target:    service.Build.Target,
 		Target:    service.Build.Target,

+ 36 - 32
local/compose/convergence.go

@@ -25,10 +25,10 @@ import (
 	"github.com/compose-spec/compose-go/types"
 	"github.com/compose-spec/compose-go/types"
 	"github.com/containerd/containerd/platforms"
 	"github.com/containerd/containerd/platforms"
 	moby "github.com/docker/docker/api/types"
 	moby "github.com/docker/docker/api/types"
-	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/network"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
 	specs "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/sirupsen/logrus"
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 
 
 	"github.com/docker/compose-cli/api/compose"
 	"github.com/docker/compose-cli/api/compose"
@@ -147,13 +147,13 @@ func getContainerProgressName(container moby.Container) string {
 func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 	eg, _ := errgroup.WithContext(ctx)
 	eg, _ := errgroup.WithContext(ctx)
 	for dep, config := range service.DependsOn {
 	for dep, config := range service.DependsOn {
-		switch config.Condition {
-		case "service_healthy":
-			eg.Go(func() error {
-				ticker := time.NewTicker(500 * time.Millisecond)
-				defer ticker.Stop()
-				for {
-					<-ticker.C
+		eg.Go(func() error {
+			ticker := time.NewTicker(500 * time.Millisecond)
+			defer ticker.Stop()
+			for {
+				<-ticker.C
+				switch config.Condition {
+				case types.ServiceConditionHealthy:
 					healthy, err := s.isServiceHealthy(ctx, project, dep)
 					healthy, err := s.isServiceHealthy(ctx, project, dep)
 					if err != nil {
 					if err != nil {
 						return err
 						return err
@@ -161,17 +161,26 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 					if healthy {
 					if healthy {
 						return nil
 						return nil
 					}
 					}
+				case types.ServiceConditionCompletedSuccessfully:
+					exited, code, err := s.isServiceCompleted(ctx, project, dep)
+					if err != nil {
+						return err
+					}
+					if exited {
+						if code != 0 {
+							return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, code)
+						}
+						return nil
+					}
+				case types.ServiceConditionStarted:
+					// already managed by InDependencyOrder
+					return nil
+				default:
+					logrus.Warnf("unsupported depends_on condition: %s", config.Condition)
+					return nil
 				}
 				}
-			})
-		case "service_completed_successfully":
-			exit, err := s.waitCompleted(ctx, project, dep)
-			if err != nil {
-				return err
 			}
 			}
-			if exit != 0 {
-				return fmt.Errorf("service %q didn't completed successfully: exit %d", dep, exit)
-			}
-		}
+		})
 	}
 	}
 	return eg.Wait()
 	return eg.Wait()
 }
 }
@@ -360,26 +369,21 @@ func (s *composeService) isServiceHealthy(ctx context.Context, project *types.Pr
 	return true, nil
 	return true, nil
 }
 }
 
 
-func (s *composeService) waitCompleted(ctx context.Context, project *types.Project, dep string) (int64, error) {
-	containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
-		Filters: filters.NewArgs(
-			projectFilter(project.Name),
-			serviceFilter(dep),
-		),
-	})
+func (s *composeService) isServiceCompleted(ctx context.Context, project *types.Project, dep string) (bool, int, error) {
+	containers, err := s.getContainers(ctx, project.Name, oneOffExclude, true, dep)
 	if err != nil {
 	if err != nil {
-		return 0, err
+		return false, 0, err
 	}
 	}
 	for _, c := range containers {
 	for _, c := range containers {
-		wait, errors := s.apiClient.ContainerWait(ctx, c.ID, container.WaitConditionNextExit)
-		select {
-		case w := <-wait:
-			return w.StatusCode, nil
-		case err := <-errors:
-			return 0, err
+		container, err := s.apiClient.ContainerInspect(ctx, c.ID)
+		if err != nil {
+			return false, 0, err
+		}
+		if container.State != nil && container.State.Status == "exited" {
+			return true, container.State.ExitCode, nil
 		}
 		}
 	}
 	}
-	return 0, nil
+	return false, 0, nil
 }
 }
 
 
 func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {
 func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {