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

Commit f0d9f56

Browse files
committed
Merge pull request #138 from vdemeester/carry-pr-8
Carry #8 docker: support relative paths for volumes
2 parents a57f9e1 + 5fee93a commit f0d9f56

File tree

12 files changed

+144
-46
lines changed

12 files changed

+144
-46
lines changed

docker/container.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ func volumeBinds(volumes map[string]struct{}, container *dockerclient.Container)
302302
}
303303

304304
func (c *Container) createContainer(imageName, oldContainer string) (*dockerclient.APIContainers, error) {
305-
createOpts, err := ConvertToAPI(c.service.serviceConfig, c.name)
305+
createOpts, err := ConvertToAPI(c.service, c.name)
306306
if err != nil {
307307
return nil, err
308308
}

docker/convert.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ func isVolume(s string) bool {
3131
}
3232

3333
// ConvertToAPI converts a service configuration to a docker API container configuration.
34-
func ConvertToAPI(c *project.ServiceConfig, name string) (*dockerclient.CreateContainerOptions, error) {
35-
config, hostConfig, err := Convert(c)
34+
func ConvertToAPI(s *Service, name string) (*dockerclient.CreateContainerOptions, error) {
35+
config, hostConfig, err := Convert(s.serviceConfig, s.context)
3636
if err != nil {
3737
return nil, err
3838
}
@@ -45,12 +45,15 @@ func ConvertToAPI(c *project.ServiceConfig, name string) (*dockerclient.CreateCo
4545
return &result, nil
4646
}
4747

48-
func volumes(c *project.ServiceConfig) map[string]struct{} {
49-
vs := Filter(c.Volumes, isVolume)
48+
func volumes(c *project.ServiceConfig, ctx *Context) map[string]struct{} {
49+
volumes := make(map[string]struct{}, len(c.Volumes))
50+
for k, v := range c.Volumes {
51+
vol := ctx.ResourceLookup.ResolvePath(v, ctx.ComposeFile)
5052

51-
volumes := make(map[string]struct{}, len(vs))
52-
for _, v := range vs {
53-
volumes[v] = struct{}{}
53+
c.Volumes[k] = vol
54+
if isVolume(vol) {
55+
volumes[vol] = struct{}{}
56+
}
5457
}
5558
return volumes
5659
}
@@ -95,7 +98,7 @@ func ports(c *project.ServiceConfig) (map[dockerclient.Port]struct{}, map[docker
9598
}
9699

97100
// Convert converts a service configuration to an docker API structures (Config and HostConfig)
98-
func Convert(c *project.ServiceConfig) (*dockerclient.Config, *dockerclient.HostConfig, error) {
101+
func Convert(c *project.ServiceConfig, ctx *Context) (*dockerclient.Config, *dockerclient.HostConfig, error) {
99102
restartPolicy, err := restartPolicy(c)
100103
if err != nil {
101104
return nil, nil, err
@@ -125,7 +128,7 @@ func Convert(c *project.ServiceConfig) (*dockerclient.Config, *dockerclient.Host
125128
OpenStdin: c.StdinOpen,
126129
WorkingDir: c.WorkingDir,
127130
VolumeDriver: c.VolumeDriver,
128-
Volumes: volumes(c),
131+
Volumes: volumes(c, ctx),
129132
}
130133
hostConfig := &dockerclient.HostConfig{
131134
VolumesFrom: utils.CopySlice(c.VolumesFrom),

docker/convert_test.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package docker
22

33
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/docker/libcompose/lookup"
48
"github.com/docker/libcompose/project"
59
shlex "github.com/flynn/go-shlex"
610
"github.com/stretchr/testify/assert"
7-
"testing"
811
)
912

1013
func TestParseCommand(t *testing.T) {
@@ -15,18 +18,33 @@ func TestParseCommand(t *testing.T) {
1518
}
1619

1720
func TestParseBindsAndVolumes(t *testing.T) {
21+
ctx := &Context{}
22+
ctx.ComposeFile = "foo/docker-compose.yml"
23+
ctx.ResourceLookup = &lookup.FileConfigLookup{}
24+
25+
abs, err := filepath.Abs(".")
26+
assert.Nil(t, err)
27+
cfg, hostCfg, err := Convert(&project.ServiceConfig{
28+
Volumes: []string{"/foo", "/home:/home", "/bar/baz", ".:/home", "/usr/lib:/usr/lib:ro"},
29+
}, ctx)
30+
assert.Nil(t, err)
31+
assert.Equal(t, map[string]struct{}{"/foo": {}, "/bar/baz": {}}, cfg.Volumes)
32+
assert.Equal(t, []string{"/home:/home", abs + "/foo:/home", "/usr/lib:/usr/lib:ro"}, hostCfg.Binds)
33+
}
34+
35+
func TestParseLabels(t *testing.T) {
36+
ctx := &Context{}
37+
ctx.ComposeFile = "foo/docker-compose.yml"
38+
ctx.ResourceLookup = &lookup.FileConfigLookup{}
1839
bashCmd := "bash"
1940
fooLabel := "foo.label"
2041
fooLabelValue := "service.config.value"
2142
sc := &project.ServiceConfig{
2243
Entrypoint: project.NewCommand(bashCmd),
23-
Volumes: []string{"/foo", "/home:/home", "/bar/baz", "/usr/lib:/usr/lib:ro"},
2444
Labels: project.NewSliceorMap(map[string]string{fooLabel: "service.config.value"}),
2545
}
26-
cfg, hostCfg, err := Convert(sc)
46+
cfg, _, err := Convert(sc, ctx)
2747
assert.Nil(t, err)
28-
assert.Equal(t, map[string]struct{}{"/foo": {}, "/bar/baz": {}}, cfg.Volumes)
29-
assert.Equal(t, []string{"/home:/home", "/usr/lib:/usr/lib:ro"}, hostCfg.Binds)
3048

3149
cfg.Labels[fooLabel] = "FUN"
3250
cfg.Entrypoint[0] = "less"

docker/project.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import (
99

1010
// NewProject creates a Project with the specified context.
1111
func NewProject(context *Context) (*project.Project, error) {
12-
if context.ConfigLookup == nil {
13-
context.ConfigLookup = &lookup.FileConfigLookup{}
12+
if context.ResourceLookup == nil {
13+
context.ResourceLookup = &lookup.FileConfigLookup{}
1414
}
1515

1616
if context.EnvironmentLookup == nil {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
server:
2+
image: busybox
3+
volumes:
4+
- .:/path

integration/up_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package integration
22

33
import (
44
"fmt"
5+
"path/filepath"
56
"strings"
67

78
"github.com/docker/libcompose/utils"
@@ -278,3 +279,21 @@ func (s *RunSuite) TestLink(c *C) {
278279
fmt.Sprintf("/%s:/%s/%s", serverName, clientName, serverName),
279280
}))
280281
}
282+
283+
func (s *RunSuite) TestRelativeVolume(c *C) {
284+
p := s.ProjectFromText(c, "up", `
285+
server:
286+
image: busybox
287+
volumes:
288+
- .:/path
289+
`)
290+
291+
absPath, err := filepath.Abs(".")
292+
c.Assert(err, IsNil)
293+
serverName := fmt.Sprintf("%s_%s_1", p, "server")
294+
cn := s.GetContainerByName(c, serverName)
295+
296+
c.Assert(cn, NotNil)
297+
c.Assert(len(cn.Mounts), DeepEquals, 1)
298+
c.Assert(cn.Mounts[0].Source, DeepEquals, absPath)
299+
}

lookup/file.go

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,44 @@ package lookup
22

33
import (
44
"io/ioutil"
5+
"os"
56
"path"
7+
"path/filepath"
68
"strings"
79

810
"github.com/Sirupsen/logrus"
911
)
1012

11-
// FileConfigLookup is a "bare" structure that implements the project.ConfigLookup interface
13+
// relativePath returns the proper relative path for the given file path. If
14+
// the relativeTo string equals "-", then it means that it's from the stdin,
15+
// and the returned path will be the current working directory. Otherwise, if
16+
// file is really an absolute path, then it will be returned without any
17+
// changes. Otherwise, the returned path will be a combination of relativeTo
18+
// and file.
19+
func relativePath(file, relativeTo string) string {
20+
// stdin: return the current working directory if possible.
21+
if relativeTo == "-" {
22+
if cwd, err := os.Getwd(); err == nil {
23+
return cwd
24+
}
25+
}
26+
27+
// If the given file is already an absolute path, just return it.
28+
// Otherwise, the returned path will be relative to the given relativeTo
29+
// path.
30+
if filepath.IsAbs(file) {
31+
return file
32+
}
33+
34+
abs, err := filepath.Abs(filepath.Join(path.Dir(relativeTo), file))
35+
if err != nil {
36+
logrus.Errorf("Failed to get absolute directory: %s", err)
37+
return file
38+
}
39+
return abs
40+
}
41+
42+
// FileConfigLookup is a "bare" structure that implements the project.ResourceLookup interface
1243
type FileConfigLookup struct {
1344
}
1445

@@ -17,14 +48,27 @@ type FileConfigLookup struct {
1748
// If file starts with a slash ('/'), it tries to load it, otherwise it will build a
1849
// filename using the folder part of relativeTo joined with file.
1950
func (f *FileConfigLookup) Lookup(file, relativeTo string) ([]byte, string, error) {
20-
if strings.HasPrefix(file, "/") {
21-
logrus.Debugf("Reading file %s", file)
22-
bytes, err := ioutil.ReadFile(file)
23-
return bytes, file, err
51+
file = relativePath(file, relativeTo)
52+
logrus.Debugf("Reading file %s", file)
53+
bytes, err := ioutil.ReadFile(file)
54+
return bytes, file, err
55+
}
56+
57+
// ResolvePath returns the path to be used for the given path volume. This
58+
// function already takes care of relative paths.
59+
func (f *FileConfigLookup) ResolvePath(path, inFile string) string {
60+
vs := strings.SplitN(path, ":", 2)
61+
if len(vs) != 2 || filepath.IsAbs(vs[0]) {
62+
return path
2463
}
2564

26-
fileName := path.Join(path.Dir(relativeTo), file)
27-
logrus.Debugf("Reading file %s relative to %s", fileName, relativeTo)
28-
bytes, err := ioutil.ReadFile(fileName)
29-
return bytes, fileName, err
65+
if !strings.HasPrefix(vs[0], "./") && !strings.HasPrefix(vs[0], "~/") &&
66+
!strings.HasPrefix(vs[0], "/") {
67+
68+
logrus.Warnf("The mapping \"%s\" is ambiguous. In a future version of Docker, it will "+
69+
"designate a \"named\" volume (see https://github.com/docker/docker/pull/14242). "+
70+
"To prevent unexpected behaviour, change it to \"./%s\".", vs[0], vs[0])
71+
}
72+
vs[0] = relativePath(vs[0], inFile)
73+
return strings.Join(vs, ":")
3074
}

lookup/file_test.go

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

33
import (
4+
"fmt"
45
"io/ioutil"
56
"path/filepath"
67
"testing"
@@ -12,8 +13,12 @@ type input struct {
1213
}
1314

1415
func TestLookupError(t *testing.T) {
16+
abs, err := filepath.Abs(".")
17+
if err != nil {
18+
t.Fatalf("Failed to get absolute directory: %s", err)
19+
}
1520
invalids := map[input]string{
16-
input{"", ""}: "read .: is a directory",
21+
input{"", ""}: fmt.Sprintf("read %s: is a directory", abs),
1722
input{"", "/tmp/"}: "read /tmp: is a directory",
1823
input{"file", "/does/not/exists/"}: "open /does/not/exists/file: no such file or directory",
1924
input{"file", "/does/not/something"}: "open /does/not/file: no such file or directory",

project/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type Context struct {
3131
isOpen bool
3232
ServiceFactory ServiceFactory
3333
EnvironmentLookup EnvironmentLookup
34-
ConfigLookup ConfigLookup
34+
ResourceLookup ResourceLookup
3535
LoggerFactory logger.Factory
3636
IgnoreMissingConfig bool
3737
Project *Project

project/merge.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func mergeProject(p *Project, bytes []byte) (map[string]*ServiceConfig, error) {
4444
}
4545

4646
for name, data := range datas {
47-
data, err := parse(p.context.ConfigLookup, p.context.EnvironmentLookup, p.File, data, datas)
47+
data, err := parse(p.context.ResourceLookup, p.context.EnvironmentLookup, p.File, data, datas)
4848
if err != nil {
4949
logrus.Errorf("Failed to parse service %s: %v", name, err)
5050
return nil, err
@@ -70,7 +70,7 @@ func adjustValues(configs map[string]*ServiceConfig) {
7070
}
7171
}
7272

73-
func readEnvFile(configLookup ConfigLookup, inFile string, serviceData rawService) (rawService, error) {
73+
func readEnvFile(resourceLookup ResourceLookup, inFile string, serviceData rawService) (rawService, error) {
7474
var config ServiceConfig
7575

7676
if err := utils.Convert(serviceData, &config); err != nil {
@@ -81,15 +81,15 @@ func readEnvFile(configLookup ConfigLookup, inFile string, serviceData rawServic
8181
return serviceData, nil
8282
}
8383

84-
if configLookup == nil {
84+
if resourceLookup == nil {
8585
return nil, fmt.Errorf("Can not use env_file in file %s no mechanism provided to load files", inFile)
8686
}
8787

8888
vars := config.Environment.Slice()
8989

9090
for i := len(config.EnvFile.Slice()) - 1; i >= 0; i-- {
9191
envFile := config.EnvFile.Slice()[i]
92-
content, _, err := configLookup.Lookup(envFile, inFile)
92+
content, _, err := resourceLookup.Lookup(envFile, inFile)
9393
if err != nil {
9494
return nil, err
9595
}
@@ -154,8 +154,8 @@ func resolveBuild(inFile string, serviceData rawService) (rawService, error) {
154154
return serviceData, nil
155155
}
156156

157-
func parse(configLookup ConfigLookup, environmentLookup EnvironmentLookup, inFile string, serviceData rawService, datas rawServiceMap) (rawService, error) {
158-
serviceData, err := readEnvFile(configLookup, inFile, serviceData)
157+
func parse(resourceLookup ResourceLookup, environmentLookup EnvironmentLookup, inFile string, serviceData rawService, datas rawServiceMap) (rawService, error) {
158+
serviceData, err := readEnvFile(resourceLookup, inFile, serviceData)
159159
if err != nil {
160160
return nil, err
161161
}
@@ -175,7 +175,7 @@ func parse(configLookup ConfigLookup, environmentLookup EnvironmentLookup, inFil
175175
return serviceData, nil
176176
}
177177

178-
if configLookup == nil {
178+
if resourceLookup == nil {
179179
return nil, fmt.Errorf("Can not use extends in file %s no mechanism provided to files", inFile)
180180
}
181181

@@ -190,12 +190,12 @@ func parse(configLookup ConfigLookup, environmentLookup EnvironmentLookup, inFil
190190

191191
if file == "" {
192192
if serviceData, ok := datas[service]; ok {
193-
baseService, err = parse(configLookup, environmentLookup, inFile, serviceData, datas)
193+
baseService, err = parse(resourceLookup, environmentLookup, inFile, serviceData, datas)
194194
} else {
195195
return nil, fmt.Errorf("Failed to find service %s to extend", service)
196196
}
197197
} else {
198-
bytes, resolved, err := configLookup.Lookup(file, inFile)
198+
bytes, resolved, err := resourceLookup.Lookup(file, inFile)
199199
if err != nil {
200200
logrus.Errorf("Failed to lookup file %s: %v", file, err)
201201
return nil, err
@@ -216,7 +216,7 @@ func parse(configLookup ConfigLookup, environmentLookup EnvironmentLookup, inFil
216216
return nil, fmt.Errorf("Failed to find service %s in file %s", service, file)
217217
}
218218

219-
baseService, err = parse(configLookup, environmentLookup, resolved, baseService, baseRawServices)
219+
baseService, err = parse(resourceLookup, environmentLookup, resolved, baseService, baseRawServices)
220220
}
221221

222222
if err != nil {

0 commit comments

Comments
 (0)