Skip to content

Commit 5a063b7

Browse files
gloursndeloof
authored andcommitted
fix provider concurrent environment map accesses
Signed-off-by: Guillaume Lours <[email protected]>
1 parent ae49bba commit 5a063b7

File tree

6 files changed

+120
-12
lines changed

6 files changed

+120
-12
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ jobs:
190190
check-latest: true
191191
cache: true
192192

193+
- name: Build example provider
194+
run: make example-provider
195+
193196
- name: Build
194197
uses: docker/bake-action@v6
195198
with:

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ install: binary
7474
install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose
7575

7676
.PHONY: e2e-compose
77-
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
77+
e2e-compose: example-provider ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
7878
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
7979

8080
.PHONY: e2e-compose-standalone
@@ -87,6 +87,10 @@ build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and r
8787
.PHONY: build-and-e2e-compose-standalone
8888
build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
8989

90+
.PHONY: example-provider
91+
example-provider: ## build example provider for e2e tests
92+
go build -o bin/build/example-provider docs/examples/provider.go
93+
9094
.PHONY: mocks
9195
mocks:
9296
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/[email protected]

docs/examples/provider.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,30 @@ func main() {
3939
}
4040
}
4141

42+
type options struct {
43+
db string
44+
size int
45+
}
46+
4247
func composeCommand() *cobra.Command {
4348
c := &cobra.Command{
4449
Use: "compose EVENT",
4550
TraverseChildren: true,
4651
}
4752
c.PersistentFlags().String("project-name", "", "compose project name") // unused
53+
54+
var options options
4855
upCmd := &cobra.Command{
49-
Use: "up",
50-
Run: up,
56+
Use: "up",
57+
Run: func(_ *cobra.Command, args []string) {
58+
up(options, args)
59+
},
5160
Args: cobra.ExactArgs(1),
5261
}
53-
upCmd.Flags().String("type", "", "Database type (mysql, postgres, etc.)")
62+
63+
upCmd.Flags().StringVar(&options.db, "type", "", "Database type (mysql, postgres, etc.)")
5464
_ = upCmd.MarkFlagRequired("type")
55-
upCmd.Flags().Int("size", 10, "Database size in GB")
65+
upCmd.Flags().IntVar(&options.size, "size", 10, "Database size in GB")
5666
upCmd.Flags().String("name", "", "Name of the database to be created")
5767
_ = upCmd.MarkFlagRequired("name")
5868

@@ -71,13 +81,13 @@ func composeCommand() *cobra.Command {
7181

7282
const lineSeparator = "\n"
7383

74-
func up(_ *cobra.Command, args []string) {
84+
func up(options options, args []string) {
7585
servicename := args[0]
7686
fmt.Printf(`{ "type": "debug", "message": "Starting %s" }%s`, servicename, lineSeparator)
7787

78-
for i := 0; i < 100; i += 10 {
88+
for i := 0; i < options.size; i += 10 {
7989
time.Sleep(1 * time.Second)
80-
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i, lineSeparator)
90+
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i*100/options.size, lineSeparator)
8191
}
8292
fmt.Printf(`{ "type": "setenv", "message": "URL=https://magic.cloud/%s" }%s`, servicename, lineSeparator)
8393
}

pkg/compose/plugins.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,18 @@ import (
2727
"os/exec"
2828
"path/filepath"
2929
"strings"
30+
"sync"
3031

31-
"github.com/compose-spec/compose-go/v2/types"
32-
"github.com/docker/cli/cli-plugins/manager"
33-
"github.com/docker/cli/cli-plugins/socket"
34-
"github.com/docker/cli/cli/config"
3532
"github.com/docker/compose/v2/pkg/progress"
3633
"github.com/sirupsen/logrus"
3734
"github.com/spf13/cobra"
3835
"go.opentelemetry.io/otel"
3936
"go.opentelemetry.io/otel/propagation"
37+
38+
"github.com/compose-spec/compose-go/v2/types"
39+
"github.com/docker/cli/cli-plugins/manager"
40+
"github.com/docker/cli/cli-plugins/socket"
41+
"github.com/docker/cli/cli/config"
4042
)
4143

4244
type JsonMessage struct {
@@ -52,6 +54,8 @@ const (
5254
providerMetadataDirectory = "compose/providers"
5355
)
5456

57+
var mux sync.Mutex
58+
5559
func (s *composeService) runPlugin(ctx context.Context, project *types.Project, service types.ServiceConfig, command string) error {
5660
provider := *service.Provider
5761

@@ -70,6 +74,8 @@ func (s *composeService) runPlugin(ctx context.Context, project *types.Project,
7074
return err
7175
}
7276

77+
mux.Lock()
78+
defer mux.Unlock()
7379
for name, s := range project.Services {
7480
if _, ok := s.DependsOn[service.Name]; ok {
7581
prefix := strings.ToUpper(service.Name) + "_"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
services:
2+
test:
3+
image: alpine
4+
command: env
5+
depends_on:
6+
- provider1
7+
- provider2
8+
provider1:
9+
provider:
10+
type: example-provider
11+
options:
12+
name: provider1
13+
type: test1
14+
size: 1
15+
provider2:
16+
provider:
17+
type: example-provider
18+
options:
19+
name: provider2
20+
type: test2
21+
size: 2

pkg/e2e/providers_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"bufio"
21+
"fmt"
22+
"os"
23+
"path/filepath"
24+
"slices"
25+
"strings"
26+
"testing"
27+
28+
"gotest.tools/v3/assert"
29+
"gotest.tools/v3/icmd"
30+
)
31+
32+
func TestDependsOnMultipleProviders(t *testing.T) {
33+
provider, err := findExecutable("example-provider")
34+
assert.NilError(t, err)
35+
36+
path := fmt.Sprintf("%s%s%s", os.Getenv("PATH"), string(os.PathListSeparator), filepath.Dir(provider))
37+
c := NewParallelCLI(t, WithEnv("PATH="+path))
38+
const projectName = "depends-on-multiple-providers"
39+
t.Cleanup(func() {
40+
c.cleanupWithDown(t, projectName)
41+
})
42+
43+
res := c.RunDockerComposeCmd(t, "-f", "fixtures/providers/depends-on-multiple-providers.yaml", "--project-name", projectName, "up")
44+
res.Assert(t, icmd.Success)
45+
env := getEnv(res.Combined(), false)
46+
assert.Check(t, slices.Contains(env, "PROVIDER1_URL=https://magic.cloud/provider1"), env)
47+
assert.Check(t, slices.Contains(env, "PROVIDER2_URL=https://magic.cloud/provider2"), env)
48+
}
49+
50+
func getEnv(out string, run bool) []string {
51+
var env []string
52+
scanner := bufio.NewScanner(strings.NewReader(out))
53+
for scanner.Scan() {
54+
line := scanner.Text()
55+
if !run && strings.HasPrefix(line, "test-1 | ") {
56+
env = append(env, line[10:])
57+
}
58+
if run && strings.Contains(line, "=") && len(strings.Split(line, "=")) == 2 {
59+
env = append(env, line)
60+
}
61+
}
62+
slices.Sort(env)
63+
return env
64+
}

0 commit comments

Comments
 (0)