Repository: docker/compose
Pull request #12151 (2024-09-23 07:49:30)

check secret source exists, as bind mount would create target
**What I did**
Check secret.file exists otherwise target will be silently created as a folder by bind mount

**Related issue**
fixes https://github.com/docker/compose/issues/8305

**(not mandatory) A picture of a cute animal, if possible in relation to what you did**


thaJeztah: This will break uses of compose against a remote daemon (where the secret is stored remotely)
ndeloof: @thaJeztah good point. I would expect anyway `secrets` and `config` are only used when data is set by client, otherwise why not just declare a plain bind mount ?
Also see https://github.com/docker/compose/issues/11867 as a request to set those from client

In the meantime, adopted here an alternate approach to just warn user, and configure bind with `CreateHostPath: false`
thaJeztah: Ah! Yes I wanted to suggest the `CreateHostPath`, but thought this was using the old API (and we couldn't use the new one).

Yup! That's better

Patch:
diff --git a/pkg/compose/convergence.go b/pkg/compose/convergence.go
index 5ad3c94b1..4f90cda99 100644
--- a/pkg/compose/convergence.go
+++ b/pkg/compose/convergence.go
@@ -364,7 +364,12 @@ func containerReasonEvents(containers Containers, eventFunc func(string, string)
 const ServiceConditionRunningOrHealthy = "running_or_healthy"
 
 //nolint:gocyclo
-func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependant string, dependencies types.DependsOnConfig, containers Containers) error {
+func (s *composeService) waitDependencies(ctx context.Context, project *types.Project, dependant string, dependencies types.DependsOnConfig, containers Containers, timeout time.Duration) error {
+	if timeout > 0 {
+		withTimeout, cancelFunc := context.WithTimeout(ctx, timeout)
+		defer cancelFunc()
+		ctx = withTimeout
+	}
 	eg, _ := errgroup.WithContext(ctx)
 	w := progress.ContextWriter(ctx)
 	for dep, config := range dependencies {
@@ -454,7 +459,11 @@ func (s *composeService) waitDependencies(ctx context.Context, project *types.Pr
 			}
 		})
 	}
-	return eg.Wait()
+	err := eg.Wait()
+	if errors.Is(err, context.DeadlineExceeded) {
+		return fmt.Errorf("timeout waiting for dependencies")
+	}
+	return err
 }
 
 func shouldWaitForDependency(serviceName string, dependencyConfig types.ServiceDependency, project *types.Project) (bool, error) {
@@ -760,12 +769,12 @@ func (s *composeService) isServiceCompleted(ctx context.Context, containers Cont
 	return false, 0, nil
 }
 
-func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig, containers Containers) error {
+func (s *composeService) startService(ctx context.Context, project *types.Project, service types.ServiceConfig, containers Containers, timeout time.Duration) error {
 	if service.Deploy != nil && service.Deploy.Replicas != nil && *service.Deploy.Replicas == 0 {
 		return nil
 	}
 
-	err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, containers)
+	err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, containers, timeout)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/compose/create.go b/pkg/compose/create.go
index c4d1734fa..f4178d6af 100644
--- a/pkg/compose/create.go
+++ b/pkg/compose/create.go
@@ -1003,11 +1003,18 @@ func buildContainerSecretMounts(p types.Project, s types.ServiceConfig) ([]mount
 			continue
 		}
 
+		if _, err := os.Stat(definedSecret.File); os.IsNotExist(err) {
+			logrus.Warnf("secret file %s does not exist", definedSecret.Name)
+		}
+
 		mnt, err := buildMount(p, types.ServiceVolumeConfig{
 			Type:     types.VolumeTypeBind,
 			Source:   definedSecret.File,
 			Target:   target,
 			ReadOnly: true,
+			Bind: &types.ServiceVolumeBind{
+				CreateHostPath: false,
+			},
 		})
 		if err != nil {
 			return nil, err
diff --git a/pkg/compose/run.go b/pkg/compose/run.go
index cdafb4b8a..1bd769dbb 100644
--- a/pkg/compose/run.go
+++ b/pkg/compose/run.go
@@ -93,7 +93,7 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project,
 	}
 
 	if !opts.NoDeps {
-		if err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, observedState); err != nil {
+		if err := s.waitDependencies(ctx, project, service.Name, service.DependsOn, observedState, 0); err != nil {
 			return "", err
 		}
 	}
diff --git a/pkg/compose/start.go b/pkg/compose/start.go
index 7caeb3057..b5fcceebf 100644
--- a/pkg/compose/start.go
+++ b/pkg/compose/start.go
@@ -129,7 +129,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
 			return err
 		}
 
-		return s.startService(ctx, project, service, containers)
+		return s.startService(ctx, project, service, containers, options.WaitTimeout)
 	})
 	if err != nil {
 		return err
@@ -149,7 +149,7 @@ func (s *composeService) start(ctx context.Context, projectName string, options
 			defer cancel()
 		}
 
-		err = s.waitDependencies(ctx, project, project.Name, depends, containers)
+		err = s.waitDependencies(ctx, project, project.Name, depends, containers, 0)
 		if err != nil {
 			if errors.Is(ctx.Err(), context.DeadlineExceeded) {
 				return fmt.Errorf("application not healthy after %s", options.WaitTimeout)


Test patch:
diff --git a/pkg/compose/convergence_test.go b/pkg/compose/convergence_test.go
index e25ccd9f6..dc981fdaf 100644
--- a/pkg/compose/convergence_test.go
+++ b/pkg/compose/convergence_test.go
@@ -239,7 +239,7 @@ func TestWaitDependencies(t *testing.T) {
 			"db":    {Condition: ServiceConditionRunningOrHealthy},
 			"redis": {Condition: ServiceConditionRunningOrHealthy},
 		}
-		assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil))
+		assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil, 0))
 	})
 	t.Run("should skip dependencies with condition service_started", func(t *testing.T) {
 		dbService := types.ServiceConfig{Name: "db", Scale: intPtr(1)}
@@ -252,7 +252,7 @@ func TestWaitDependencies(t *testing.T) {
 			"db":    {Condition: types.ServiceConditionStarted, Required: true},
 			"redis": {Condition: types.ServiceConditionStarted, Required: true},
 		}
-		assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil))
+		assert.NilError(t, tested.waitDependencies(context.Background(), &project, "", dependencies, nil, 0))
 	})
 }