Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 16a53f5

Browse files
committed
Parse multifile COMPOSE_FILE envvar, w/ side effect
Fixes #212 urfave/cli does not distinguish whether the first string in the slice comes from the envvar or is from a flag. Worse off, it appends the flag values to the envvar value instead of overriding it. To ensure the multifile envvar case is always handled, the first string must always be split. It gives a more consistent behavior, then, to split each string in the slice. With this side effect, the following is possible and would merge the compose files left to right: ``` $ COMPOSE_FILE=a.yml:b.yml ./libcompose-cli -f c.yml -f d.yml:e.yml up ``` Signed-off-by: Dustin Chapman <[email protected]>
1 parent e1a83f7 commit 16a53f5

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

cli/command/command.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package command
22

33
import (
44
"os"
5+
"strings"
56

67
"github.com/docker/libcompose/cli/app"
78
"github.com/docker/libcompose/project"
@@ -10,7 +11,14 @@ import (
1011

1112
// Populate updates the specified project context based on command line arguments and subcommands.
1213
func Populate(context *project.Context, c *cli.Context) {
13-
context.ComposeFiles = c.GlobalStringSlice("file")
14+
// urfave/cli does not distinguish whether the first string in the slice comes from the envvar
15+
// or is from a flag. Worse off, it appends the flag values to the envvar value instead of
16+
// overriding it. To ensure the multifile envvar case is always handled, the first string
17+
// must always be split. It gives a more consistent behavior, then, to split each string in
18+
// the slice.
19+
for _, v := range c.GlobalStringSlice("file") {
20+
context.ComposeFiles = append(context.ComposeFiles, strings.Split(v, string(os.PathListSeparator))...)
21+
}
1422

1523
if len(context.ComposeFiles) == 0 {
1624
context.ComposeFiles = []string{"docker-compose.yml"}

cli/docker/app/factory_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app
33
import (
44
"flag"
55
"io/ioutil"
6+
"os"
67
"path/filepath"
78
"testing"
89

@@ -55,3 +56,88 @@ func TestProjectFactoryProjectNameIsNormalized(t *testing.T) {
5556
}
5657
}
5758
}
59+
60+
func TestProjectFactoryFileArgMayContainMultipleFiles(t *testing.T) {
61+
sep := string(os.PathListSeparator)
62+
fileCases := []struct {
63+
requested []string
64+
available []string
65+
expected []string
66+
}{
67+
{
68+
requested: []string{},
69+
available: []string{"docker-compose.yml"},
70+
expected: []string{"docker-compose.yml"},
71+
},
72+
{
73+
requested: []string{},
74+
available: []string{"docker-compose.yml", "docker-compose.override.yml"},
75+
expected: []string{"docker-compose.yml", "docker-compose.override.yml"},
76+
},
77+
{
78+
requested: []string{"one.yml"},
79+
available: []string{"one.yml"},
80+
expected: []string{"one.yml"},
81+
},
82+
{
83+
requested: []string{"one.yml"},
84+
available: []string{"docker-compose.yml", "one.yml"},
85+
expected: []string{"one.yml"},
86+
},
87+
{
88+
requested: []string{"one.yml", "two.yml", "three.yml"},
89+
available: []string{"one.yml", "two.yml", "three.yml"},
90+
expected: []string{"one.yml", "two.yml", "three.yml"},
91+
},
92+
{
93+
requested: []string{"one.yml" + sep + "two.yml" + sep + "three.yml"},
94+
available: []string{"one.yml", "two.yml", "three.yml"},
95+
expected: []string{"one.yml", "two.yml", "three.yml"},
96+
},
97+
{
98+
requested: []string{"one.yml" + sep + "two.yml", "three.yml" + sep + "four.yml"},
99+
available: []string{"one.yml", "two.yml", "three.yml", "four.yml"},
100+
expected: []string{"one.yml", "two.yml", "three.yml", "four.yml"},
101+
},
102+
{
103+
requested: []string{"one.yml", "two.yml" + sep + "three.yml"},
104+
available: []string{"one.yml", "two.yml", "three.yml"},
105+
expected: []string{"one.yml", "two.yml", "three.yml"},
106+
},
107+
}
108+
109+
for _, fileCase := range fileCases {
110+
tmpDir, err := ioutil.TempDir("", "project-factory-test")
111+
if err != nil {
112+
t.Fatal(err)
113+
}
114+
defer os.RemoveAll(tmpDir)
115+
if err = os.Chdir(tmpDir); err != nil {
116+
t.Fatal(err)
117+
}
118+
119+
for _, file := range fileCase.available {
120+
ioutil.WriteFile(file, []byte(`hello:
121+
image: busybox`), 0700)
122+
}
123+
globalSet := flag.NewFlagSet("test", 0)
124+
// Set the project-name flag
125+
globalSet.String("project-name", "example", "doc")
126+
// Set the compose file flag
127+
fcr := cli.StringSlice(fileCase.requested)
128+
globalSet.Var(&fcr, "file", "doc")
129+
c := cli.NewContext(nil, globalSet, nil)
130+
factory := &ProjectFactory{}
131+
p, err := factory.Create(c)
132+
if err != nil {
133+
t.Fatal(err)
134+
}
135+
136+
for i, v := range p.(*project.Project).Files {
137+
if v != fileCase.expected[i] {
138+
t.Fatalf("requested %s, available %s, expected %s, got %s",
139+
fileCase.requested, fileCase.available, fileCase.expected, p.(*project.Project).Files)
140+
}
141+
}
142+
}
143+
}

0 commit comments

Comments
 (0)