Skip to content

Commit

Permalink
compose: implement Bind.CreateHostPath
Browse files Browse the repository at this point in the history
Fix issue 1652

Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed Apr 5, 2023
1 parent d125958 commit a620083
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 10 deletions.
32 changes: 32 additions & 0 deletions cmd/nerdctl/compose_up_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ package main

import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"

"github.com/containerd/nerdctl/pkg/testutil"
"gotest.tools/v3/assert"
"gotest.tools/v3/icmd"
)

Expand Down Expand Up @@ -50,3 +53,32 @@ services:
}
c.Assert(expected)
}

// https://github.com/containerd/nerdctl/issues/1652
func TestComposeUpBindCreateHostPath(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip(`FIXME: no support for Windows path: (error: "volume target must be an absolute path, got \"/mnt\")`)
}

base := testutil.NewBase(t)

var dockerComposeYAML = fmt.Sprintf(`
services:
test:
image: %s
command: sh -euxc "echo hi >/mnt/test"
volumes:
# ./foo should be automatically created
- ./foo:/mnt
`, testutil.CommonImage)

comp := testutil.NewComposeDir(t, dockerComposeYAML)
defer comp.CleanUp()

base.ComposeCmd("-f", comp.YAMLFullPath(), "up").AssertOK()
defer base.ComposeCmd("-f", comp.YAMLFullPath(), "down").AssertOK()
testFile := filepath.Join(comp.Dir(), "foo", "test")
testB, err := os.ReadFile(testFile)
assert.NilError(t, err)
assert.Equal(t, "hi\n", string(testB))
}
28 changes: 18 additions & 10 deletions pkg/composer/serviceparser/serviceparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/csv"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
Expand Down Expand Up @@ -178,6 +179,7 @@ func warnUnknownFields(svc types.ServiceConfig) {
type Container struct {
Name string // e.g., "compose-wordpress_wordpress_1"
RunArgs []string // {"--pull=never", ...}
Mkdir []string // For Bind.CreateHostPath
}

type Build struct {
Expand Down Expand Up @@ -659,11 +661,12 @@ func newContainer(project *types.Project, parsed *Service, i int) (*Container, e
}

for _, v := range svc.Volumes {
vStr, err := serviceVolumeConfigToFlagV(v, project)
vStr, mkdir, err := serviceVolumeConfigToFlagV(v, project)
if err != nil {
return nil, err
}
c.RunArgs = append(c.RunArgs, "-v="+vStr)
c.Mkdir = mkdir
}

for _, config := range svc.Configs {
Expand Down Expand Up @@ -733,7 +736,7 @@ func servicePortConfigToFlagP(c types.ServicePortConfig) (string, error) {
return s, nil
}

func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Project) (string, error) {
func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Project) (flagV string, mkdir []string, err error) {
if unknown := reflectutil.UnknownNonEmptyFields(&c,
"Type",
"Source",
Expand All @@ -746,7 +749,7 @@ func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Proj
}
if c.Bind != nil {
// c.Bind is expected to be a non-nil reference to an empty Bind struct
if unknown := reflectutil.UnknownNonEmptyFields(c.Bind); len(unknown) > 0 {
if unknown := reflectutil.UnknownNonEmptyFields(c.Bind, "CreateHostPath"); len(unknown) > 0 {
logrus.Warnf("Ignoring: volume: Bind: %+v", unknown)
}
}
Expand All @@ -758,10 +761,10 @@ func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Proj
}

if c.Target == "" {
return "", errors.New("volume target is missing")
return "", nil, errors.New("volume target is missing")
}
if !filepath.IsAbs(c.Target) {
return "", fmt.Errorf("volume target must be an absolute path, got %q", c.Target)
return "", nil, fmt.Errorf("volume target must be an absolute path, got %q", c.Target)
}

if c.Source == "" {
Expand All @@ -770,15 +773,15 @@ func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Proj
if c.ReadOnly {
s += ":ro"
}
return s, nil
return s, mkdir, nil
}

var src string
switch c.Type {
case "volume":
vol, ok := project.Volumes[c.Source]
if !ok {
return "", fmt.Errorf("invalid volume %q", c.Source)
return "", nil, fmt.Errorf("invalid volume %q", c.Source)
}
// c.Source is like "db_data", vol.Name is like "compose-wordpress_db_data"
src = vol.Name
Expand All @@ -787,16 +790,21 @@ func serviceVolumeConfigToFlagV(c types.ServiceVolumeConfig, project *types.Proj
var err error
src, err = filepath.Abs(src)
if err != nil {
return "", fmt.Errorf("invalid relative path %q: %w", c.Source, err)
return "", nil, fmt.Errorf("invalid relative path %q: %w", c.Source, err)
}
if c.Bind != nil && c.Bind.CreateHostPath {
if _, stErr := os.Stat(src); errors.Is(stErr, os.ErrNotExist) {
mkdir = append(mkdir, src)
}
}
default:
return "", fmt.Errorf("unsupported volume type: %q", c.Type)
return "", nil, fmt.Errorf("unsupported volume type: %q", c.Type)
}
s := fmt.Sprintf("%s:%s", src, c.Target)
if c.ReadOnly {
s += ":ro"
}
return s, nil
return s, mkdir, nil
}

func fileReferenceConfigToFlagV(c types.FileReferenceConfig, project *types.Project, secret bool) (string, error) {
Expand Down
7 changes: 7 additions & 0 deletions pkg/composer/up_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ func (c *Composer) upServiceContainer(ctx context.Context, service *serviceparse
logrus.Infof("Creating container %s", container.Name)
}

for _, f := range container.Mkdir {
logrus.Debugf("Creating a directory %q", f)
if err = os.MkdirAll(f, 0o755); err != nil {
return "", fmt.Errorf("failed to create a directory %q: %w", f, err)
}
}

tempDir, err := os.MkdirTemp(os.TempDir(), "compose-")
if err != nil {
return "", fmt.Errorf("error while creating/re-creating container %s: %w", container.Name, err)
Expand Down

0 comments on commit a620083

Please sign in to comment.