Skip to content

Commit

Permalink
docker: introduce a new mount syntax
Browse files Browse the repository at this point in the history
Introduce a new more-block friendly for specifying mounts:

```hcl
config {
  image = "..."

  mount "target-path" {
    type = "..."
    volume_options { ... }
  }
}
```

We introduce this as the current `mounts` require a list of mounts, that
`hcl2` doesn't allow to have a map syntax.
  • Loading branch information
Mahmood Ali committed Dec 14, 2020
1 parent 3a7f455 commit e62b70c
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 72 deletions.
124 changes: 74 additions & 50 deletions drivers/docker/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,29 @@ var (
})),
"mac_address": hclspec.NewAttr("mac_address", "string", false),
"memory_hard_limit": hclspec.NewAttr("memory_hard_limit", "number", false),
"mount": hclspec.NewBlockMap("mount", []string{"target"}, hclspec.NewObject(map[string]*hclspec.Spec{
"type": hclspec.NewDefault(
hclspec.NewAttr("type", "string", false),
hclspec.NewLiteral("\"volume\""),
),
"source": hclspec.NewAttr("source", "string", false),
"readonly": hclspec.NewAttr("readonly", "bool", false),
"bind_options": hclspec.NewBlock("bind_options", false, hclspec.NewObject(map[string]*hclspec.Spec{
"propagation": hclspec.NewAttr("propagation", "string", false),
})),
"tmpfs_options": hclspec.NewBlock("tmpfs_options", false, hclspec.NewObject(map[string]*hclspec.Spec{
"size": hclspec.NewAttr("size", "number", false),
"mode": hclspec.NewAttr("mode", "number", false),
})),
"volume_options": hclspec.NewBlock("volume_options", false, hclspec.NewObject(map[string]*hclspec.Spec{
"no_copy": hclspec.NewAttr("no_copy", "bool", false),
"labels": hclspec.NewAttr("labels", "list(map(string))", false),
"driver_config": hclspec.NewBlock("driver_config", false, hclspec.NewObject(map[string]*hclspec.Spec{
"name": hclspec.NewAttr("name", "string", false),
"options": hclspec.NewAttr("options", "list(map(string))", false),
})),
})),
})),
"mounts": hclspec.NewBlockList("mounts", hclspec.NewObject(map[string]*hclspec.Spec{
"type": hclspec.NewDefault(
hclspec.NewAttr("type", "string", false),
Expand Down Expand Up @@ -398,56 +421,57 @@ var (
)

type TaskConfig struct {
Image string `codec:"image"`
AdvertiseIPv6Addr bool `codec:"advertise_ipv6_address"`
Args []string `codec:"args"`
Auth DockerAuth `codec:"auth"`
AuthSoftFail bool `codec:"auth_soft_fail"`
CapAdd []string `codec:"cap_add"`
CapDrop []string `codec:"cap_drop"`
Command string `codec:"command"`
CPUCFSPeriod int64 `codec:"cpu_cfs_period"`
CPUHardLimit bool `codec:"cpu_hard_limit"`
CPUSetCPUs string `codec:"cpuset_cpus"`
Devices []DockerDevice `codec:"devices"`
DNSSearchDomains []string `codec:"dns_search_domains"`
DNSOptions []string `codec:"dns_options"`
DNSServers []string `codec:"dns_servers"`
Entrypoint []string `codec:"entrypoint"`
ExtraHosts []string `codec:"extra_hosts"`
ForcePull bool `codec:"force_pull"`
Hostname string `codec:"hostname"`
Interactive bool `codec:"interactive"`
IPCMode string `codec:"ipc_mode"`
IPv4Address string `codec:"ipv4_address"`
IPv6Address string `codec:"ipv6_address"`
Labels hclutils.MapStrStr `codec:"labels"`
LoadImage string `codec:"load"`
Logging DockerLogging `codec:"logging"`
MacAddress string `codec:"mac_address"`
MemoryHardLimit int64 `codec:"memory_hard_limit"`
Mounts []DockerMount `codec:"mounts"`
NetworkAliases []string `codec:"network_aliases"`
NetworkMode string `codec:"network_mode"`
Runtime string `codec:"runtime"`
PidsLimit int64 `codec:"pids_limit"`
PidMode string `codec:"pid_mode"`
Ports []string `codec:"ports"`
PortMap hclutils.MapStrInt `codec:"port_map"`
Privileged bool `codec:"privileged"`
ImagePullTimeout string `codec:"image_pull_timeout"`
ReadonlyRootfs bool `codec:"readonly_rootfs"`
SecurityOpt []string `codec:"security_opt"`
ShmSize int64 `codec:"shm_size"`
StorageOpt map[string]string `codec:"storage_opt"`
Sysctl hclutils.MapStrStr `codec:"sysctl"`
TTY bool `codec:"tty"`
Ulimit hclutils.MapStrStr `codec:"ulimit"`
UTSMode string `codec:"uts_mode"`
UsernsMode string `codec:"userns_mode"`
Volumes []string `codec:"volumes"`
VolumeDriver string `codec:"volume_driver"`
WorkDir string `codec:"work_dir"`
Image string `codec:"image"`
AdvertiseIPv6Addr bool `codec:"advertise_ipv6_address"`
Args []string `codec:"args"`
Auth DockerAuth `codec:"auth"`
AuthSoftFail bool `codec:"auth_soft_fail"`
CapAdd []string `codec:"cap_add"`
CapDrop []string `codec:"cap_drop"`
Command string `codec:"command"`
CPUCFSPeriod int64 `codec:"cpu_cfs_period"`
CPUHardLimit bool `codec:"cpu_hard_limit"`
CPUSetCPUs string `codec:"cpuset_cpus"`
Devices []DockerDevice `codec:"devices"`
DNSSearchDomains []string `codec:"dns_search_domains"`
DNSOptions []string `codec:"dns_options"`
DNSServers []string `codec:"dns_servers"`
Entrypoint []string `codec:"entrypoint"`
ExtraHosts []string `codec:"extra_hosts"`
ForcePull bool `codec:"force_pull"`
Hostname string `codec:"hostname"`
Interactive bool `codec:"interactive"`
IPCMode string `codec:"ipc_mode"`
IPv4Address string `codec:"ipv4_address"`
IPv6Address string `codec:"ipv6_address"`
Labels hclutils.MapStrStr `codec:"labels"`
LoadImage string `codec:"load"`
Logging DockerLogging `codec:"logging"`
MacAddress string `codec:"mac_address"`
MemoryHardLimit int64 `codec:"memory_hard_limit"`
Mounts []DockerMount `codec:"mounts"`
MountMap map[string]DockerMount `codec:"mount"`
NetworkAliases []string `codec:"network_aliases"`
NetworkMode string `codec:"network_mode"`
Runtime string `codec:"runtime"`
PidsLimit int64 `codec:"pids_limit"`
PidMode string `codec:"pid_mode"`
Ports []string `codec:"ports"`
PortMap hclutils.MapStrInt `codec:"port_map"`
Privileged bool `codec:"privileged"`
ImagePullTimeout string `codec:"image_pull_timeout"`
ReadonlyRootfs bool `codec:"readonly_rootfs"`
SecurityOpt []string `codec:"security_opt"`
ShmSize int64 `codec:"shm_size"`
StorageOpt map[string]string `codec:"storage_opt"`
Sysctl hclutils.MapStrStr `codec:"sysctl"`
TTY bool `codec:"tty"`
Ulimit hclutils.MapStrStr `codec:"ulimit"`
UTSMode string `codec:"uts_mode"`
UsernsMode string `codec:"userns_mode"`
Volumes []string `codec:"volumes"`
VolumeDriver string `codec:"volume_driver"`
WorkDir string `codec:"work_dir"`
}

type DockerAuth struct {
Expand Down
43 changes: 43 additions & 0 deletions drivers/docker/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestConfig_ParseHCL(t *testing.T) {
Image: "redis:3.2",
Devices: []DockerDevice{},
Mounts: []DockerMount{},
MountMap: map[string]DockerMount{},
CPUCFSPeriod: 100000,
ImagePullTimeout: "5m",
},
Expand Down Expand Up @@ -56,6 +57,7 @@ func TestConfig_ParseJSON(t *testing.T) {
expected: TaskConfig{
Image: "bash:3",
Mounts: []DockerMount{},
MountMap: map[string]DockerMount{},
Devices: []DockerDevice{},
CPUCFSPeriod: 100000,
ImagePullTimeout: "5m",
Expand All @@ -67,6 +69,7 @@ func TestConfig_ParseJSON(t *testing.T) {
expected: TaskConfig{
Image: "bash:3",
Mounts: []DockerMount{},
MountMap: map[string]DockerMount{},
Devices: []DockerDevice{},
CPUCFSPeriod: 100000,
ImagePullTimeout: "5m",
Expand All @@ -78,6 +81,7 @@ func TestConfig_ParseJSON(t *testing.T) {
expected: TaskConfig{
Image: "bash:3",
Mounts: []DockerMount{},
MountMap: map[string]DockerMount{},
Devices: []DockerDevice{},
CPUCFSPeriod: 100000,
ImagePullTimeout: "5m",
Expand All @@ -89,6 +93,7 @@ func TestConfig_ParseJSON(t *testing.T) {
expected: TaskConfig{
Image: "bash:3",
Mounts: []DockerMount{},
MountMap: map[string]DockerMount{},
Devices: []DockerDevice{},
CPUCFSPeriod: 100000,
ImagePullTimeout: "5m",
Expand Down Expand Up @@ -229,6 +234,25 @@ config {
}
mac_address = "02:42:ac:11:00:02"
memory_hard_limit = 512
mount "/mount-bind-target" {
type = "bind"
source = "/bind-source-mount"
readonly = true
bind_options {
propagation = "rshared"
}
}
mount "/mount-tmpfs-target" {
type = "tmpfs"
readonly = true
tmpfs_options {
size = 30000
mode = 0777
}
}
mounts = [
{
type = "bind"
Expand Down Expand Up @@ -360,6 +384,25 @@ config {
}},
MacAddress: "02:42:ac:11:00:02",
MemoryHardLimit: 512,
MountMap: map[string]DockerMount{
"/mount-bind-target": {
Type: "bind",
Source: "/bind-source-mount",
ReadOnly: true,
BindOptions: DockerBindOptions{
Propagation: "rshared",
},
},
"/mount-tmpfs-target": {
Type: "tmpfs",
Source: "",
ReadOnly: true,
TmpfsOptions: DockerTmpfsOptions{
SizeBytes: 30000,
Mode: 511,
},
},
},
Mounts: []DockerMount{
{
Type: "bind",
Expand Down
62 changes: 40 additions & 22 deletions drivers/docker/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -947,32 +947,21 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T

// Setup mounts
for _, m := range driverConfig.Mounts {
hm, err := m.toDockerHostMount()
hm, err := d.toDockerMount(&m, task)
if err != nil {
return c, err
}

switch hm.Type {
case "bind":
hm.Source = expandPath(task.TaskDir().Dir, hm.Source)

// paths inside alloc dir are always allowed as they mount within
// a container, and treated as relative to task dir
if !d.config.Volumes.Enabled && !isParentPath(task.AllocDir, hm.Source) {
return c, fmt.Errorf(
"volumes are not enabled; cannot mount host path: %q %q",
hm.Source, task.AllocDir)
}
case "tmpfs":
// no source, so no sandbox check required
default: // "volume", but also any new thing that comes along
if !d.config.Volumes.Enabled {
return c, fmt.Errorf(
"volumes are not enabled; cannot mount volume: %q", hm.Source)
}
hostConfig.Mounts = append(hostConfig.Mounts, *hm)
}
for t, m := range driverConfig.MountMap {
if m.Target == "" {
m.Target = t
}

hostConfig.Mounts = append(hostConfig.Mounts, hm)
hm, err := d.toDockerMount(&m, task)
if err != nil {
return c, err
}
hostConfig.Mounts = append(hostConfig.Mounts, *hm)
}

// Setup DNS
Expand Down Expand Up @@ -1177,6 +1166,35 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T
}, nil
}

func (d *Driver) toDockerMount(m *DockerMount, task *drivers.TaskConfig) (*docker.HostMount, error) {
hm, err := m.toDockerHostMount()
if err != nil {
return nil, err
}

switch hm.Type {
case "bind":
hm.Source = expandPath(task.TaskDir().Dir, hm.Source)

// paths inside alloc dir are always allowed as they mount within
// a container, and treated as relative to task dir
if !d.config.Volumes.Enabled && !isParentPath(task.AllocDir, hm.Source) {
return nil, fmt.Errorf(
"volumes are not enabled; cannot mount host path: %q %q",
hm.Source, task.AllocDir)
}
case "tmpfs":
// no source, so no sandbox check required
default: // "volume", but also any new thing that comes along
if !d.config.Volumes.Enabled {
return nil, fmt.Errorf(
"volumes are not enabled; cannot mount volume: %q", hm.Source)
}
}

return &hm, nil
}

// detectIP of Docker container. Returns the first IP found as well as true if
// the IP should be advertised (bridge network IPs return false). Returns an
// empty string and false if no IP could be found.
Expand Down
Loading

0 comments on commit e62b70c

Please sign in to comment.