mirror of https://github.com/docker/cli.git
Merge pull request #480 from dnephin/remove-unnecessary-deps
Remove 5k lines of unnecessary dependencies
This commit is contained in:
commit
e6006219ab
|
@ -1,8 +1,6 @@
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -14,7 +12,6 @@ import (
|
||||||
"github.com/docker/cli/internal/test/testutil"
|
"github.com/docker/cli/internal/test/testutil"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
@ -115,7 +112,7 @@ func TestParseRunWithInvalidArgs(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func TestParseRunVolumes(t *testing.T) {
|
func TestParseWithVolumes(t *testing.T) {
|
||||||
|
|
||||||
// A single volume
|
// A single volume
|
||||||
arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
|
arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
|
||||||
|
@ -584,161 +581,6 @@ func TestParseEntryPoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This tests the cases for binds which are generated through
|
|
||||||
// DecodeContainerConfig rather than Parse()
|
|
||||||
// nolint: gocyclo
|
|
||||||
func TestDecodeContainerConfigVolumes(t *testing.T) {
|
|
||||||
|
|
||||||
// Root to root
|
|
||||||
bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// No destination path
|
|
||||||
bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// // No destination path or mode
|
|
||||||
bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A whole lot of nothing
|
|
||||||
bindsOrVols = []string{`:`}
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A whole lot of nothing with no mode
|
|
||||||
bindsOrVols = []string{`::`}
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Too much including an invalid mode
|
|
||||||
wTmp := os.Getenv("TEMP")
|
|
||||||
bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Windows specific error tests
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// Volume which does not include a drive letter
|
|
||||||
bindsOrVols = []string{`\tmp`}
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root to C-Drive
|
|
||||||
bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container path that does not include a drive letter
|
|
||||||
bindsOrVols = []string{`c:\windows:\somewhere`}
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linux-specific error tests
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
// Just root
|
|
||||||
bindsOrVols = []string{`/`}
|
|
||||||
if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
|
|
||||||
t.Fatalf("binds %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
|
|
||||||
t.Fatalf("volume %v should have failed", bindsOrVols)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A single volume that looks like a bind mount passed in Volumes.
|
|
||||||
// This should be handled as a bind mount, not a volume.
|
|
||||||
vols := []string{`/foo:/bar`}
|
|
||||||
if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
|
|
||||||
t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
|
|
||||||
} else if hostConfig.Binds != nil {
|
|
||||||
t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
|
|
||||||
} else if _, exists := config.Volumes[vols[0]]; !exists {
|
|
||||||
t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
|
|
||||||
// to call DecodeContainerConfig. It effectively does what a client would
|
|
||||||
// do when calling the daemon by constructing a JSON stream of a
|
|
||||||
// ContainerConfigWrapper which is populated by the set of volume specs
|
|
||||||
// passed into it. It returns a config and a hostconfig which can be
|
|
||||||
// validated to ensure DecodeContainerConfig has manipulated the structures
|
|
||||||
// correctly.
|
|
||||||
func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) {
|
|
||||||
var (
|
|
||||||
b []byte
|
|
||||||
err error
|
|
||||||
c *container.Config
|
|
||||||
h *container.HostConfig
|
|
||||||
)
|
|
||||||
w := runconfig.ContainerConfigWrapper{
|
|
||||||
Config: &container.Config{
|
|
||||||
Volumes: map[string]struct{}{},
|
|
||||||
},
|
|
||||||
HostConfig: &container.HostConfig{
|
|
||||||
NetworkMode: "none",
|
|
||||||
Binds: binds,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, v := range volumes {
|
|
||||||
w.Config.Volumes[v] = struct{}{}
|
|
||||||
}
|
|
||||||
if b, err = json.Marshal(w); err != nil {
|
|
||||||
return nil, nil, errors.Errorf("Error on marshal %s", err.Error())
|
|
||||||
}
|
|
||||||
c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Errorf("Error parsing %s: %v", string(b), err)
|
|
||||||
}
|
|
||||||
if c == nil || h == nil {
|
|
||||||
return nil, nil, errors.Errorf("Empty config or hostconfig")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, h, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestValidateDevice(t *testing.T) {
|
func TestValidateDevice(t *testing.T) {
|
||||||
valid := []string{
|
valid := []string{
|
||||||
"/home",
|
"/home",
|
||||||
|
|
|
@ -32,7 +32,6 @@ github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
|
||||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
||||||
github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe
|
github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe
|
||||||
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
|
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
|
||||||
github.com/opencontainers/selinux v1.0.0-rc1
|
|
||||||
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
||||||
github.com/pmezard/go-difflib v1.0.0
|
github.com/pmezard/go-difflib v1.0.0
|
||||||
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
|
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
// Package parsers provides helper functions to parse and validate different type
|
|
||||||
// of string. It can be hosts, unix addresses, tcp addresses, filters, kernel
|
|
||||||
// operating system versions.
|
|
||||||
package parsers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value)
|
|
||||||
func ParseKeyValueOpt(opt string) (string, string, error) {
|
|
||||||
parts := strings.SplitN(opt, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseUintList parses and validates the specified string as the value
|
|
||||||
// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be
|
|
||||||
// one of the formats below. Note that duplicates are actually allowed in the
|
|
||||||
// input string. It returns a `map[int]bool` with available elements from `val`
|
|
||||||
// set to `true`.
|
|
||||||
// Supported formats:
|
|
||||||
// 7
|
|
||||||
// 1-6
|
|
||||||
// 0,3-4,7,8-10
|
|
||||||
// 0-0,0,1-7
|
|
||||||
// 03,1-3 <- this is gonna get parsed as [1,2,3]
|
|
||||||
// 3,2,1
|
|
||||||
// 0-2,3,1
|
|
||||||
func ParseUintList(val string) (map[int]bool, error) {
|
|
||||||
if val == "" {
|
|
||||||
return map[int]bool{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
availableInts := make(map[int]bool)
|
|
||||||
split := strings.Split(val, ",")
|
|
||||||
errInvalidFormat := fmt.Errorf("invalid format: %s", val)
|
|
||||||
|
|
||||||
for _, r := range split {
|
|
||||||
if !strings.Contains(r, "-") {
|
|
||||||
v, err := strconv.Atoi(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errInvalidFormat
|
|
||||||
}
|
|
||||||
availableInts[v] = true
|
|
||||||
} else {
|
|
||||||
split := strings.SplitN(r, "-", 2)
|
|
||||||
min, err := strconv.Atoi(split[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errInvalidFormat
|
|
||||||
}
|
|
||||||
max, err := strconv.Atoi(split[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, errInvalidFormat
|
|
||||||
}
|
|
||||||
if max < min {
|
|
||||||
return nil, errInvalidFormat
|
|
||||||
}
|
|
||||||
for i := min; i <= max; i++ {
|
|
||||||
availableInts[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return availableInts, nil
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
SysInfo stores information about which features a kernel supports.
|
|
|
@ -1,12 +0,0 @@
|
||||||
// +build !linux,!windows
|
|
||||||
|
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NumCPU returns the number of CPUs
|
|
||||||
func NumCPU() int {
|
|
||||||
return runtime.NumCPU()
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// numCPU queries the system for the count of threads available
|
|
||||||
// for use to this process.
|
|
||||||
//
|
|
||||||
// Issues two syscalls.
|
|
||||||
// Returns 0 on errors. Use |runtime.NumCPU| in that case.
|
|
||||||
func numCPU() int {
|
|
||||||
// Gets the affinity mask for a process: The very one invoking this function.
|
|
||||||
pid, _, _ := syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
|
||||||
|
|
||||||
var mask [1024 / 64]uintptr
|
|
||||||
_, _, err := syscall.RawSyscall(syscall.SYS_SCHED_GETAFFINITY, pid, uintptr(len(mask)*8), uintptr(unsafe.Pointer(&mask[0])))
|
|
||||||
if err != 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// For every available thread a bit is set in the mask.
|
|
||||||
ncpu := 0
|
|
||||||
for _, e := range mask {
|
|
||||||
if e == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ncpu += int(popcnt(uint64(e)))
|
|
||||||
}
|
|
||||||
return ncpu
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumCPU returns the number of CPUs which are currently online
|
|
||||||
func NumCPU() int {
|
|
||||||
if ncpu := numCPU(); ncpu > 0 {
|
|
||||||
return ncpu
|
|
||||||
}
|
|
||||||
return runtime.NumCPU()
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
getCurrentProcess = kernel32.NewProc("GetCurrentProcess")
|
|
||||||
getProcessAffinityMask = kernel32.NewProc("GetProcessAffinityMask")
|
|
||||||
)
|
|
||||||
|
|
||||||
func numCPU() int {
|
|
||||||
// Gets the affinity mask for a process
|
|
||||||
var mask, sysmask uintptr
|
|
||||||
currentProcess, _, _ := getCurrentProcess.Call()
|
|
||||||
ret, _, _ := getProcessAffinityMask.Call(currentProcess, uintptr(unsafe.Pointer(&mask)), uintptr(unsafe.Pointer(&sysmask)))
|
|
||||||
if ret == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
// For every available thread a bit is set in the mask.
|
|
||||||
ncpu := int(popcnt(uint64(mask)))
|
|
||||||
return ncpu
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumCPU returns the number of CPUs which are currently online
|
|
||||||
func NumCPU() int {
|
|
||||||
if ncpu := numCPU(); ncpu > 0 {
|
|
||||||
return ncpu
|
|
||||||
}
|
|
||||||
return runtime.NumCPU()
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
import "github.com/docker/docker/pkg/parsers"
|
|
||||||
|
|
||||||
// SysInfo stores information about which features a kernel supports.
|
|
||||||
// TODO Windows: Factor out platform specific capabilities.
|
|
||||||
type SysInfo struct {
|
|
||||||
// Whether the kernel supports AppArmor or not
|
|
||||||
AppArmor bool
|
|
||||||
// Whether the kernel supports Seccomp or not
|
|
||||||
Seccomp bool
|
|
||||||
|
|
||||||
cgroupMemInfo
|
|
||||||
cgroupCPUInfo
|
|
||||||
cgroupBlkioInfo
|
|
||||||
cgroupCpusetInfo
|
|
||||||
cgroupPids
|
|
||||||
|
|
||||||
// Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work
|
|
||||||
IPv4ForwardingDisabled bool
|
|
||||||
|
|
||||||
// Whether bridge-nf-call-iptables is supported or not
|
|
||||||
BridgeNFCallIPTablesDisabled bool
|
|
||||||
|
|
||||||
// Whether bridge-nf-call-ip6tables is supported or not
|
|
||||||
BridgeNFCallIP6TablesDisabled bool
|
|
||||||
|
|
||||||
// Whether the cgroup has the mountpoint of "devices" or not
|
|
||||||
CgroupDevicesEnabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type cgroupMemInfo struct {
|
|
||||||
// Whether memory limit is supported or not
|
|
||||||
MemoryLimit bool
|
|
||||||
|
|
||||||
// Whether swap limit is supported or not
|
|
||||||
SwapLimit bool
|
|
||||||
|
|
||||||
// Whether soft limit is supported or not
|
|
||||||
MemoryReservation bool
|
|
||||||
|
|
||||||
// Whether OOM killer disable is supported or not
|
|
||||||
OomKillDisable bool
|
|
||||||
|
|
||||||
// Whether memory swappiness is supported or not
|
|
||||||
MemorySwappiness bool
|
|
||||||
|
|
||||||
// Whether kernel memory limit is supported or not
|
|
||||||
KernelMemory bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type cgroupCPUInfo struct {
|
|
||||||
// Whether CPU shares is supported or not
|
|
||||||
CPUShares bool
|
|
||||||
|
|
||||||
// Whether CPU CFS(Completely Fair Scheduler) period is supported or not
|
|
||||||
CPUCfsPeriod bool
|
|
||||||
|
|
||||||
// Whether CPU CFS(Completely Fair Scheduler) quota is supported or not
|
|
||||||
CPUCfsQuota bool
|
|
||||||
|
|
||||||
// Whether CPU real-time period is supported or not
|
|
||||||
CPURealtimePeriod bool
|
|
||||||
|
|
||||||
// Whether CPU real-time runtime is supported or not
|
|
||||||
CPURealtimeRuntime bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type cgroupBlkioInfo struct {
|
|
||||||
// Whether Block IO weight is supported or not
|
|
||||||
BlkioWeight bool
|
|
||||||
|
|
||||||
// Whether Block IO weight_device is supported or not
|
|
||||||
BlkioWeightDevice bool
|
|
||||||
|
|
||||||
// Whether Block IO read limit in bytes per second is supported or not
|
|
||||||
BlkioReadBpsDevice bool
|
|
||||||
|
|
||||||
// Whether Block IO write limit in bytes per second is supported or not
|
|
||||||
BlkioWriteBpsDevice bool
|
|
||||||
|
|
||||||
// Whether Block IO read limit in IO per second is supported or not
|
|
||||||
BlkioReadIOpsDevice bool
|
|
||||||
|
|
||||||
// Whether Block IO write limit in IO per second is supported or not
|
|
||||||
BlkioWriteIOpsDevice bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type cgroupCpusetInfo struct {
|
|
||||||
// Whether Cpuset is supported or not
|
|
||||||
Cpuset bool
|
|
||||||
|
|
||||||
// Available Cpuset's cpus
|
|
||||||
Cpus string
|
|
||||||
|
|
||||||
// Available Cpuset's memory nodes
|
|
||||||
Mems string
|
|
||||||
}
|
|
||||||
|
|
||||||
type cgroupPids struct {
|
|
||||||
// Whether Pids Limit is supported or not
|
|
||||||
PidsLimit bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCpusetCpusAvailable returns `true` if the provided string set is contained
|
|
||||||
// in cgroup's cpuset.cpus set, `false` otherwise.
|
|
||||||
// If error is not nil a parsing error occurred.
|
|
||||||
func (c cgroupCpusetInfo) IsCpusetCpusAvailable(provided string) (bool, error) {
|
|
||||||
return isCpusetListAvailable(provided, c.Cpus)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCpusetMemsAvailable returns `true` if the provided string set is contained
|
|
||||||
// in cgroup's cpuset.mems set, `false` otherwise.
|
|
||||||
// If error is not nil a parsing error occurred.
|
|
||||||
func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) {
|
|
||||||
return isCpusetListAvailable(provided, c.Mems)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCpusetListAvailable(provided, available string) (bool, error) {
|
|
||||||
parsedProvided, err := parsers.ParseUintList(provided)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
parsedAvailable, err := parsers.ParseUintList(available)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
for k := range parsedProvided {
|
|
||||||
if !parsedAvailable[k] {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns bit count of 1, used by NumCPU
|
|
||||||
func popcnt(x uint64) (n byte) {
|
|
||||||
x -= (x >> 1) & 0x5555555555555555
|
|
||||||
x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
|
|
||||||
x += x >> 4
|
|
||||||
x &= 0x0f0f0f0f0f0f0f0f
|
|
||||||
x *= 0x0101010101010101
|
|
||||||
return byte(x >> 56)
|
|
||||||
}
|
|
|
@ -1,259 +0,0 @@
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SeccompModeFilter refers to the syscall argument SECCOMP_MODE_FILTER.
|
|
||||||
SeccompModeFilter = uintptr(2)
|
|
||||||
)
|
|
||||||
|
|
||||||
func findCgroupMountpoints() (map[string]string, error) {
|
|
||||||
cgMounts, err := cgroups.GetCgroupMounts(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to parse cgroup information: %v", err)
|
|
||||||
}
|
|
||||||
mps := make(map[string]string)
|
|
||||||
for _, m := range cgMounts {
|
|
||||||
for _, ss := range m.Subsystems {
|
|
||||||
mps[ss] = m.Mountpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mps, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new SysInfo, using the filesystem to detect which features
|
|
||||||
// the kernel supports. If `quiet` is `false` warnings are printed in logs
|
|
||||||
// whenever an error occurs or misconfigurations are present.
|
|
||||||
func New(quiet bool) *SysInfo {
|
|
||||||
sysInfo := &SysInfo{}
|
|
||||||
cgMounts, err := findCgroupMountpoints()
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("Failed to parse cgroup information: %v", err)
|
|
||||||
} else {
|
|
||||||
sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet)
|
|
||||||
sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet)
|
|
||||||
sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet)
|
|
||||||
sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet)
|
|
||||||
sysInfo.cgroupPids = checkCgroupPids(quiet)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, ok := cgMounts["devices"]
|
|
||||||
sysInfo.CgroupDevicesEnabled = ok
|
|
||||||
|
|
||||||
sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward")
|
|
||||||
sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables")
|
|
||||||
sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
|
|
||||||
|
|
||||||
// Check if AppArmor is supported.
|
|
||||||
if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
|
|
||||||
sysInfo.AppArmor = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Seccomp is supported, via CONFIG_SECCOMP.
|
|
||||||
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_SECCOMP, 0, 0); err != syscall.EINVAL {
|
|
||||||
// Make sure the kernel has CONFIG_SECCOMP_FILTER.
|
|
||||||
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECCOMP, SeccompModeFilter, 0); err != syscall.EINVAL {
|
|
||||||
sysInfo.Seccomp = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sysInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkCgroupMem reads the memory information from the memory cgroup mount point.
|
|
||||||
func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo {
|
|
||||||
mountPoint, ok := cgMounts["memory"]
|
|
||||||
if !ok {
|
|
||||||
if !quiet {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup memory limit")
|
|
||||||
}
|
|
||||||
return cgroupMemInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes")
|
|
||||||
if !quiet && !swapLimit {
|
|
||||||
logrus.Warn("Your kernel does not support swap memory limit")
|
|
||||||
}
|
|
||||||
memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes")
|
|
||||||
if !quiet && !memoryReservation {
|
|
||||||
logrus.Warn("Your kernel does not support memory reservation")
|
|
||||||
}
|
|
||||||
oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control")
|
|
||||||
if !quiet && !oomKillDisable {
|
|
||||||
logrus.Warn("Your kernel does not support oom control")
|
|
||||||
}
|
|
||||||
memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness")
|
|
||||||
if !quiet && !memorySwappiness {
|
|
||||||
logrus.Warn("Your kernel does not support memory swappiness")
|
|
||||||
}
|
|
||||||
kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes")
|
|
||||||
if !quiet && !kernelMemory {
|
|
||||||
logrus.Warn("Your kernel does not support kernel memory limit")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cgroupMemInfo{
|
|
||||||
MemoryLimit: true,
|
|
||||||
SwapLimit: swapLimit,
|
|
||||||
MemoryReservation: memoryReservation,
|
|
||||||
OomKillDisable: oomKillDisable,
|
|
||||||
MemorySwappiness: memorySwappiness,
|
|
||||||
KernelMemory: kernelMemory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkCgroupCPU reads the cpu information from the cpu cgroup mount point.
|
|
||||||
func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo {
|
|
||||||
mountPoint, ok := cgMounts["cpu"]
|
|
||||||
if !ok {
|
|
||||||
if !quiet {
|
|
||||||
logrus.Warn("Unable to find cpu cgroup in mounts")
|
|
||||||
}
|
|
||||||
return cgroupCPUInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuShares := cgroupEnabled(mountPoint, "cpu.shares")
|
|
||||||
if !quiet && !cpuShares {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup cpu shares")
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us")
|
|
||||||
if !quiet && !cpuCfsPeriod {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup cfs period")
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us")
|
|
||||||
if !quiet && !cpuCfsQuota {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup cfs quotas")
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us")
|
|
||||||
if !quiet && !cpuRealtimePeriod {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup rt period")
|
|
||||||
}
|
|
||||||
|
|
||||||
cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us")
|
|
||||||
if !quiet && !cpuRealtimeRuntime {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup rt runtime")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cgroupCPUInfo{
|
|
||||||
CPUShares: cpuShares,
|
|
||||||
CPUCfsPeriod: cpuCfsPeriod,
|
|
||||||
CPUCfsQuota: cpuCfsQuota,
|
|
||||||
CPURealtimePeriod: cpuRealtimePeriod,
|
|
||||||
CPURealtimeRuntime: cpuRealtimeRuntime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point.
|
|
||||||
func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo {
|
|
||||||
mountPoint, ok := cgMounts["blkio"]
|
|
||||||
if !ok {
|
|
||||||
if !quiet {
|
|
||||||
logrus.Warn("Unable to find blkio cgroup in mounts")
|
|
||||||
}
|
|
||||||
return cgroupBlkioInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
weight := cgroupEnabled(mountPoint, "blkio.weight")
|
|
||||||
if !quiet && !weight {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup blkio weight")
|
|
||||||
}
|
|
||||||
|
|
||||||
weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device")
|
|
||||||
if !quiet && !weightDevice {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup blkio weight_device")
|
|
||||||
}
|
|
||||||
|
|
||||||
readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
|
|
||||||
if !quiet && !readBpsDevice {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device")
|
|
||||||
}
|
|
||||||
|
|
||||||
writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
|
|
||||||
if !quiet && !writeBpsDevice {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device")
|
|
||||||
}
|
|
||||||
readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device")
|
|
||||||
if !quiet && !readIOpsDevice {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device")
|
|
||||||
}
|
|
||||||
|
|
||||||
writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device")
|
|
||||||
if !quiet && !writeIOpsDevice {
|
|
||||||
logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device")
|
|
||||||
}
|
|
||||||
return cgroupBlkioInfo{
|
|
||||||
BlkioWeight: weight,
|
|
||||||
BlkioWeightDevice: weightDevice,
|
|
||||||
BlkioReadBpsDevice: readBpsDevice,
|
|
||||||
BlkioWriteBpsDevice: writeBpsDevice,
|
|
||||||
BlkioReadIOpsDevice: readIOpsDevice,
|
|
||||||
BlkioWriteIOpsDevice: writeIOpsDevice,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point.
|
|
||||||
func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo {
|
|
||||||
mountPoint, ok := cgMounts["cpuset"]
|
|
||||||
if !ok {
|
|
||||||
if !quiet {
|
|
||||||
logrus.Warn("Unable to find cpuset cgroup in mounts")
|
|
||||||
}
|
|
||||||
return cgroupCpusetInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus"))
|
|
||||||
if err != nil {
|
|
||||||
return cgroupCpusetInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems"))
|
|
||||||
if err != nil {
|
|
||||||
return cgroupCpusetInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cgroupCpusetInfo{
|
|
||||||
Cpuset: true,
|
|
||||||
Cpus: strings.TrimSpace(string(cpus)),
|
|
||||||
Mems: strings.TrimSpace(string(mems)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkCgroupPids reads the pids information from the pids cgroup mount point.
|
|
||||||
func checkCgroupPids(quiet bool) cgroupPids {
|
|
||||||
_, err := cgroups.FindCgroupMountpoint("pids")
|
|
||||||
if err != nil {
|
|
||||||
if !quiet {
|
|
||||||
logrus.Warn(err)
|
|
||||||
}
|
|
||||||
return cgroupPids{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cgroupPids{
|
|
||||||
PidsLimit: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cgroupEnabled(mountPoint, name string) bool {
|
|
||||||
_, err := os.Stat(path.Join(mountPoint, name))
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readProcBool(path string) bool {
|
|
||||||
val, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(string(val)) == "1"
|
|
||||||
}
|
|
|
@ -1,121 +0,0 @@
|
||||||
// +build solaris,cgo
|
|
||||||
|
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -llgrp
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/lgrp_user.h>
|
|
||||||
int getLgrpCount() {
|
|
||||||
lgrp_cookie_t lgrpcookie = LGRP_COOKIE_NONE;
|
|
||||||
uint_t nlgrps;
|
|
||||||
|
|
||||||
if ((lgrpcookie = lgrp_init(LGRP_VIEW_OS)) == LGRP_COOKIE_NONE) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
nlgrps = lgrp_nlgrps(lgrpcookie);
|
|
||||||
return nlgrps;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// IsCPUSharesAvailable returns whether CPUShares setting is supported.
|
|
||||||
// We need FSS to be set as default scheduling class to support CPU Shares
|
|
||||||
func IsCPUSharesAvailable() bool {
|
|
||||||
cmd := exec.Command("/usr/sbin/dispadmin", "-d")
|
|
||||||
outBuf := new(bytes.Buffer)
|
|
||||||
errBuf := new(bytes.Buffer)
|
|
||||||
cmd.Stderr = errBuf
|
|
||||||
cmd.Stdout = outBuf
|
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return (strings.Contains(outBuf.String(), "FSS"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new SysInfo, using the filesystem to detect which features
|
|
||||||
// the kernel supports.
|
|
||||||
//NOTE Solaris: If we change the below capabilities be sure
|
|
||||||
// to update verifyPlatformContainerSettings() in daemon_solaris.go
|
|
||||||
func New(quiet bool) *SysInfo {
|
|
||||||
sysInfo := &SysInfo{}
|
|
||||||
sysInfo.cgroupMemInfo = setCgroupMem(quiet)
|
|
||||||
sysInfo.cgroupCPUInfo = setCgroupCPU(quiet)
|
|
||||||
sysInfo.cgroupBlkioInfo = setCgroupBlkioInfo(quiet)
|
|
||||||
sysInfo.cgroupCpusetInfo = setCgroupCPUsetInfo(quiet)
|
|
||||||
|
|
||||||
sysInfo.IPv4ForwardingDisabled = false
|
|
||||||
|
|
||||||
sysInfo.AppArmor = false
|
|
||||||
|
|
||||||
return sysInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// setCgroupMem reads the memory information for Solaris.
|
|
||||||
func setCgroupMem(quiet bool) cgroupMemInfo {
|
|
||||||
|
|
||||||
return cgroupMemInfo{
|
|
||||||
MemoryLimit: true,
|
|
||||||
SwapLimit: true,
|
|
||||||
MemoryReservation: false,
|
|
||||||
OomKillDisable: false,
|
|
||||||
MemorySwappiness: false,
|
|
||||||
KernelMemory: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setCgroupCPU reads the cpu information for Solaris.
|
|
||||||
func setCgroupCPU(quiet bool) cgroupCPUInfo {
|
|
||||||
|
|
||||||
return cgroupCPUInfo{
|
|
||||||
CPUShares: true,
|
|
||||||
CPUCfsPeriod: false,
|
|
||||||
CPUCfsQuota: true,
|
|
||||||
CPURealtimePeriod: false,
|
|
||||||
CPURealtimeRuntime: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// blkio switches are not supported in Solaris.
|
|
||||||
func setCgroupBlkioInfo(quiet bool) cgroupBlkioInfo {
|
|
||||||
|
|
||||||
return cgroupBlkioInfo{
|
|
||||||
BlkioWeight: false,
|
|
||||||
BlkioWeightDevice: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// setCgroupCPUsetInfo reads the cpuset information for Solaris.
|
|
||||||
func setCgroupCPUsetInfo(quiet bool) cgroupCpusetInfo {
|
|
||||||
|
|
||||||
return cgroupCpusetInfo{
|
|
||||||
Cpuset: true,
|
|
||||||
Cpus: getCPUCount(),
|
|
||||||
Mems: getLgrpCount(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCPUCount() string {
|
|
||||||
ncpus := C.sysconf(C._SC_NPROCESSORS_ONLN)
|
|
||||||
if ncpus <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strconv.FormatInt(int64(ncpus), 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLgrpCount() string {
|
|
||||||
nlgrps := C.getLgrpCount()
|
|
||||||
if nlgrps <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strconv.FormatInt(int64(nlgrps), 16)
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// +build !linux,!solaris,!windows
|
|
||||||
|
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
// New returns an empty SysInfo for non linux nor solaris for now.
|
|
||||||
func New(quiet bool) *SysInfo {
|
|
||||||
sysInfo := &SysInfo{}
|
|
||||||
return sysInfo
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package sysinfo
|
|
||||||
|
|
||||||
// New returns an empty SysInfo for windows for now.
|
|
||||||
func New(quiet bool) *SysInfo {
|
|
||||||
sysInfo := &SysInfo{}
|
|
||||||
return sysInfo
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
|
||||||
"github.com/docker/docker/volume"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerDecoder implements httputils.ContainerDecoder
|
|
||||||
// calling DecodeContainerConfig.
|
|
||||||
type ContainerDecoder struct{}
|
|
||||||
|
|
||||||
// DecodeConfig makes ContainerDecoder to implement httputils.ContainerDecoder
|
|
||||||
func (r ContainerDecoder) DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
|
|
||||||
return DecodeContainerConfig(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeHostConfig makes ContainerDecoder to implement httputils.ContainerDecoder
|
|
||||||
func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
|
|
||||||
return DecodeHostConfig(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
|
|
||||||
// struct and returns both a Config and a HostConfig struct
|
|
||||||
// Be aware this function is not checking whether the resulted structs are nil,
|
|
||||||
// it's your business to do so
|
|
||||||
func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
|
|
||||||
var w ContainerConfigWrapper
|
|
||||||
|
|
||||||
decoder := json.NewDecoder(src)
|
|
||||||
if err := decoder.Decode(&w); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hc := w.getHostConfig()
|
|
||||||
|
|
||||||
// Perform platform-specific processing of Volumes and Binds.
|
|
||||||
if w.Config != nil && hc != nil {
|
|
||||||
|
|
||||||
// Initialize the volumes map if currently nil
|
|
||||||
if w.Config.Volumes == nil {
|
|
||||||
w.Config.Volumes = make(map[string]struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now validate all the volumes and binds
|
|
||||||
if err := validateMountSettings(w.Config, hc); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certain parameters need daemon-side validation that cannot be done
|
|
||||||
// on the client, as only the daemon knows what is valid for the platform.
|
|
||||||
if err := validateNetMode(w.Config, hc); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate isolation
|
|
||||||
if err := validateIsolation(hc); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate QoS
|
|
||||||
if err := validateQoS(hc); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Resources
|
|
||||||
if err := validateResources(hc, sysinfo.New(true)); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate Privileged
|
|
||||||
if err := validatePrivileged(hc); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate ReadonlyRootfs
|
|
||||||
if err := validateReadonlyRootfs(hc); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.Config, hc, w.NetworkingConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateMountSettings validates each of the volumes and bind settings
|
|
||||||
// passed by the caller to ensure they are valid.
|
|
||||||
func validateMountSettings(c *container.Config, hc *container.HostConfig) error {
|
|
||||||
// it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 )
|
|
||||||
|
|
||||||
// Ensure all volumes and binds are valid.
|
|
||||||
for spec := range c.Volumes {
|
|
||||||
if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
|
|
||||||
return fmt.Errorf("invalid volume spec %q: %v", spec, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, spec := range hc.Binds {
|
|
||||||
if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
|
|
||||||
return fmt.Errorf("invalid bind mount spec %q: %v", spec, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerConfigWrapper is a Config wrapper that holds the container Config (portable)
|
|
||||||
// and the corresponding HostConfig (non-portable).
|
|
||||||
type ContainerConfigWrapper struct {
|
|
||||||
*container.Config
|
|
||||||
InnerHostConfig *container.HostConfig `json:"HostConfig,omitempty"`
|
|
||||||
Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
|
|
||||||
NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
|
|
||||||
*container.HostConfig // Deprecated. Exported to read attributes from json that are not in the inner host config structure.
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHostConfig gets the HostConfig of the Config.
|
|
||||||
// It's mostly there to handle Deprecated fields of the ContainerConfigWrapper
|
|
||||||
func (w *ContainerConfigWrapper) getHostConfig() *container.HostConfig {
|
|
||||||
hc := w.HostConfig
|
|
||||||
|
|
||||||
if hc == nil && w.InnerHostConfig != nil {
|
|
||||||
hc = w.InnerHostConfig
|
|
||||||
} else if w.InnerHostConfig != nil {
|
|
||||||
if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 {
|
|
||||||
w.InnerHostConfig.Memory = hc.Memory
|
|
||||||
}
|
|
||||||
if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 {
|
|
||||||
w.InnerHostConfig.MemorySwap = hc.MemorySwap
|
|
||||||
}
|
|
||||||
if hc.CPUShares != 0 && w.InnerHostConfig.CPUShares == 0 {
|
|
||||||
w.InnerHostConfig.CPUShares = hc.CPUShares
|
|
||||||
}
|
|
||||||
if hc.CpusetCpus != "" && w.InnerHostConfig.CpusetCpus == "" {
|
|
||||||
w.InnerHostConfig.CpusetCpus = hc.CpusetCpus
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.VolumeDriver != "" && w.InnerHostConfig.VolumeDriver == "" {
|
|
||||||
w.InnerHostConfig.VolumeDriver = hc.VolumeDriver
|
|
||||||
}
|
|
||||||
|
|
||||||
hc = w.InnerHostConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc != nil {
|
|
||||||
if w.Cpuset != "" && hc.CpusetCpus == "" {
|
|
||||||
hc.CpusetCpus = w.Cpuset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure NetworkMode has an acceptable value. We do this to ensure
|
|
||||||
// backwards compatible API behavior.
|
|
||||||
SetDefaultNetModeIfBlank(hc)
|
|
||||||
|
|
||||||
return hc
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerConfigWrapper is a Config wrapper that holds the container Config (portable)
|
|
||||||
// and the corresponding HostConfig (non-portable).
|
|
||||||
type ContainerConfigWrapper struct {
|
|
||||||
*container.Config
|
|
||||||
HostConfig *container.HostConfig `json:"HostConfig,omitempty"`
|
|
||||||
NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHostConfig gets the HostConfig of the Config.
|
|
||||||
func (w *ContainerConfigWrapper) getHostConfig() *container.HostConfig {
|
|
||||||
return w.HostConfig
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrConflictContainerNetworkAndLinks conflict between --net=container and links
|
|
||||||
ErrConflictContainerNetworkAndLinks = fmt.Errorf("conflicting options: container type network can't be used with links. This would result in undefined behavior")
|
|
||||||
// ErrConflictSharedNetwork conflict between private and other networks
|
|
||||||
ErrConflictSharedNetwork = fmt.Errorf("container sharing network namespace with another container or host cannot be connected to any other network")
|
|
||||||
// ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network.
|
|
||||||
ErrConflictHostNetwork = fmt.Errorf("container cannot be disconnected from host network or connected to host network")
|
|
||||||
// ErrConflictNoNetwork conflict between private and other networks
|
|
||||||
ErrConflictNoNetwork = fmt.Errorf("container cannot be connected to multiple networks with one of the networks in private (none) mode")
|
|
||||||
// ErrConflictNetworkAndDNS conflict between --dns and the network mode
|
|
||||||
ErrConflictNetworkAndDNS = fmt.Errorf("conflicting options: dns and the network mode")
|
|
||||||
// ErrConflictNetworkHostname conflict between the hostname and the network mode
|
|
||||||
ErrConflictNetworkHostname = fmt.Errorf("conflicting options: hostname and the network mode")
|
|
||||||
// ErrConflictHostNetworkAndLinks conflict between --net=host and links
|
|
||||||
ErrConflictHostNetworkAndLinks = fmt.Errorf("conflicting options: host type networking can't be used with links. This would result in undefined behavior")
|
|
||||||
// ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode
|
|
||||||
ErrConflictContainerNetworkAndMac = fmt.Errorf("conflicting options: mac-address and the network mode")
|
|
||||||
// ErrConflictNetworkHosts conflict between add-host and the network mode
|
|
||||||
ErrConflictNetworkHosts = fmt.Errorf("conflicting options: custom host-to-IP mapping and the network mode")
|
|
||||||
// ErrConflictNetworkPublishPorts conflict between the publish options and the network mode
|
|
||||||
ErrConflictNetworkPublishPorts = fmt.Errorf("conflicting options: port publishing and the container type network mode")
|
|
||||||
// ErrConflictNetworkExposePorts conflict between the expose option and the network mode
|
|
||||||
ErrConflictNetworkExposePorts = fmt.Errorf("conflicting options: port exposing and the container type network mode")
|
|
||||||
// ErrUnsupportedNetworkAndIP conflict between network mode and requested ip address
|
|
||||||
ErrUnsupportedNetworkAndIP = fmt.Errorf("user specified IP address is supported on user defined networks only")
|
|
||||||
// ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and requested ip address
|
|
||||||
ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("user specified IP address is supported only when connecting to networks with user configured subnets")
|
|
||||||
// ErrUnsupportedNetworkAndAlias conflict between network mode and alias
|
|
||||||
ErrUnsupportedNetworkAndAlias = fmt.Errorf("network-scoped alias is supported only for containers in user defined networks")
|
|
||||||
// ErrConflictUTSHostname conflict between the hostname and the UTS mode
|
|
||||||
ErrConflictUTSHostname = fmt.Errorf("conflicting options: hostname and the UTS mode")
|
|
||||||
)
|
|
|
@ -1,80 +0,0 @@
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecodeHostConfig creates a HostConfig based on the specified Reader.
|
|
||||||
// It assumes the content of the reader will be JSON, and decodes it.
|
|
||||||
func DecodeHostConfig(src io.Reader) (*container.HostConfig, error) {
|
|
||||||
decoder := json.NewDecoder(src)
|
|
||||||
|
|
||||||
var w ContainerConfigWrapper
|
|
||||||
if err := decoder.Decode(&w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hc := w.getHostConfig()
|
|
||||||
return hc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaultNetModeIfBlank changes the NetworkMode in a HostConfig structure
|
|
||||||
// to default if it is not populated. This ensures backwards compatibility after
|
|
||||||
// the validation of the network mode was moved from the docker CLI to the
|
|
||||||
// docker daemon.
|
|
||||||
func SetDefaultNetModeIfBlank(hc *container.HostConfig) {
|
|
||||||
if hc != nil {
|
|
||||||
if hc.NetworkMode == container.NetworkMode("") {
|
|
||||||
hc.NetworkMode = container.NetworkMode("default")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateNetContainerMode ensures that the various combinations of requested
|
|
||||||
// network settings wrt container mode are valid.
|
|
||||||
func validateNetContainerMode(c *container.Config, hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
parts := strings.Split(string(hc.NetworkMode), ":")
|
|
||||||
if parts[0] == "container" {
|
|
||||||
if len(parts) < 2 || parts[1] == "" {
|
|
||||||
return fmt.Errorf("Invalid network mode: invalid container format container:<name|id>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && c.Hostname != "" {
|
|
||||||
return ErrConflictNetworkHostname
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && len(hc.Links) > 0 {
|
|
||||||
return ErrConflictContainerNetworkAndLinks
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && len(hc.DNS) > 0 {
|
|
||||||
return ErrConflictNetworkAndDNS
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && len(hc.ExtraHosts) > 0 {
|
|
||||||
return ErrConflictNetworkHosts
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hc.NetworkMode.IsContainer() || hc.NetworkMode.IsHost()) && c.MacAddress != "" {
|
|
||||||
return ErrConflictContainerNetworkAndMac
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && (len(hc.PortBindings) > 0 || hc.PublishAllPorts == true) {
|
|
||||||
return ErrConflictNetworkPublishPorts
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && len(c.ExposedPorts) > 0 {
|
|
||||||
return ErrConflictNetworkExposePorts
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultDaemonNetworkMode returns the default network stack the daemon should
|
|
||||||
// use.
|
|
||||||
func DefaultDaemonNetworkMode() container.NetworkMode {
|
|
||||||
return container.NetworkMode("bridge")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPreDefinedNetwork indicates if a network is predefined by the daemon
|
|
||||||
func IsPreDefinedNetwork(network string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateNetMode ensures that the various combinations of requested
|
|
||||||
// network settings are valid.
|
|
||||||
func validateNetMode(c *container.Config, hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateIsolation performs platform specific validation of the
|
|
||||||
// isolation level in the hostconfig structure.
|
|
||||||
// This setting is currently discarded for Solaris so this is a no-op.
|
|
||||||
func validateIsolation(hc *container.HostConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateQoS performs platform specific validation of the QoS settings
|
|
||||||
func validateQoS(hc *container.HostConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateResources performs platform specific validation of the resource settings
|
|
||||||
func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validatePrivileged performs platform specific validation of the Privileged setting
|
|
||||||
func validatePrivileged(hc *container.HostConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
// +build !windows,!solaris
|
|
||||||
|
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultDaemonNetworkMode returns the default network stack the daemon should
|
|
||||||
// use.
|
|
||||||
func DefaultDaemonNetworkMode() container.NetworkMode {
|
|
||||||
return container.NetworkMode("bridge")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPreDefinedNetwork indicates if a network is predefined by the daemon
|
|
||||||
func IsPreDefinedNetwork(network string) bool {
|
|
||||||
n := container.NetworkMode(network)
|
|
||||||
return n.IsBridge() || n.IsHost() || n.IsNone() || n.IsDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateNetMode ensures that the various combinations of requested
|
|
||||||
// network settings are valid.
|
|
||||||
func validateNetMode(c *container.Config, hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := validateNetContainerMode(c, hc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.UTSMode.IsHost() && c.Hostname != "" {
|
|
||||||
return ErrConflictUTSHostname
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsHost() && len(hc.Links) > 0 {
|
|
||||||
return ErrConflictHostNetworkAndLinks
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateIsolation performs platform specific validation of
|
|
||||||
// isolation in the hostconfig structure. Linux only supports "default"
|
|
||||||
// which is LXC container isolation
|
|
||||||
func validateIsolation(hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !hc.Isolation.IsValid() {
|
|
||||||
return fmt.Errorf("Invalid isolation: %q - %s only supports 'default'", hc.Isolation, runtime.GOOS)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateQoS performs platform specific validation of the QoS settings
|
|
||||||
func validateQoS(hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.IOMaximumBandwidth != 0 {
|
|
||||||
return fmt.Errorf("Invalid QoS settings: %s does not support configuration of maximum bandwidth", runtime.GOOS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.IOMaximumIOps != 0 {
|
|
||||||
return fmt.Errorf("Invalid QoS settings: %s does not support configuration of maximum IOPs", runtime.GOOS)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateResources performs platform specific validation of the resource settings
|
|
||||||
// cpu-rt-runtime and cpu-rt-period can not be greater than their parent, cpu-rt-runtime requires sys_nice
|
|
||||||
func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.Resources.CPURealtimePeriod > 0 && !si.CPURealtimePeriod {
|
|
||||||
return fmt.Errorf("Your kernel does not support cgroup cpu real-time period")
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.Resources.CPURealtimeRuntime > 0 && !si.CPURealtimeRuntime {
|
|
||||||
return fmt.Errorf("Your kernel does not support cgroup cpu real-time runtime")
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.Resources.CPURealtimePeriod != 0 && hc.Resources.CPURealtimeRuntime != 0 && hc.Resources.CPURealtimeRuntime > hc.Resources.CPURealtimePeriod {
|
|
||||||
return fmt.Errorf("cpu real-time runtime cannot be higher than cpu real-time period")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validatePrivileged performs platform specific validation of the Privileged setting
|
|
||||||
func validatePrivileged(hc *container.HostConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateReadonlyRootfs performs platform specific validation of the ReadonlyRootfs setting
|
|
||||||
func validateReadonlyRootfs(hc *container.HostConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
package runconfig
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/pkg/sysinfo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultDaemonNetworkMode returns the default network stack the daemon should
|
|
||||||
// use.
|
|
||||||
func DefaultDaemonNetworkMode() container.NetworkMode {
|
|
||||||
return container.NetworkMode("nat")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPreDefinedNetwork indicates if a network is predefined by the daemon
|
|
||||||
func IsPreDefinedNetwork(network string) bool {
|
|
||||||
return !container.NetworkMode(network).IsUserDefined()
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateNetMode ensures that the various combinations of requested
|
|
||||||
// network settings are valid.
|
|
||||||
func validateNetMode(c *container.Config, hc *container.HostConfig) error {
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := validateNetContainerMode(c, hc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.NetworkMode.IsContainer() && hc.Isolation.IsHyperV() {
|
|
||||||
return fmt.Errorf("Using the network stack of another container is not supported while using Hyper-V Containers")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateIsolation performs platform specific validation of the
|
|
||||||
// isolation in the hostconfig structure. Windows supports 'default' (or
|
|
||||||
// blank), 'process', or 'hyperv'.
|
|
||||||
func validateIsolation(hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !hc.Isolation.IsValid() {
|
|
||||||
return fmt.Errorf("Invalid isolation: %q. Windows supports 'default', 'process', or 'hyperv'", hc.Isolation)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateQoS performs platform specific validation of the Qos settings
|
|
||||||
func validateQoS(hc *container.HostConfig) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateResources performs platform specific validation of the resource settings
|
|
||||||
func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if hc.Resources.CPURealtimePeriod != 0 {
|
|
||||||
return fmt.Errorf("Windows does not support CPU real-time period")
|
|
||||||
}
|
|
||||||
if hc.Resources.CPURealtimeRuntime != 0 {
|
|
||||||
return fmt.Errorf("Windows does not support CPU real-time runtime")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validatePrivileged performs platform specific validation of the Privileged setting
|
|
||||||
func validatePrivileged(hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if hc.Privileged {
|
|
||||||
return fmt.Errorf("Windows does not support privileged mode")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateReadonlyRootfs performs platform specific validation of the ReadonlyRootfs setting
|
|
||||||
func validateReadonlyRootfs(hc *container.HostConfig) error {
|
|
||||||
// We may not be passed a host config, such as in the case of docker commit
|
|
||||||
if hc == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if hc.ReadonlyRootfs {
|
|
||||||
return fmt.Errorf("Windows does not support root filesystem in read-only mode")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/mount"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errBindNotExist = errors.New("bind source path does not exist")
|
|
||||||
|
|
||||||
type validateOpts struct {
|
|
||||||
skipBindSourceCheck bool
|
|
||||||
skipAbsolutePathCheck bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error {
|
|
||||||
opts := validateOpts{}
|
|
||||||
for _, o := range options {
|
|
||||||
o(&opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mnt.Target) == 0 {
|
|
||||||
return &errMountConfig{mnt, errMissingField("Target")}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateNotRoot(mnt.Target); err != nil {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.skipAbsolutePathCheck {
|
|
||||||
if err := validateAbsolute(mnt.Target); err != nil {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch mnt.Type {
|
|
||||||
case mount.TypeBind:
|
|
||||||
if len(mnt.Source) == 0 {
|
|
||||||
return &errMountConfig{mnt, errMissingField("Source")}
|
|
||||||
}
|
|
||||||
// Don't error out just because the propagation mode is not supported on the platform
|
|
||||||
if opts := mnt.BindOptions; opts != nil {
|
|
||||||
if len(opts.Propagation) > 0 && len(propagationModes) > 0 {
|
|
||||||
if _, ok := propagationModes[opts.Propagation]; !ok {
|
|
||||||
return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mnt.VolumeOptions != nil {
|
|
||||||
return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateAbsolute(mnt.Source); err != nil {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not allow binding to non-existent path
|
|
||||||
if !opts.skipBindSourceCheck {
|
|
||||||
fi, err := os.Stat(mnt.Source)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
return &errMountConfig{mnt, errBindNotExist}
|
|
||||||
}
|
|
||||||
if err := validateStat(fi); err != nil {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case mount.TypeVolume:
|
|
||||||
if mnt.BindOptions != nil {
|
|
||||||
return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mnt.Source) == 0 && mnt.ReadOnly {
|
|
||||||
return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mnt.Source) != 0 {
|
|
||||||
if valid, err := IsVolumeNameValid(mnt.Source); !valid {
|
|
||||||
if err == nil {
|
|
||||||
err = errors.New("invalid volume name")
|
|
||||||
}
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case mount.TypeTmpfs:
|
|
||||||
if len(mnt.Source) != 0 {
|
|
||||||
return &errMountConfig{mnt, errExtraField("Source")}
|
|
||||||
}
|
|
||||||
if err := ValidateTmpfsMountDestination(mnt.Target); err != nil {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
|
||||||
return &errMountConfig{mnt, err}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return &errMountConfig{mnt, errors.New("mount type unknown")}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type errMountConfig struct {
|
|
||||||
mount *mount.Mount
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *errMountConfig) Error() string {
|
|
||||||
return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
func errExtraField(name string) error {
|
|
||||||
return fmt.Errorf("field %s must not be specified", name)
|
|
||||||
}
|
|
||||||
func errMissingField(name string) error {
|
|
||||||
return fmt.Errorf("field %s must not be empty", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateAbsolute(p string) error {
|
|
||||||
p = convertSlash(p)
|
|
||||||
if filepath.IsAbs(p) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateTmpfsMountDestination validates the destination of tmpfs mount.
|
|
||||||
// Currently, we have only two obvious rule for validation:
|
|
||||||
// - path must not be "/"
|
|
||||||
// - path must be absolute
|
|
||||||
// We should add more rules carefully (#30166)
|
|
||||||
func ValidateTmpfsMountDestination(dest string) error {
|
|
||||||
if err := validateNotRoot(dest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return validateAbsolute(dest)
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
var (
|
|
||||||
testDestinationPath = "/foo"
|
|
||||||
testSourcePath = "/foo"
|
|
||||||
)
|
|
|
@ -1,6 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
var (
|
|
||||||
testDestinationPath = `c:\foo`
|
|
||||||
testSourcePath = `c:\foo`
|
|
||||||
)
|
|
|
@ -1,374 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
"github.com/docker/docker/pkg/idtools"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultDriverName is the driver name used for the driver
|
|
||||||
// implemented in the local package.
|
|
||||||
const DefaultDriverName = "local"
|
|
||||||
|
|
||||||
// Scopes define if a volume has is cluster-wide (global) or local only.
|
|
||||||
// Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
|
|
||||||
const (
|
|
||||||
LocalScope = "local"
|
|
||||||
GlobalScope = "global"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Driver is for creating and removing volumes.
|
|
||||||
type Driver interface {
|
|
||||||
// Name returns the name of the volume driver.
|
|
||||||
Name() string
|
|
||||||
// Create makes a new volume with the given name.
|
|
||||||
Create(name string, opts map[string]string) (Volume, error)
|
|
||||||
// Remove deletes the volume.
|
|
||||||
Remove(vol Volume) (err error)
|
|
||||||
// List lists all the volumes the driver has
|
|
||||||
List() ([]Volume, error)
|
|
||||||
// Get retrieves the volume with the requested name
|
|
||||||
Get(name string) (Volume, error)
|
|
||||||
// Scope returns the scope of the driver (e.g. `global` or `local`).
|
|
||||||
// Scope determines how the driver is handled at a cluster level
|
|
||||||
Scope() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capability defines a set of capabilities that a driver is able to handle.
|
|
||||||
type Capability struct {
|
|
||||||
// Scope is the scope of the driver, `global` or `local`
|
|
||||||
// A `global` scope indicates that the driver manages volumes across the cluster
|
|
||||||
// A `local` scope indicates that the driver only manages volumes resources local to the host
|
|
||||||
// Scope is declared by the driver
|
|
||||||
Scope string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume is a place to store data. It is backed by a specific driver, and can be mounted.
|
|
||||||
type Volume interface {
|
|
||||||
// Name returns the name of the volume
|
|
||||||
Name() string
|
|
||||||
// DriverName returns the name of the driver which owns this volume.
|
|
||||||
DriverName() string
|
|
||||||
// Path returns the absolute path to the volume.
|
|
||||||
Path() string
|
|
||||||
// Mount mounts the volume and returns the absolute path to
|
|
||||||
// where it can be consumed.
|
|
||||||
Mount(id string) (string, error)
|
|
||||||
// Unmount unmounts the volume when it is no longer in use.
|
|
||||||
Unmount(id string) error
|
|
||||||
// CreatedAt returns Volume Creation time
|
|
||||||
CreatedAt() (time.Time, error)
|
|
||||||
// Status returns low-level status information about a volume
|
|
||||||
Status() map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`)
|
|
||||||
type DetailedVolume interface {
|
|
||||||
Labels() map[string]string
|
|
||||||
Options() map[string]string
|
|
||||||
Scope() string
|
|
||||||
Volume
|
|
||||||
}
|
|
||||||
|
|
||||||
// MountPoint is the intersection point between a volume and a container. It
|
|
||||||
// specifies which volume is to be used and where inside a container it should
|
|
||||||
// be mounted.
|
|
||||||
type MountPoint struct {
|
|
||||||
// Source is the source path of the mount.
|
|
||||||
// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
|
|
||||||
Source string
|
|
||||||
// Destination is the path relative to the container root (`/`) to the mount point
|
|
||||||
// It is where the `Source` is mounted to
|
|
||||||
Destination string
|
|
||||||
// RW is set to true when the mountpoint should be mounted as read-write
|
|
||||||
RW bool
|
|
||||||
// Name is the name reference to the underlying data defined by `Source`
|
|
||||||
// e.g., the volume name
|
|
||||||
Name string
|
|
||||||
// Driver is the volume driver used to create the volume (if it is a volume)
|
|
||||||
Driver string
|
|
||||||
// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
|
|
||||||
Type mounttypes.Type `json:",omitempty"`
|
|
||||||
// Volume is the volume providing data to this mountpoint.
|
|
||||||
// This is nil unless `Type` is set to `TypeVolume`
|
|
||||||
Volume Volume `json:"-"`
|
|
||||||
|
|
||||||
// Mode is the comma separated list of options supplied by the user when creating
|
|
||||||
// the bind/volume mount.
|
|
||||||
// Note Mode is not used on Windows
|
|
||||||
Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
|
|
||||||
|
|
||||||
// Propagation describes how the mounts are propagated from the host into the
|
|
||||||
// mount point, and vice-versa.
|
|
||||||
// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
|
|
||||||
// Note Propagation is not used on Windows
|
|
||||||
Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
|
|
||||||
|
|
||||||
// Specifies if data should be copied from the container before the first mount
|
|
||||||
// Use a pointer here so we can tell if the user set this value explicitly
|
|
||||||
// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
|
|
||||||
CopyData bool `json:"-"`
|
|
||||||
// ID is the opaque ID used to pass to the volume driver.
|
|
||||||
// This should be set by calls to `Mount` and unset by calls to `Unmount`
|
|
||||||
ID string `json:",omitempty"`
|
|
||||||
|
|
||||||
// Sepc is a copy of the API request that created this mount.
|
|
||||||
Spec mounttypes.Mount
|
|
||||||
|
|
||||||
// Track usage of this mountpoint
|
|
||||||
// Specifically needed for containers which are running and calls to `docker cp`
|
|
||||||
// because both these actions require mounting the volumes.
|
|
||||||
active int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup frees resources used by the mountpoint
|
|
||||||
func (m *MountPoint) Cleanup() error {
|
|
||||||
if m.Volume == nil || m.ID == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Volume.Unmount(m.ID); err != nil {
|
|
||||||
return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
m.active--
|
|
||||||
if m.active == 0 {
|
|
||||||
m.ID = ""
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup sets up a mount point by either mounting the volume if it is
|
|
||||||
// configured, or creating the source directory if supplied.
|
|
||||||
// The, optional, checkFun parameter allows doing additional checking
|
|
||||||
// before creating the source directory on the host.
|
|
||||||
func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) {
|
|
||||||
defer func() {
|
|
||||||
if err != nil || !label.RelabelNeeded(m.Mode) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode))
|
|
||||||
if err == syscall.ENOTSUP {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
path = ""
|
|
||||||
err = errors.Wrapf(err, "error setting label on mount source '%s'", m.Source)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if m.Volume != nil {
|
|
||||||
id := m.ID
|
|
||||||
if id == "" {
|
|
||||||
id = stringid.GenerateNonCryptoID()
|
|
||||||
}
|
|
||||||
path, err := m.Volume.Mount(id)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ID = id
|
|
||||||
m.active++
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.Source) == 0 {
|
|
||||||
return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
||||||
}
|
|
||||||
|
|
||||||
// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
|
|
||||||
if m.Type == mounttypes.TypeBind {
|
|
||||||
// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
|
|
||||||
// the use case is to forbid creating the daemon socket as a directory if the daemon is in
|
|
||||||
// the process of shutting down.
|
|
||||||
if checkFun != nil {
|
|
||||||
if err := checkFun(m); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
|
|
||||||
// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
|
|
||||||
if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
|
|
||||||
if perr, ok := err.(*os.PathError); ok {
|
|
||||||
if perr.Err != syscall.ENOTDIR {
|
|
||||||
return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m.Source, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the path of a volume in a mount point.
|
|
||||||
func (m *MountPoint) Path() string {
|
|
||||||
if m.Volume != nil {
|
|
||||||
return m.Volume.Path()
|
|
||||||
}
|
|
||||||
return m.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseVolumesFrom ensures that the supplied volumes-from is valid.
|
|
||||||
func ParseVolumesFrom(spec string) (string, string, error) {
|
|
||||||
if len(spec) == 0 {
|
|
||||||
return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
|
||||||
}
|
|
||||||
|
|
||||||
specParts := strings.SplitN(spec, ":", 2)
|
|
||||||
id := specParts[0]
|
|
||||||
mode := "rw"
|
|
||||||
|
|
||||||
if len(specParts) == 2 {
|
|
||||||
mode = specParts[1]
|
|
||||||
if !ValidMountMode(mode) {
|
|
||||||
return "", "", errInvalidMode(mode)
|
|
||||||
}
|
|
||||||
// For now don't allow propagation properties while importing
|
|
||||||
// volumes from data container. These volumes will inherit
|
|
||||||
// the same propagation property as of the original volume
|
|
||||||
// in data container. This probably can be relaxed in future.
|
|
||||||
if HasPropagation(mode) {
|
|
||||||
return "", "", errInvalidMode(mode)
|
|
||||||
}
|
|
||||||
// Do not allow copy modes on volumes-from
|
|
||||||
if _, isSet := getCopyMode(mode); isSet {
|
|
||||||
return "", "", errInvalidMode(mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return id, mode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
|
|
||||||
// structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
|
|
||||||
// validate the spec and create a MountPoint
|
|
||||||
func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
||||||
arr, err := splitRawSpec(convertSlash(raw))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var spec mounttypes.Mount
|
|
||||||
var mode string
|
|
||||||
switch len(arr) {
|
|
||||||
case 1:
|
|
||||||
// Just a destination path in the container
|
|
||||||
spec.Target = arr[0]
|
|
||||||
case 2:
|
|
||||||
if ValidMountMode(arr[1]) {
|
|
||||||
// Destination + Mode is not a valid volume - volumes
|
|
||||||
// cannot include a mode. e.g. /foo:rw
|
|
||||||
return nil, errInvalidSpec(raw)
|
|
||||||
}
|
|
||||||
// Host Source Path or Name + Destination
|
|
||||||
spec.Source = arr[0]
|
|
||||||
spec.Target = arr[1]
|
|
||||||
case 3:
|
|
||||||
// HostSourcePath+DestinationPath+Mode
|
|
||||||
spec.Source = arr[0]
|
|
||||||
spec.Target = arr[1]
|
|
||||||
mode = arr[2]
|
|
||||||
default:
|
|
||||||
return nil, errInvalidSpec(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ValidMountMode(mode) {
|
|
||||||
return nil, errInvalidMode(mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.IsAbs(spec.Source) {
|
|
||||||
spec.Type = mounttypes.TypeBind
|
|
||||||
} else {
|
|
||||||
spec.Type = mounttypes.TypeVolume
|
|
||||||
}
|
|
||||||
|
|
||||||
spec.ReadOnly = !ReadWrite(mode)
|
|
||||||
|
|
||||||
// cannot assume that if a volume driver is passed in that we should set it
|
|
||||||
if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
|
|
||||||
spec.VolumeOptions = &mounttypes.VolumeOptions{
|
|
||||||
DriverConfig: &mounttypes.Driver{Name: volumeDriver},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if copyData, isSet := getCopyMode(mode); isSet {
|
|
||||||
if spec.VolumeOptions == nil {
|
|
||||||
spec.VolumeOptions = &mounttypes.VolumeOptions{}
|
|
||||||
}
|
|
||||||
spec.VolumeOptions.NoCopy = !copyData
|
|
||||||
}
|
|
||||||
if HasPropagation(mode) {
|
|
||||||
spec.BindOptions = &mounttypes.BindOptions{
|
|
||||||
Propagation: GetPropagation(mode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
|
|
||||||
if mp != nil {
|
|
||||||
mp.Mode = mode
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
|
||||||
}
|
|
||||||
return mp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
|
|
||||||
func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
|
|
||||||
if err := validateMountConfig(&cfg, options...); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mp := &MountPoint{
|
|
||||||
RW: !cfg.ReadOnly,
|
|
||||||
Destination: clean(convertSlash(cfg.Target)),
|
|
||||||
Type: cfg.Type,
|
|
||||||
Spec: cfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cfg.Type {
|
|
||||||
case mounttypes.TypeVolume:
|
|
||||||
if cfg.Source == "" {
|
|
||||||
mp.Name = stringid.GenerateNonCryptoID()
|
|
||||||
} else {
|
|
||||||
mp.Name = cfg.Source
|
|
||||||
}
|
|
||||||
mp.CopyData = DefaultCopyMode
|
|
||||||
|
|
||||||
if cfg.VolumeOptions != nil {
|
|
||||||
if cfg.VolumeOptions.DriverConfig != nil {
|
|
||||||
mp.Driver = cfg.VolumeOptions.DriverConfig.Name
|
|
||||||
}
|
|
||||||
if cfg.VolumeOptions.NoCopy {
|
|
||||||
mp.CopyData = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case mounttypes.TypeBind:
|
|
||||||
mp.Source = clean(convertSlash(cfg.Source))
|
|
||||||
if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
|
|
||||||
mp.Propagation = cfg.BindOptions.Propagation
|
|
||||||
} else {
|
|
||||||
// If user did not specify a propagation mode, get
|
|
||||||
// default propagation mode.
|
|
||||||
mp.Propagation = DefaultPropagationMode
|
|
||||||
}
|
|
||||||
case mounttypes.TypeTmpfs:
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
return mp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func errInvalidMode(mode string) error {
|
|
||||||
return fmt.Errorf("invalid mode: %v", mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func errInvalidSpec(spec string) error {
|
|
||||||
return fmt.Errorf("invalid volume specification: '%s'", spec)
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// {<copy mode>=isEnabled}
|
|
||||||
var copyModes = map[string]bool{
|
|
||||||
"nocopy": false,
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyModeExists(mode string) bool {
|
|
||||||
_, exists := copyModes[mode]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCopyMode gets the copy mode from the mode string for mounts
|
|
||||||
func getCopyMode(mode string) (bool, bool) {
|
|
||||||
for _, o := range strings.Split(mode, ",") {
|
|
||||||
if isEnabled, exists := copyModes[o]; exists {
|
|
||||||
return isEnabled, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DefaultCopyMode, false
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultCopyMode is the copy mode used by default for normal/named volumes
|
|
||||||
DefaultCopyMode = true
|
|
||||||
)
|
|
|
@ -1,6 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultCopyMode is the copy mode used by default for normal/named volumes
|
|
||||||
DefaultCopyMode = false
|
|
||||||
)
|
|
|
@ -1,56 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
|
|
||||||
// for mount(2).
|
|
||||||
func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
|
|
||||||
var rawOpts []string
|
|
||||||
if readOnly {
|
|
||||||
rawOpts = append(rawOpts, "ro")
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt != nil && opt.Mode != 0 {
|
|
||||||
rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt != nil && opt.SizeBytes != 0 {
|
|
||||||
// calculate suffix here, making this linux specific, but that is
|
|
||||||
// okay, since API is that way anyways.
|
|
||||||
|
|
||||||
// we do this by finding the suffix that divides evenly into the
|
|
||||||
// value, returning the value itself, with no suffix, if it fails.
|
|
||||||
//
|
|
||||||
// For the most part, we don't enforce any semantic to this values.
|
|
||||||
// The operating system will usually align this and enforce minimum
|
|
||||||
// and maximums.
|
|
||||||
var (
|
|
||||||
size = opt.SizeBytes
|
|
||||||
suffix string
|
|
||||||
)
|
|
||||||
for _, r := range []struct {
|
|
||||||
suffix string
|
|
||||||
divisor int64
|
|
||||||
}{
|
|
||||||
{"g", 1 << 30},
|
|
||||||
{"m", 1 << 20},
|
|
||||||
{"k", 1 << 10},
|
|
||||||
} {
|
|
||||||
if size%r.divisor == 0 {
|
|
||||||
size = size / r.divisor
|
|
||||||
suffix = r.suffix
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
|
|
||||||
}
|
|
||||||
return strings.Join(rawOpts, ","), nil
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultPropagationMode defines what propagation mode should be used by
|
|
||||||
// default if user has not specified one explicitly.
|
|
||||||
// propagation modes
|
|
||||||
const DefaultPropagationMode = mounttypes.PropagationRPrivate
|
|
||||||
|
|
||||||
var propagationModes = map[mounttypes.Propagation]bool{
|
|
||||||
mounttypes.PropagationPrivate: true,
|
|
||||||
mounttypes.PropagationRPrivate: true,
|
|
||||||
mounttypes.PropagationSlave: true,
|
|
||||||
mounttypes.PropagationRSlave: true,
|
|
||||||
mounttypes.PropagationShared: true,
|
|
||||||
mounttypes.PropagationRShared: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPropagation extracts and returns the mount propagation mode. If there
|
|
||||||
// are no specifications, then by default it is "private".
|
|
||||||
func GetPropagation(mode string) mounttypes.Propagation {
|
|
||||||
for _, o := range strings.Split(mode, ",") {
|
|
||||||
prop := mounttypes.Propagation(o)
|
|
||||||
if propagationModes[prop] {
|
|
||||||
return prop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DefaultPropagationMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPropagation checks if there is a valid propagation mode present in
|
|
||||||
// passed string. Returns true if a valid propagation mode specifier is
|
|
||||||
// present, false otherwise.
|
|
||||||
func HasPropagation(mode string) bool {
|
|
||||||
for _, o := range strings.Split(mode, ",") {
|
|
||||||
if propagationModes[mounttypes.Propagation(o)] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
import mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
|
|
||||||
// DefaultPropagationMode is used only in linux. In other cases it returns
|
|
||||||
// empty string.
|
|
||||||
const DefaultPropagationMode mounttypes.Propagation = ""
|
|
||||||
|
|
||||||
// propagation modes not supported on this platform.
|
|
||||||
var propagationModes = map[mounttypes.Propagation]bool{}
|
|
||||||
|
|
||||||
// GetPropagation is not supported. Return empty string.
|
|
||||||
func GetPropagation(mode string) mounttypes.Propagation {
|
|
||||||
return DefaultPropagationMode
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPropagation checks if there is a valid propagation mode present in
|
|
||||||
// passed string. Returns true if a valid propagation mode specifier is
|
|
||||||
// present, false otherwise.
|
|
||||||
func HasPropagation(mode string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
// +build linux freebsd darwin solaris
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
)
|
|
||||||
|
|
||||||
var platformRawValidationOpts = []func(o *validateOpts){
|
|
||||||
// need to make sure to not error out if the bind source does not exist on unix
|
|
||||||
// this is supported for historical reasons, the path will be automatically
|
|
||||||
// created later.
|
|
||||||
func(o *validateOpts) { o.skipBindSourceCheck = true },
|
|
||||||
}
|
|
||||||
|
|
||||||
// read-write modes
|
|
||||||
var rwModes = map[string]bool{
|
|
||||||
"rw": true,
|
|
||||||
"ro": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// label modes
|
|
||||||
var labelModes = map[string]bool{
|
|
||||||
"Z": true,
|
|
||||||
"z": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// consistency modes
|
|
||||||
var consistencyModes = map[mounttypes.Consistency]bool{
|
|
||||||
mounttypes.ConsistencyFull: true,
|
|
||||||
mounttypes.ConsistencyCached: true,
|
|
||||||
mounttypes.ConsistencyDelegated: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackwardsCompatible decides whether this mount point can be
|
|
||||||
// used in old versions of Docker or not.
|
|
||||||
// Only bind mounts and local volumes can be used in old versions of Docker.
|
|
||||||
func (m *MountPoint) BackwardsCompatible() bool {
|
|
||||||
return len(m.Source) > 0 || m.Driver == DefaultDriverName
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasResource checks whether the given absolute path for a container is in
|
|
||||||
// this mount point. If the relative path starts with `../` then the resource
|
|
||||||
// is outside of this mount point, but we can't simply check for this prefix
|
|
||||||
// because it misses `..` which is also outside of the mount, so check both.
|
|
||||||
func (m *MountPoint) HasResource(absolutePath string) bool {
|
|
||||||
relPath, err := filepath.Rel(m.Destination, absolutePath)
|
|
||||||
return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
|
||||||
func IsVolumeNameValid(name string) (bool, error) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidMountMode will make sure the mount mode is valid.
|
|
||||||
// returns if it's a valid mount mode or not.
|
|
||||||
func ValidMountMode(mode string) bool {
|
|
||||||
if mode == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
rwModeCount := 0
|
|
||||||
labelModeCount := 0
|
|
||||||
propagationModeCount := 0
|
|
||||||
copyModeCount := 0
|
|
||||||
consistencyModeCount := 0
|
|
||||||
|
|
||||||
for _, o := range strings.Split(mode, ",") {
|
|
||||||
switch {
|
|
||||||
case rwModes[o]:
|
|
||||||
rwModeCount++
|
|
||||||
case labelModes[o]:
|
|
||||||
labelModeCount++
|
|
||||||
case propagationModes[mounttypes.Propagation(o)]:
|
|
||||||
propagationModeCount++
|
|
||||||
case copyModeExists(o):
|
|
||||||
copyModeCount++
|
|
||||||
case consistencyModes[mounttypes.Consistency(o)]:
|
|
||||||
consistencyModeCount++
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only one string for each mode is allowed.
|
|
||||||
if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadWrite tells you if a mode string is a valid read-write mode or not.
|
|
||||||
// If there are no specifications w.r.t read write mode, then by default
|
|
||||||
// it returns true.
|
|
||||||
func ReadWrite(mode string) bool {
|
|
||||||
if !ValidMountMode(mode) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, o := range strings.Split(mode, ",") {
|
|
||||||
if o == "ro" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateNotRoot(p string) error {
|
|
||||||
p = filepath.Clean(convertSlash(p))
|
|
||||||
if p == "/" {
|
|
||||||
return fmt.Errorf("invalid specification: destination can't be '/'")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateCopyMode(mode bool) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertSlash(p string) string {
|
|
||||||
return filepath.ToSlash(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitRawSpec(raw string) ([]string, error) {
|
|
||||||
if strings.Count(raw, ":") > 2 {
|
|
||||||
return nil, errInvalidSpec(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
arr := strings.SplitN(raw, ":", 3)
|
|
||||||
if arr[0] == "" {
|
|
||||||
return nil, errInvalidSpec(raw)
|
|
||||||
}
|
|
||||||
return arr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clean(p string) string {
|
|
||||||
return filepath.Clean(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateStat(fi os.FileInfo) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
|
|
||||||
// for mount(2).
|
|
||||||
func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
|
|
||||||
return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
package volume
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// read-write modes
|
|
||||||
var rwModes = map[string]bool{
|
|
||||||
"rw": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// read-only modes
|
|
||||||
var roModes = map[string]bool{
|
|
||||||
"ro": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var platformRawValidationOpts = []func(*validateOpts){
|
|
||||||
// filepath.IsAbs is weird on Windows:
|
|
||||||
// `c:` is not considered an absolute path
|
|
||||||
// `c:\` is considered an absolute path
|
|
||||||
// In any case, the regex matching below ensures absolute paths
|
|
||||||
// TODO: consider this a bug with filepath.IsAbs (?)
|
|
||||||
func(o *validateOpts) { o.skipAbsolutePathCheck = true },
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Spec should be in the format [source:]destination[:mode]
|
|
||||||
//
|
|
||||||
// Examples: c:\foo bar:d:rw
|
|
||||||
// c:\foo:d:\bar
|
|
||||||
// myname:d:
|
|
||||||
// d:\
|
|
||||||
//
|
|
||||||
// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
|
|
||||||
// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
|
|
||||||
// test is https://regex-golang.appspot.com/assets/html/index.html
|
|
||||||
//
|
|
||||||
// Useful link for referencing named capturing groups:
|
|
||||||
// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
|
|
||||||
//
|
|
||||||
// There are three match groups: source, destination and mode.
|
|
||||||
//
|
|
||||||
|
|
||||||
// RXHostDir is the first option of a source
|
|
||||||
RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
|
|
||||||
// RXName is the second option of a source
|
|
||||||
RXName = `[^\\/:*?"<>|\r\n]+`
|
|
||||||
// RXReservedNames are reserved names not possible on Windows
|
|
||||||
RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
|
|
||||||
|
|
||||||
// RXSource is the combined possibilities for a source
|
|
||||||
RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
|
|
||||||
|
|
||||||
// Source. Can be either a host directory, a name, or omitted:
|
|
||||||
// HostDir:
|
|
||||||
// - Essentially using the folder solution from
|
|
||||||
// https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
|
|
||||||
// but adding case insensitivity.
|
|
||||||
// - Must be an absolute path such as c:\path
|
|
||||||
// - Can include spaces such as `c:\program files`
|
|
||||||
// - And then followed by a colon which is not in the capture group
|
|
||||||
// - And can be optional
|
|
||||||
// Name:
|
|
||||||
// - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
|
|
||||||
// - And then followed by a colon which is not in the capture group
|
|
||||||
// - And can be optional
|
|
||||||
|
|
||||||
// RXDestination is the regex expression for the mount destination
|
|
||||||
RXDestination = `(?P<destination>([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?))`
|
|
||||||
// Destination (aka container path):
|
|
||||||
// - Variation on hostdir but can be a drive followed by colon as well
|
|
||||||
// - If a path, must be absolute. Can include spaces
|
|
||||||
// - Drive cannot be c: (explicitly checked in code, not RegEx)
|
|
||||||
|
|
||||||
// RXMode is the regex expression for the mode of the mount
|
|
||||||
// Mode (optional):
|
|
||||||
// - Hopefully self explanatory in comparison to above regex's.
|
|
||||||
// - Colon is not in the capture group
|
|
||||||
RXMode = `(:(?P<mode>(?i)ro|rw))?`
|
|
||||||
)
|
|
||||||
|
|
||||||
// BackwardsCompatible decides whether this mount point can be
|
|
||||||
// used in old versions of Docker or not.
|
|
||||||
// Windows volumes are never backwards compatible.
|
|
||||||
func (m *MountPoint) BackwardsCompatible() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitRawSpec(raw string) ([]string, error) {
|
|
||||||
specExp := regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
|
|
||||||
match := specExp.FindStringSubmatch(strings.ToLower(raw))
|
|
||||||
|
|
||||||
// Must have something back
|
|
||||||
if len(match) == 0 {
|
|
||||||
return nil, errInvalidSpec(raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
var split []string
|
|
||||||
matchgroups := make(map[string]string)
|
|
||||||
// Pull out the sub expressions from the named capture groups
|
|
||||||
for i, name := range specExp.SubexpNames() {
|
|
||||||
matchgroups[name] = strings.ToLower(match[i])
|
|
||||||
}
|
|
||||||
if source, exists := matchgroups["source"]; exists {
|
|
||||||
if source != "" {
|
|
||||||
split = append(split, source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if destination, exists := matchgroups["destination"]; exists {
|
|
||||||
if destination != "" {
|
|
||||||
split = append(split, destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if mode, exists := matchgroups["mode"]; exists {
|
|
||||||
if mode != "" {
|
|
||||||
split = append(split, mode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fix #26329. If the destination appears to be a file, and the source is null,
|
|
||||||
// it may be because we've fallen through the possible naming regex and hit a
|
|
||||||
// situation where the user intention was to map a file into a container through
|
|
||||||
// a local volume, but this is not supported by the platform.
|
|
||||||
if matchgroups["source"] == "" && matchgroups["destination"] != "" {
|
|
||||||
validName, err := IsVolumeNameValid(matchgroups["destination"])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !validName {
|
|
||||||
if fi, err := os.Stat(matchgroups["destination"]); err == nil {
|
|
||||||
if !fi.IsDir() {
|
|
||||||
return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return split, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
|
|
||||||
func IsVolumeNameValid(name string) (bool, error) {
|
|
||||||
nameExp := regexp.MustCompile(`^` + RXName + `$`)
|
|
||||||
if !nameExp.MatchString(name) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
|
|
||||||
if nameExp.MatchString(name) {
|
|
||||||
return false, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidMountMode will make sure the mount mode is valid.
|
|
||||||
// returns if it's a valid mount mode or not.
|
|
||||||
func ValidMountMode(mode string) bool {
|
|
||||||
if mode == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadWrite tells you if a mode string is a valid read-write mode or not.
|
|
||||||
func ReadWrite(mode string) bool {
|
|
||||||
return rwModes[strings.ToLower(mode)] || mode == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateNotRoot(p string) error {
|
|
||||||
p = strings.ToLower(convertSlash(p))
|
|
||||||
if p == "c:" || p == `c:\` {
|
|
||||||
return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateCopyMode(mode bool) error {
|
|
||||||
if mode {
|
|
||||||
return fmt.Errorf("Windows does not support copying image path content")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertSlash(p string) string {
|
|
||||||
return filepath.FromSlash(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clean(p string) string {
|
|
||||||
if match, _ := regexp.MatchString("^[a-z]:$", p); match {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
return filepath.Clean(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateStat(fi os.FileInfo) error {
|
|
||||||
if !fi.IsDir() {
|
|
||||||
return fmt.Errorf("source path must be a directory")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package cgroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Manager interface {
|
|
||||||
// Applies cgroup configuration to the process with the specified pid
|
|
||||||
Apply(pid int) error
|
|
||||||
|
|
||||||
// Returns the PIDs inside the cgroup set
|
|
||||||
GetPids() ([]int, error)
|
|
||||||
|
|
||||||
// Returns the PIDs inside the cgroup set & all sub-cgroups
|
|
||||||
GetAllPids() ([]int, error)
|
|
||||||
|
|
||||||
// Returns statistics for the cgroup set
|
|
||||||
GetStats() (*Stats, error)
|
|
||||||
|
|
||||||
// Toggles the freezer cgroup according with specified state
|
|
||||||
Freeze(state configs.FreezerState) error
|
|
||||||
|
|
||||||
// Destroys the cgroup set
|
|
||||||
Destroy() error
|
|
||||||
|
|
||||||
// NewCgroupManager() and LoadCgroupManager() require following attributes:
|
|
||||||
// Paths map[string]string
|
|
||||||
// Cgroups *cgroups.Cgroup
|
|
||||||
// Paths maps cgroup subsystem to path at which it is mounted.
|
|
||||||
// Cgroups specifies specific cgroup settings for the various subsystems
|
|
||||||
|
|
||||||
// Returns cgroup paths to save in a state file and to be able to
|
|
||||||
// restore the object later.
|
|
||||||
GetPaths() map[string]string
|
|
||||||
|
|
||||||
// Sets the cgroup as configured.
|
|
||||||
Set(container *configs.Config) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotFoundError struct {
|
|
||||||
Subsystem string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NotFoundError) Error() string {
|
|
||||||
return fmt.Sprintf("mountpoint for %s not found", e.Subsystem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNotFoundError(sub string) error {
|
|
||||||
return &NotFoundError{
|
|
||||||
Subsystem: sub,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsNotFound(err error) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, ok := err.(*NotFoundError)
|
|
||||||
return ok
|
|
||||||
}
|
|
3
vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_unsupported.go
generated
vendored
3
vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_unsupported.go
generated
vendored
|
@ -1,3 +0,0 @@
|
||||||
// +build !linux
|
|
||||||
|
|
||||||
package cgroups
|
|
|
@ -1,106 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package cgroups
|
|
||||||
|
|
||||||
type ThrottlingData struct {
|
|
||||||
// Number of periods with throttling active
|
|
||||||
Periods uint64 `json:"periods,omitempty"`
|
|
||||||
// Number of periods when the container hit its throttling limit.
|
|
||||||
ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
|
|
||||||
// Aggregate time the container was throttled for in nanoseconds.
|
|
||||||
ThrottledTime uint64 `json:"throttled_time,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CpuUsage denotes the usage of a CPU.
|
|
||||||
// All CPU stats are aggregate since container inception.
|
|
||||||
type CpuUsage struct {
|
|
||||||
// Total CPU time consumed.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
TotalUsage uint64 `json:"total_usage,omitempty"`
|
|
||||||
// Total CPU time consumed per core.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
|
|
||||||
// Time spent by tasks of the cgroup in kernel mode.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
|
|
||||||
// Time spent by tasks of the cgroup in user mode.
|
|
||||||
// Units: nanoseconds.
|
|
||||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CpuStats struct {
|
|
||||||
CpuUsage CpuUsage `json:"cpu_usage,omitempty"`
|
|
||||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryData struct {
|
|
||||||
Usage uint64 `json:"usage,omitempty"`
|
|
||||||
MaxUsage uint64 `json:"max_usage,omitempty"`
|
|
||||||
Failcnt uint64 `json:"failcnt"`
|
|
||||||
Limit uint64 `json:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryStats struct {
|
|
||||||
// memory used for cache
|
|
||||||
Cache uint64 `json:"cache,omitempty"`
|
|
||||||
// usage of memory
|
|
||||||
Usage MemoryData `json:"usage,omitempty"`
|
|
||||||
// usage of memory + swap
|
|
||||||
SwapUsage MemoryData `json:"swap_usage,omitempty"`
|
|
||||||
// usage of kernel memory
|
|
||||||
KernelUsage MemoryData `json:"kernel_usage,omitempty"`
|
|
||||||
// usage of kernel TCP memory
|
|
||||||
KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"`
|
|
||||||
|
|
||||||
Stats map[string]uint64 `json:"stats,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PidsStats struct {
|
|
||||||
// number of pids in the cgroup
|
|
||||||
Current uint64 `json:"current,omitempty"`
|
|
||||||
// active pids hard limit
|
|
||||||
Limit uint64 `json:"limit,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BlkioStatEntry struct {
|
|
||||||
Major uint64 `json:"major,omitempty"`
|
|
||||||
Minor uint64 `json:"minor,omitempty"`
|
|
||||||
Op string `json:"op,omitempty"`
|
|
||||||
Value uint64 `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BlkioStats struct {
|
|
||||||
// number of bytes tranferred to and from the block device
|
|
||||||
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
|
|
||||||
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"`
|
|
||||||
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
|
|
||||||
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"`
|
|
||||||
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"`
|
|
||||||
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"`
|
|
||||||
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"`
|
|
||||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HugetlbStats struct {
|
|
||||||
// current res_counter usage for hugetlb
|
|
||||||
Usage uint64 `json:"usage,omitempty"`
|
|
||||||
// maximum usage ever recorded.
|
|
||||||
MaxUsage uint64 `json:"max_usage,omitempty"`
|
|
||||||
// number of times hugetlb usage allocation failure.
|
|
||||||
Failcnt uint64 `json:"failcnt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stats struct {
|
|
||||||
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
|
||||||
PidsStats PidsStats `json:"pids_stats,omitempty"`
|
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
|
||||||
// the map is in the format "size of hugepage: stats of the hugepage"
|
|
||||||
HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStats() *Stats {
|
|
||||||
memoryStats := MemoryStats{Stats: make(map[string]uint64)}
|
|
||||||
hugetlbStats := make(map[string]HugetlbStats)
|
|
||||||
return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats}
|
|
||||||
}
|
|
|
@ -1,436 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package cgroups
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cgroupNamePrefix = "name="
|
|
||||||
CgroupProcesses = "cgroup.procs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
|
|
||||||
func FindCgroupMountpoint(subsystem string) (string, error) {
|
|
||||||
// We are not using mount.GetMounts() because it's super-inefficient,
|
|
||||||
// parsing it directly sped up x10 times because of not using Sscanf.
|
|
||||||
// It was one of two major performance drawbacks in container start.
|
|
||||||
if !isSubsystemAvailable(subsystem) {
|
|
||||||
return "", NewNotFoundError(subsystem)
|
|
||||||
}
|
|
||||||
f, err := os.Open("/proc/self/mountinfo")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
txt := scanner.Text()
|
|
||||||
fields := strings.Split(txt, " ")
|
|
||||||
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
|
|
||||||
if opt == subsystem {
|
|
||||||
return fields[4], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", NewNotFoundError(subsystem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) {
|
|
||||||
if !isSubsystemAvailable(subsystem) {
|
|
||||||
return "", "", NewNotFoundError(subsystem)
|
|
||||||
}
|
|
||||||
f, err := os.Open("/proc/self/mountinfo")
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
txt := scanner.Text()
|
|
||||||
fields := strings.Split(txt, " ")
|
|
||||||
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
|
|
||||||
if opt == subsystem {
|
|
||||||
return fields[4], fields[3], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", "", NewNotFoundError(subsystem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSubsystemAvailable(subsystem string) bool {
|
|
||||||
cgroups, err := ParseCgroupFile("/proc/self/cgroup")
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, avail := cgroups[subsystem]
|
|
||||||
return avail
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindCgroupMountpointDir() (string, error) {
|
|
||||||
f, err := os.Open("/proc/self/mountinfo")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
text := scanner.Text()
|
|
||||||
fields := strings.Split(text, " ")
|
|
||||||
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
|
||||||
index := strings.Index(text, " - ")
|
|
||||||
postSeparatorFields := strings.Fields(text[index+3:])
|
|
||||||
numPostFields := len(postSeparatorFields)
|
|
||||||
|
|
||||||
// This is an error as we can't detect if the mount is for "cgroup"
|
|
||||||
if numPostFields == 0 {
|
|
||||||
return "", fmt.Errorf("Found no fields post '-' in %q", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if postSeparatorFields[0] == "cgroup" {
|
|
||||||
// Check that the mount is properly formated.
|
|
||||||
if numPostFields < 3 {
|
|
||||||
return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Dir(fields[4]), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", NewNotFoundError("cgroup")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mount struct {
|
|
||||||
Mountpoint string
|
|
||||||
Root string
|
|
||||||
Subsystems []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) {
|
|
||||||
if len(m.Subsystems) == 0 {
|
|
||||||
return "", fmt.Errorf("no subsystem for mount")
|
|
||||||
}
|
|
||||||
|
|
||||||
return getControllerPath(m.Subsystems[0], cgroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount, error) {
|
|
||||||
res := make([]Mount, 0, len(ss))
|
|
||||||
scanner := bufio.NewScanner(mi)
|
|
||||||
numFound := 0
|
|
||||||
for scanner.Scan() && numFound < len(ss) {
|
|
||||||
txt := scanner.Text()
|
|
||||||
sepIdx := strings.Index(txt, " - ")
|
|
||||||
if sepIdx == -1 {
|
|
||||||
return nil, fmt.Errorf("invalid mountinfo format")
|
|
||||||
}
|
|
||||||
if txt[sepIdx+3:sepIdx+10] == "cgroup2" || txt[sepIdx+3:sepIdx+9] != "cgroup" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Split(txt, " ")
|
|
||||||
m := Mount{
|
|
||||||
Mountpoint: fields[4],
|
|
||||||
Root: fields[3],
|
|
||||||
}
|
|
||||||
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
|
|
||||||
if !ss[opt] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(opt, cgroupNamePrefix) {
|
|
||||||
m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):])
|
|
||||||
} else {
|
|
||||||
m.Subsystems = append(m.Subsystems, opt)
|
|
||||||
}
|
|
||||||
if !all {
|
|
||||||
numFound++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res = append(res, m)
|
|
||||||
}
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgroupMounts returns the mounts for the cgroup subsystems.
|
|
||||||
// all indicates whether to return just the first instance or all the mounts.
|
|
||||||
func GetCgroupMounts(all bool) ([]Mount, error) {
|
|
||||||
f, err := os.Open("/proc/self/mountinfo")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
allSubsystems, err := ParseCgroupFile("/proc/self/cgroup")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
allMap := make(map[string]bool)
|
|
||||||
for s := range allSubsystems {
|
|
||||||
allMap[s] = true
|
|
||||||
}
|
|
||||||
return getCgroupMountsHelper(allMap, f, all)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllSubsystems returns all the cgroup subsystems supported by the kernel
|
|
||||||
func GetAllSubsystems() ([]string, error) {
|
|
||||||
f, err := os.Open("/proc/cgroups")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
subsystems := []string{}
|
|
||||||
|
|
||||||
s := bufio.NewScanner(f)
|
|
||||||
for s.Scan() {
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
text := s.Text()
|
|
||||||
if text[0] != '#' {
|
|
||||||
parts := strings.Fields(text)
|
|
||||||
if len(parts) >= 4 && parts[3] != "0" {
|
|
||||||
subsystems = append(subsystems, parts[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return subsystems, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetThisCgroupDir returns the relative path to the cgroup docker is running in.
|
|
||||||
func GetThisCgroupDir(subsystem string) (string, error) {
|
|
||||||
cgroups, err := ParseCgroupFile("/proc/self/cgroup")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getControllerPath(subsystem, cgroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInitCgroupDir(subsystem string) (string, error) {
|
|
||||||
|
|
||||||
cgroups, err := ParseCgroupFile("/proc/1/cgroup")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return getControllerPath(subsystem, cgroups)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readProcsFile(dir string) ([]int, error) {
|
|
||||||
f, err := os.Open(filepath.Join(dir, CgroupProcesses))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
var (
|
|
||||||
s = bufio.NewScanner(f)
|
|
||||||
out = []int{}
|
|
||||||
)
|
|
||||||
|
|
||||||
for s.Scan() {
|
|
||||||
if t := s.Text(); t != "" {
|
|
||||||
pid, err := strconv.Atoi(t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
out = append(out, pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseCgroupFile parses the given cgroup file, typically from
|
|
||||||
// /proc/<pid>/cgroup, into a map of subgroups to cgroup names.
|
|
||||||
func ParseCgroupFile(path string) (map[string]string, error) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return parseCgroupFromReader(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function for ParseCgroupFile to make testing easier
|
|
||||||
func parseCgroupFromReader(r io.Reader) (map[string]string, error) {
|
|
||||||
s := bufio.NewScanner(r)
|
|
||||||
cgroups := make(map[string]string)
|
|
||||||
|
|
||||||
for s.Scan() {
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
text := s.Text()
|
|
||||||
// from cgroups(7):
|
|
||||||
// /proc/[pid]/cgroup
|
|
||||||
// ...
|
|
||||||
// For each cgroup hierarchy ... there is one entry
|
|
||||||
// containing three colon-separated fields of the form:
|
|
||||||
// hierarchy-ID:subsystem-list:cgroup-path
|
|
||||||
parts := strings.SplitN(text, ":", 3)
|
|
||||||
if len(parts) < 3 {
|
|
||||||
return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, subs := range strings.Split(parts[1], ",") {
|
|
||||||
cgroups[subs] = parts[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cgroups, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {
|
|
||||||
|
|
||||||
if p, ok := cgroups[subsystem]; ok {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if p, ok := cgroups[cgroupNamePrefix+subsystem]; ok {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", NewNotFoundError(subsystem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PathExists(path string) bool {
|
|
||||||
if _, err := os.Stat(path); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnterPid(cgroupPaths map[string]string, pid int) error {
|
|
||||||
for _, path := range cgroupPaths {
|
|
||||||
if PathExists(path) {
|
|
||||||
if err := WriteCgroupProc(path, pid); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemovePaths iterates over the provided paths removing them.
|
|
||||||
// We trying to remove all paths five times with increasing delay between tries.
|
|
||||||
// If after all there are not removed cgroups - appropriate error will be
|
|
||||||
// returned.
|
|
||||||
func RemovePaths(paths map[string]string) (err error) {
|
|
||||||
delay := 10 * time.Millisecond
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
if i != 0 {
|
|
||||||
time.Sleep(delay)
|
|
||||||
delay *= 2
|
|
||||||
}
|
|
||||||
for s, p := range paths {
|
|
||||||
os.RemoveAll(p)
|
|
||||||
// TODO: here probably should be logging
|
|
||||||
_, err := os.Stat(p)
|
|
||||||
// We need this strange way of checking cgroups existence because
|
|
||||||
// RemoveAll almost always returns error, even on already removed
|
|
||||||
// cgroups
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
delete(paths, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(paths) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Failed to remove paths: %v", paths)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetHugePageSize() ([]string, error) {
|
|
||||||
var pageSizes []string
|
|
||||||
sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"}
|
|
||||||
files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
|
|
||||||
if err != nil {
|
|
||||||
return pageSizes, err
|
|
||||||
}
|
|
||||||
for _, st := range files {
|
|
||||||
nameArray := strings.Split(st.Name(), "-")
|
|
||||||
pageSize, err := units.RAMInBytes(nameArray[1])
|
|
||||||
if err != nil {
|
|
||||||
return []string{}, err
|
|
||||||
}
|
|
||||||
sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList)
|
|
||||||
pageSizes = append(pageSizes, sizeString)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pageSizes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPids returns all pids, that were added to cgroup at path.
|
|
||||||
func GetPids(path string) ([]int, error) {
|
|
||||||
return readProcsFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllPids returns all pids, that were added to cgroup at path and to all its
|
|
||||||
// subcgroups.
|
|
||||||
func GetAllPids(path string) ([]int, error) {
|
|
||||||
var pids []int
|
|
||||||
// collect pids from all sub-cgroups
|
|
||||||
err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
|
|
||||||
dir, file := filepath.Split(p)
|
|
||||||
if file != CgroupProcesses {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if iErr != nil {
|
|
||||||
return iErr
|
|
||||||
}
|
|
||||||
cPids, err := readProcsFile(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pids = append(pids, cPids...)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return pids, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file
|
|
||||||
func WriteCgroupProc(dir string, pid int) error {
|
|
||||||
// Normally dir should not be empty, one case is that cgroup subsystem
|
|
||||||
// is not mounted, we will get empty dir, and we want it fail here.
|
|
||||||
if dir == "" {
|
|
||||||
return fmt.Errorf("no such directory for %s", CgroupProcesses)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dont attach any pid to the cgroup if -1 is specified as a pid
|
|
||||||
if pid != -1 {
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(dir, CgroupProcesses), []byte(strconv.Itoa(pid)), 0700); err != nil {
|
|
||||||
return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// blockIODevice holds major:minor format supported in blkio cgroup
|
|
||||||
type blockIODevice struct {
|
|
||||||
// Major is the device's major number
|
|
||||||
Major int64 `json:"major"`
|
|
||||||
// Minor is the device's minor number
|
|
||||||
Minor int64 `json:"minor"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair
|
|
||||||
type WeightDevice struct {
|
|
||||||
blockIODevice
|
|
||||||
// Weight is the bandwidth rate for the device, range is from 10 to 1000
|
|
||||||
Weight uint16 `json:"weight"`
|
|
||||||
// LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only
|
|
||||||
LeafWeight uint16 `json:"leafWeight"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWeightDevice returns a configured WeightDevice pointer
|
|
||||||
func NewWeightDevice(major, minor int64, weight, leafWeight uint16) *WeightDevice {
|
|
||||||
wd := &WeightDevice{}
|
|
||||||
wd.Major = major
|
|
||||||
wd.Minor = minor
|
|
||||||
wd.Weight = weight
|
|
||||||
wd.LeafWeight = leafWeight
|
|
||||||
return wd
|
|
||||||
}
|
|
||||||
|
|
||||||
// WeightString formats the struct to be writable to the cgroup specific file
|
|
||||||
func (wd *WeightDevice) WeightString() string {
|
|
||||||
return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeafWeightString formats the struct to be writable to the cgroup specific file
|
|
||||||
func (wd *WeightDevice) LeafWeightString() string {
|
|
||||||
return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThrottleDevice struct holds a `major:minor rate_per_second` pair
|
|
||||||
type ThrottleDevice struct {
|
|
||||||
blockIODevice
|
|
||||||
// Rate is the IO rate limit per cgroup per device
|
|
||||||
Rate uint64 `json:"rate"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewThrottleDevice returns a configured ThrottleDevice pointer
|
|
||||||
func NewThrottleDevice(major, minor int64, rate uint64) *ThrottleDevice {
|
|
||||||
td := &ThrottleDevice{}
|
|
||||||
td.Major = major
|
|
||||||
td.Minor = minor
|
|
||||||
td.Rate = rate
|
|
||||||
return td
|
|
||||||
}
|
|
||||||
|
|
||||||
// String formats the struct to be writable to the cgroup specific file
|
|
||||||
func (td *ThrottleDevice) String() string {
|
|
||||||
return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate)
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
type FreezerState string
|
|
||||||
|
|
||||||
const (
|
|
||||||
Undefined FreezerState = ""
|
|
||||||
Frozen FreezerState = "FROZEN"
|
|
||||||
Thawed FreezerState = "THAWED"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cgroup struct {
|
|
||||||
// Deprecated, use Path instead
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
|
|
||||||
// name of parent of cgroup or slice
|
|
||||||
// Deprecated, use Path instead
|
|
||||||
Parent string `json:"parent,omitempty"`
|
|
||||||
|
|
||||||
// Path specifies the path to cgroups that are created and/or joined by the container.
|
|
||||||
// The path is assumed to be relative to the host system cgroup mountpoint.
|
|
||||||
Path string `json:"path"`
|
|
||||||
|
|
||||||
// ScopePrefix describes prefix for the scope name
|
|
||||||
ScopePrefix string `json:"scope_prefix"`
|
|
||||||
|
|
||||||
// Paths represent the absolute cgroups paths to join.
|
|
||||||
// This takes precedence over Path.
|
|
||||||
Paths map[string]string
|
|
||||||
|
|
||||||
// Resources contains various cgroups settings to apply
|
|
||||||
*Resources
|
|
||||||
}
|
|
||||||
|
|
||||||
type Resources struct {
|
|
||||||
// If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list.
|
|
||||||
// Deprecated
|
|
||||||
AllowAllDevices *bool `json:"allow_all_devices,omitempty"`
|
|
||||||
// Deprecated
|
|
||||||
AllowedDevices []*Device `json:"allowed_devices,omitempty"`
|
|
||||||
// Deprecated
|
|
||||||
DeniedDevices []*Device `json:"denied_devices,omitempty"`
|
|
||||||
|
|
||||||
Devices []*Device `json:"devices"`
|
|
||||||
|
|
||||||
// Memory limit (in bytes)
|
|
||||||
Memory int64 `json:"memory"`
|
|
||||||
|
|
||||||
// Memory reservation or soft_limit (in bytes)
|
|
||||||
MemoryReservation int64 `json:"memory_reservation"`
|
|
||||||
|
|
||||||
// Total memory usage (memory + swap); set `-1` to enable unlimited swap
|
|
||||||
MemorySwap int64 `json:"memory_swap"`
|
|
||||||
|
|
||||||
// Kernel memory limit (in bytes)
|
|
||||||
KernelMemory int64 `json:"kernel_memory"`
|
|
||||||
|
|
||||||
// Kernel memory limit for TCP use (in bytes)
|
|
||||||
KernelMemoryTCP int64 `json:"kernel_memory_tcp"`
|
|
||||||
|
|
||||||
// CPU shares (relative weight vs. other containers)
|
|
||||||
CpuShares int64 `json:"cpu_shares"`
|
|
||||||
|
|
||||||
// CPU hardcap limit (in usecs). Allowed cpu time in a given period.
|
|
||||||
CpuQuota int64 `json:"cpu_quota"`
|
|
||||||
|
|
||||||
// CPU period to be used for hardcapping (in usecs). 0 to use system default.
|
|
||||||
CpuPeriod int64 `json:"cpu_period"`
|
|
||||||
|
|
||||||
// How many time CPU will use in realtime scheduling (in usecs).
|
|
||||||
CpuRtRuntime int64 `json:"cpu_rt_quota"`
|
|
||||||
|
|
||||||
// CPU period to be used for realtime scheduling (in usecs).
|
|
||||||
CpuRtPeriod int64 `json:"cpu_rt_period"`
|
|
||||||
|
|
||||||
// CPU to use
|
|
||||||
CpusetCpus string `json:"cpuset_cpus"`
|
|
||||||
|
|
||||||
// MEM to use
|
|
||||||
CpusetMems string `json:"cpuset_mems"`
|
|
||||||
|
|
||||||
// Process limit; set <= `0' to disable limit.
|
|
||||||
PidsLimit int64 `json:"pids_limit"`
|
|
||||||
|
|
||||||
// Specifies per cgroup weight, range is from 10 to 1000.
|
|
||||||
BlkioWeight uint16 `json:"blkio_weight"`
|
|
||||||
|
|
||||||
// Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only
|
|
||||||
BlkioLeafWeight uint16 `json:"blkio_leaf_weight"`
|
|
||||||
|
|
||||||
// Weight per cgroup per device, can override BlkioWeight.
|
|
||||||
BlkioWeightDevice []*WeightDevice `json:"blkio_weight_device"`
|
|
||||||
|
|
||||||
// IO read rate limit per cgroup per device, bytes per second.
|
|
||||||
BlkioThrottleReadBpsDevice []*ThrottleDevice `json:"blkio_throttle_read_bps_device"`
|
|
||||||
|
|
||||||
// IO write rate limit per cgroup per device, bytes per second.
|
|
||||||
BlkioThrottleWriteBpsDevice []*ThrottleDevice `json:"blkio_throttle_write_bps_device"`
|
|
||||||
|
|
||||||
// IO read rate limit per cgroup per device, IO per second.
|
|
||||||
BlkioThrottleReadIOPSDevice []*ThrottleDevice `json:"blkio_throttle_read_iops_device"`
|
|
||||||
|
|
||||||
// IO write rate limit per cgroup per device, IO per second.
|
|
||||||
BlkioThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkio_throttle_write_iops_device"`
|
|
||||||
|
|
||||||
// set the freeze value for the process
|
|
||||||
Freezer FreezerState `json:"freezer"`
|
|
||||||
|
|
||||||
// Hugetlb limit (in bytes)
|
|
||||||
HugetlbLimit []*HugepageLimit `json:"hugetlb_limit"`
|
|
||||||
|
|
||||||
// Whether to disable OOM Killer
|
|
||||||
OomKillDisable bool `json:"oom_kill_disable"`
|
|
||||||
|
|
||||||
// Tuning swappiness behaviour per cgroup
|
|
||||||
MemorySwappiness *int64 `json:"memory_swappiness"`
|
|
||||||
|
|
||||||
// Set priority of network traffic for container
|
|
||||||
NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"`
|
|
||||||
|
|
||||||
// Set class identifier for container's network packets
|
|
||||||
NetClsClassid uint32 `json:"net_cls_classid_u"`
|
|
||||||
}
|
|
6
vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go
generated
vendored
6
vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go
generated
vendored
|
@ -1,6 +0,0 @@
|
||||||
// +build !windows,!linux,!freebsd
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
type Cgroup struct {
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
// TODO Windows: This can ultimately be entirely factored out on Windows as
|
|
||||||
// cgroups are a Unix-specific construct.
|
|
||||||
type Cgroup struct {
|
|
||||||
}
|
|
|
@ -1,332 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Rlimit struct {
|
|
||||||
Type int `json:"type"`
|
|
||||||
Hard uint64 `json:"hard"`
|
|
||||||
Soft uint64 `json:"soft"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDMap represents UID/GID Mappings for User Namespaces.
|
|
||||||
type IDMap struct {
|
|
||||||
ContainerID int `json:"container_id"`
|
|
||||||
HostID int `json:"host_id"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Seccomp represents syscall restrictions
|
|
||||||
// By default, only the native architecture of the kernel is allowed to be used
|
|
||||||
// for syscalls. Additional architectures can be added by specifying them in
|
|
||||||
// Architectures.
|
|
||||||
type Seccomp struct {
|
|
||||||
DefaultAction Action `json:"default_action"`
|
|
||||||
Architectures []string `json:"architectures"`
|
|
||||||
Syscalls []*Syscall `json:"syscalls"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Action is taken upon rule match in Seccomp
|
|
||||||
type Action int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Kill Action = iota + 1
|
|
||||||
Errno
|
|
||||||
Trap
|
|
||||||
Allow
|
|
||||||
Trace
|
|
||||||
)
|
|
||||||
|
|
||||||
// Operator is a comparison operator to be used when matching syscall arguments in Seccomp
|
|
||||||
type Operator int
|
|
||||||
|
|
||||||
const (
|
|
||||||
EqualTo Operator = iota + 1
|
|
||||||
NotEqualTo
|
|
||||||
GreaterThan
|
|
||||||
GreaterThanOrEqualTo
|
|
||||||
LessThan
|
|
||||||
LessThanOrEqualTo
|
|
||||||
MaskEqualTo
|
|
||||||
)
|
|
||||||
|
|
||||||
// Arg is a rule to match a specific syscall argument in Seccomp
|
|
||||||
type Arg struct {
|
|
||||||
Index uint `json:"index"`
|
|
||||||
Value uint64 `json:"value"`
|
|
||||||
ValueTwo uint64 `json:"value_two"`
|
|
||||||
Op Operator `json:"op"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Syscall is a rule to match a syscall in Seccomp
|
|
||||||
type Syscall struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Action Action `json:"action"`
|
|
||||||
Args []*Arg `json:"args"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Windows. Many of these fields should be factored out into those parts
|
|
||||||
// which are common across platforms, and those which are platform specific.
|
|
||||||
|
|
||||||
// Config defines configuration options for executing a process inside a contained environment.
|
|
||||||
type Config struct {
|
|
||||||
// NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs
|
|
||||||
// This is a common option when the container is running in ramdisk
|
|
||||||
NoPivotRoot bool `json:"no_pivot_root"`
|
|
||||||
|
|
||||||
// ParentDeathSignal specifies the signal that is sent to the container's process in the case
|
|
||||||
// that the parent process dies.
|
|
||||||
ParentDeathSignal int `json:"parent_death_signal"`
|
|
||||||
|
|
||||||
// Path to a directory containing the container's root filesystem.
|
|
||||||
Rootfs string `json:"rootfs"`
|
|
||||||
|
|
||||||
// Readonlyfs will remount the container's rootfs as readonly where only externally mounted
|
|
||||||
// bind mounts are writtable.
|
|
||||||
Readonlyfs bool `json:"readonlyfs"`
|
|
||||||
|
|
||||||
// Specifies the mount propagation flags to be applied to /.
|
|
||||||
RootPropagation int `json:"rootPropagation"`
|
|
||||||
|
|
||||||
// Mounts specify additional source and destination paths that will be mounted inside the container's
|
|
||||||
// rootfs and mount namespace if specified
|
|
||||||
Mounts []*Mount `json:"mounts"`
|
|
||||||
|
|
||||||
// The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well!
|
|
||||||
Devices []*Device `json:"devices"`
|
|
||||||
|
|
||||||
MountLabel string `json:"mount_label"`
|
|
||||||
|
|
||||||
// Hostname optionally sets the container's hostname if provided
|
|
||||||
Hostname string `json:"hostname"`
|
|
||||||
|
|
||||||
// Namespaces specifies the container's namespaces that it should setup when cloning the init process
|
|
||||||
// If a namespace is not provided that namespace is shared from the container's parent process
|
|
||||||
Namespaces Namespaces `json:"namespaces"`
|
|
||||||
|
|
||||||
// Capabilities specify the capabilities to keep when executing the process inside the container
|
|
||||||
// All capbilities not specified will be dropped from the processes capability mask
|
|
||||||
Capabilities []string `json:"capabilities"`
|
|
||||||
|
|
||||||
// Networks specifies the container's network setup to be created
|
|
||||||
Networks []*Network `json:"networks"`
|
|
||||||
|
|
||||||
// Routes can be specified to create entries in the route table as the container is started
|
|
||||||
Routes []*Route `json:"routes"`
|
|
||||||
|
|
||||||
// Cgroups specifies specific cgroup settings for the various subsystems that the container is
|
|
||||||
// placed into to limit the resources the container has available
|
|
||||||
Cgroups *Cgroup `json:"cgroups"`
|
|
||||||
|
|
||||||
// AppArmorProfile specifies the profile to apply to the process running in the container and is
|
|
||||||
// change at the time the process is execed
|
|
||||||
AppArmorProfile string `json:"apparmor_profile,omitempty"`
|
|
||||||
|
|
||||||
// ProcessLabel specifies the label to apply to the process running in the container. It is
|
|
||||||
// commonly used by selinux
|
|
||||||
ProcessLabel string `json:"process_label,omitempty"`
|
|
||||||
|
|
||||||
// Rlimits specifies the resource limits, such as max open files, to set in the container
|
|
||||||
// If Rlimits are not set, the container will inherit rlimits from the parent process
|
|
||||||
Rlimits []Rlimit `json:"rlimits,omitempty"`
|
|
||||||
|
|
||||||
// OomScoreAdj specifies the adjustment to be made by the kernel when calculating oom scores
|
|
||||||
// for a process. Valid values are between the range [-1000, '1000'], where processes with
|
|
||||||
// higher scores are preferred for being killed.
|
|
||||||
// More information about kernel oom score calculation here: https://lwn.net/Articles/317814/
|
|
||||||
OomScoreAdj int `json:"oom_score_adj"`
|
|
||||||
|
|
||||||
// UidMappings is an array of User ID mappings for User Namespaces
|
|
||||||
UidMappings []IDMap `json:"uid_mappings"`
|
|
||||||
|
|
||||||
// GidMappings is an array of Group ID mappings for User Namespaces
|
|
||||||
GidMappings []IDMap `json:"gid_mappings"`
|
|
||||||
|
|
||||||
// MaskPaths specifies paths within the container's rootfs to mask over with a bind
|
|
||||||
// mount pointing to /dev/null as to prevent reads of the file.
|
|
||||||
MaskPaths []string `json:"mask_paths"`
|
|
||||||
|
|
||||||
// ReadonlyPaths specifies paths within the container's rootfs to remount as read-only
|
|
||||||
// so that these files prevent any writes.
|
|
||||||
ReadonlyPaths []string `json:"readonly_paths"`
|
|
||||||
|
|
||||||
// Sysctl is a map of properties and their values. It is the equivalent of using
|
|
||||||
// sysctl -w my.property.name value in Linux.
|
|
||||||
Sysctl map[string]string `json:"sysctl"`
|
|
||||||
|
|
||||||
// Seccomp allows actions to be taken whenever a syscall is made within the container.
|
|
||||||
// A number of rules are given, each having an action to be taken if a syscall matches it.
|
|
||||||
// A default action to be taken if no rules match is also given.
|
|
||||||
Seccomp *Seccomp `json:"seccomp"`
|
|
||||||
|
|
||||||
// NoNewPrivileges controls whether processes in the container can gain additional privileges.
|
|
||||||
NoNewPrivileges bool `json:"no_new_privileges,omitempty"`
|
|
||||||
|
|
||||||
// Hooks are a collection of actions to perform at various container lifecycle events.
|
|
||||||
// CommandHooks are serialized to JSON, but other hooks are not.
|
|
||||||
Hooks *Hooks
|
|
||||||
|
|
||||||
// Version is the version of opencontainer specification that is supported.
|
|
||||||
Version string `json:"version"`
|
|
||||||
|
|
||||||
// Labels are user defined metadata that is stored in the config and populated on the state
|
|
||||||
Labels []string `json:"labels"`
|
|
||||||
|
|
||||||
// NoNewKeyring will not allocated a new session keyring for the container. It will use the
|
|
||||||
// callers keyring in this case.
|
|
||||||
NoNewKeyring bool `json:"no_new_keyring"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hooks struct {
|
|
||||||
// Prestart commands are executed after the container namespaces are created,
|
|
||||||
// but before the user supplied command is executed from init.
|
|
||||||
Prestart []Hook
|
|
||||||
|
|
||||||
// Poststart commands are executed after the container init process starts.
|
|
||||||
Poststart []Hook
|
|
||||||
|
|
||||||
// Poststop commands are executed after the container init process exits.
|
|
||||||
Poststop []Hook
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hooks *Hooks) UnmarshalJSON(b []byte) error {
|
|
||||||
var state struct {
|
|
||||||
Prestart []CommandHook
|
|
||||||
Poststart []CommandHook
|
|
||||||
Poststop []CommandHook
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &state); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize := func(shooks []CommandHook) (hooks []Hook) {
|
|
||||||
for _, shook := range shooks {
|
|
||||||
hooks = append(hooks, shook)
|
|
||||||
}
|
|
||||||
|
|
||||||
return hooks
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks.Prestart = deserialize(state.Prestart)
|
|
||||||
hooks.Poststart = deserialize(state.Poststart)
|
|
||||||
hooks.Poststop = deserialize(state.Poststop)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hooks Hooks) MarshalJSON() ([]byte, error) {
|
|
||||||
serialize := func(hooks []Hook) (serializableHooks []CommandHook) {
|
|
||||||
for _, hook := range hooks {
|
|
||||||
switch chook := hook.(type) {
|
|
||||||
case CommandHook:
|
|
||||||
serializableHooks = append(serializableHooks, chook)
|
|
||||||
default:
|
|
||||||
logrus.Warnf("cannot serialize hook of type %T, skipping", hook)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return serializableHooks
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(map[string]interface{}{
|
|
||||||
"prestart": serialize(hooks.Prestart),
|
|
||||||
"poststart": serialize(hooks.Poststart),
|
|
||||||
"poststop": serialize(hooks.Poststop),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// HookState is the payload provided to a hook on execution.
|
|
||||||
type HookState struct {
|
|
||||||
Version string `json:"ociVersion"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Pid int `json:"pid"`
|
|
||||||
Root string `json:"root"`
|
|
||||||
BundlePath string `json:"bundlePath"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Hook interface {
|
|
||||||
// Run executes the hook with the provided state.
|
|
||||||
Run(HookState) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFunctionHook will call the provided function when the hook is run.
|
|
||||||
func NewFunctionHook(f func(HookState) error) FuncHook {
|
|
||||||
return FuncHook{
|
|
||||||
run: f,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type FuncHook struct {
|
|
||||||
run func(HookState) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f FuncHook) Run(s HookState) error {
|
|
||||||
return f.run(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Args []string `json:"args"`
|
|
||||||
Env []string `json:"env"`
|
|
||||||
Dir string `json:"dir"`
|
|
||||||
Timeout *time.Duration `json:"timeout"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommandHook will execute the provided command when the hook is run.
|
|
||||||
func NewCommandHook(cmd Command) CommandHook {
|
|
||||||
return CommandHook{
|
|
||||||
Command: cmd,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandHook struct {
|
|
||||||
Command
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Command) Run(s HookState) error {
|
|
||||||
b, err := json.Marshal(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var stdout, stderr bytes.Buffer
|
|
||||||
cmd := exec.Cmd{
|
|
||||||
Path: c.Path,
|
|
||||||
Args: c.Args,
|
|
||||||
Env: c.Env,
|
|
||||||
Stdin: bytes.NewReader(b),
|
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: &stderr,
|
|
||||||
}
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
errC := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
err := cmd.Wait()
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("error running hook: %v, stdout: %s, stderr: %s", err, stdout.String(), stderr.String())
|
|
||||||
}
|
|
||||||
errC <- err
|
|
||||||
}()
|
|
||||||
var timerCh <-chan time.Time
|
|
||||||
if c.Timeout != nil {
|
|
||||||
timer := time.NewTimer(*c.Timeout)
|
|
||||||
defer timer.Stop()
|
|
||||||
timerCh = timer.C
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case err := <-errC:
|
|
||||||
return err
|
|
||||||
case <-timerCh:
|
|
||||||
cmd.Process.Kill()
|
|
||||||
cmd.Wait()
|
|
||||||
return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
// +build freebsd linux
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// HostUID gets the root uid for the process on host which could be non-zero
|
|
||||||
// when user namespaces are enabled.
|
|
||||||
func (c Config) HostUID() (int, error) {
|
|
||||||
if c.Namespaces.Contains(NEWUSER) {
|
|
||||||
if c.UidMappings == nil {
|
|
||||||
return -1, fmt.Errorf("User namespaces enabled, but no user mappings found.")
|
|
||||||
}
|
|
||||||
id, found := c.hostIDFromMapping(0, c.UidMappings)
|
|
||||||
if !found {
|
|
||||||
return -1, fmt.Errorf("User namespaces enabled, but no root user mapping found.")
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
// Return default root uid 0
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HostGID gets the root gid for the process on host which could be non-zero
|
|
||||||
// when user namespaces are enabled.
|
|
||||||
func (c Config) HostGID() (int, error) {
|
|
||||||
if c.Namespaces.Contains(NEWUSER) {
|
|
||||||
if c.GidMappings == nil {
|
|
||||||
return -1, fmt.Errorf("User namespaces enabled, but no gid mappings found.")
|
|
||||||
}
|
|
||||||
id, found := c.hostIDFromMapping(0, c.GidMappings)
|
|
||||||
if !found {
|
|
||||||
return -1, fmt.Errorf("User namespaces enabled, but no root group mapping found.")
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
// Return default root gid 0
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility function that gets a host ID for a container ID from user namespace map
|
|
||||||
// if that ID is present in the map.
|
|
||||||
func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) {
|
|
||||||
for _, m := range uMap {
|
|
||||||
if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) {
|
|
||||||
hostID := m.HostID + (containerID - m.ContainerID)
|
|
||||||
return hostID, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, false
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Wildcard = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO Windows: This can be factored out in the future
|
|
||||||
|
|
||||||
type Device struct {
|
|
||||||
// Device type, block, char, etc.
|
|
||||||
Type rune `json:"type"`
|
|
||||||
|
|
||||||
// Path to the device.
|
|
||||||
Path string `json:"path"`
|
|
||||||
|
|
||||||
// Major is the device's major number.
|
|
||||||
Major int64 `json:"major"`
|
|
||||||
|
|
||||||
// Minor is the device's minor number.
|
|
||||||
Minor int64 `json:"minor"`
|
|
||||||
|
|
||||||
// Cgroup permissions format, rwm.
|
|
||||||
Permissions string `json:"permissions"`
|
|
||||||
|
|
||||||
// FileMode permission bits for the device.
|
|
||||||
FileMode os.FileMode `json:"file_mode"`
|
|
||||||
|
|
||||||
// Uid of the device.
|
|
||||||
Uid uint32 `json:"uid"`
|
|
||||||
|
|
||||||
// Gid of the device.
|
|
||||||
Gid uint32 `json:"gid"`
|
|
||||||
|
|
||||||
// Write the file to the allowed list
|
|
||||||
Allow bool `json:"allow"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Device) CgroupString() string {
|
|
||||||
return fmt.Sprintf("%c %s:%s %s", d.Type, deviceNumberString(d.Major), deviceNumberString(d.Minor), d.Permissions)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Device) Mkdev() int {
|
|
||||||
return int((d.Major << 8) | (d.Minor & 0xff) | ((d.Minor & 0xfff00) << 12))
|
|
||||||
}
|
|
||||||
|
|
||||||
// deviceNumberString converts the device number to a string return result.
|
|
||||||
func deviceNumberString(number int64) string {
|
|
||||||
if number == Wildcard {
|
|
||||||
return "*"
|
|
||||||
}
|
|
||||||
return fmt.Sprint(number)
|
|
||||||
}
|
|
111
vendor/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go
generated
vendored
111
vendor/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go
generated
vendored
|
@ -1,111 +0,0 @@
|
||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultSimpleDevices are devices that are to be both allowed and created.
|
|
||||||
DefaultSimpleDevices = []*Device{
|
|
||||||
// /dev/null and zero
|
|
||||||
{
|
|
||||||
Path: "/dev/null",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 1,
|
|
||||||
Minor: 3,
|
|
||||||
Permissions: "rwm",
|
|
||||||
FileMode: 0666,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "/dev/zero",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 1,
|
|
||||||
Minor: 5,
|
|
||||||
Permissions: "rwm",
|
|
||||||
FileMode: 0666,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Path: "/dev/full",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 1,
|
|
||||||
Minor: 7,
|
|
||||||
Permissions: "rwm",
|
|
||||||
FileMode: 0666,
|
|
||||||
},
|
|
||||||
|
|
||||||
// consoles and ttys
|
|
||||||
{
|
|
||||||
Path: "/dev/tty",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 5,
|
|
||||||
Minor: 0,
|
|
||||||
Permissions: "rwm",
|
|
||||||
FileMode: 0666,
|
|
||||||
},
|
|
||||||
|
|
||||||
// /dev/urandom,/dev/random
|
|
||||||
{
|
|
||||||
Path: "/dev/urandom",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 1,
|
|
||||||
Minor: 9,
|
|
||||||
Permissions: "rwm",
|
|
||||||
FileMode: 0666,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "/dev/random",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 1,
|
|
||||||
Minor: 8,
|
|
||||||
Permissions: "rwm",
|
|
||||||
FileMode: 0666,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
DefaultAllowedDevices = append([]*Device{
|
|
||||||
// allow mknod for any device
|
|
||||||
{
|
|
||||||
Type: 'c',
|
|
||||||
Major: Wildcard,
|
|
||||||
Minor: Wildcard,
|
|
||||||
Permissions: "m",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: 'b',
|
|
||||||
Major: Wildcard,
|
|
||||||
Minor: Wildcard,
|
|
||||||
Permissions: "m",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Path: "/dev/console",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 5,
|
|
||||||
Minor: 1,
|
|
||||||
Permissions: "rwm",
|
|
||||||
},
|
|
||||||
// /dev/pts/ - pts namespaces are "coming soon"
|
|
||||||
{
|
|
||||||
Path: "",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 136,
|
|
||||||
Minor: Wildcard,
|
|
||||||
Permissions: "rwm",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 5,
|
|
||||||
Minor: 2,
|
|
||||||
Permissions: "rwm",
|
|
||||||
},
|
|
||||||
|
|
||||||
// tuntap
|
|
||||||
{
|
|
||||||
Path: "",
|
|
||||||
Type: 'c',
|
|
||||||
Major: 10,
|
|
||||||
Minor: 200,
|
|
||||||
Permissions: "rwm",
|
|
||||||
},
|
|
||||||
}, DefaultSimpleDevices...)
|
|
||||||
DefaultAutoCreatedDevices = append([]*Device{}, DefaultSimpleDevices...)
|
|
||||||
)
|
|
|
@ -1,9 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
type HugepageLimit struct {
|
|
||||||
// which type of hugepage to limit.
|
|
||||||
Pagesize string `json:"page_size"`
|
|
||||||
|
|
||||||
// usage limit for hugepage.
|
|
||||||
Limit uint64 `json:"limit"`
|
|
||||||
}
|
|
14
vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go
generated
vendored
14
vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IfPrioMap struct {
|
|
||||||
Interface string `json:"interface"`
|
|
||||||
Priority int64 `json:"priority"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IfPrioMap) CgroupString() string {
|
|
||||||
return fmt.Sprintf("%s %d", i.Interface, i.Priority)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
const (
|
|
||||||
// EXT_COPYUP is a directive to copy up the contents of a directory when
|
|
||||||
// a tmpfs is mounted over it.
|
|
||||||
EXT_COPYUP = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
type Mount struct {
|
|
||||||
// Source path for the mount.
|
|
||||||
Source string `json:"source"`
|
|
||||||
|
|
||||||
// Destination path for the mount inside the container.
|
|
||||||
Destination string `json:"destination"`
|
|
||||||
|
|
||||||
// Device the mount is for.
|
|
||||||
Device string `json:"device"`
|
|
||||||
|
|
||||||
// Mount flags.
|
|
||||||
Flags int `json:"flags"`
|
|
||||||
|
|
||||||
// Propagation Flags
|
|
||||||
PropagationFlags []int `json:"propagation_flags"`
|
|
||||||
|
|
||||||
// Mount data applied to the mount.
|
|
||||||
Data string `json:"data"`
|
|
||||||
|
|
||||||
// Relabel source if set, "z" indicates shared, "Z" indicates unshared.
|
|
||||||
Relabel string `json:"relabel"`
|
|
||||||
|
|
||||||
// Extensions are additional flags that are specific to runc.
|
|
||||||
Extensions int `json:"extensions"`
|
|
||||||
|
|
||||||
// Optional Command to be run before Source is mounted.
|
|
||||||
PremountCmds []Command `json:"premount_cmds"`
|
|
||||||
|
|
||||||
// Optional Command to be run after Source is mounted.
|
|
||||||
PostmountCmds []Command `json:"postmount_cmds"`
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
type NamespaceType string
|
|
||||||
|
|
||||||
type Namespaces []Namespace
|
|
31
vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go
generated
vendored
31
vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go
generated
vendored
|
@ -1,31 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
func (n *Namespace) Syscall() int {
|
|
||||||
return namespaceInfo[n.Type]
|
|
||||||
}
|
|
||||||
|
|
||||||
var namespaceInfo = map[NamespaceType]int{
|
|
||||||
NEWNET: syscall.CLONE_NEWNET,
|
|
||||||
NEWNS: syscall.CLONE_NEWNS,
|
|
||||||
NEWUSER: syscall.CLONE_NEWUSER,
|
|
||||||
NEWIPC: syscall.CLONE_NEWIPC,
|
|
||||||
NEWUTS: syscall.CLONE_NEWUTS,
|
|
||||||
NEWPID: syscall.CLONE_NEWPID,
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloneFlags parses the container's Namespaces options to set the correct
|
|
||||||
// flags on clone, unshare. This function returns flags only for new namespaces.
|
|
||||||
func (n *Namespaces) CloneFlags() uintptr {
|
|
||||||
var flag int
|
|
||||||
for _, v := range *n {
|
|
||||||
if v.Path != "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
flag |= namespaceInfo[v.Type]
|
|
||||||
}
|
|
||||||
return uintptr(flag)
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// +build !linux,!windows
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
func (n *Namespace) Syscall() int {
|
|
||||||
panic("No namespace syscall support")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloneFlags parses the container's Namespaces options to set the correct
|
|
||||||
// flags on clone, unshare. This function returns flags only for new namespaces.
|
|
||||||
func (n *Namespaces) CloneFlags() uintptr {
|
|
||||||
panic("No namespace syscall support")
|
|
||||||
return uintptr(0)
|
|
||||||
}
|
|
127
vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go
generated
vendored
127
vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go
generated
vendored
|
@ -1,127 +0,0 @@
|
||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NEWNET NamespaceType = "NEWNET"
|
|
||||||
NEWPID NamespaceType = "NEWPID"
|
|
||||||
NEWNS NamespaceType = "NEWNS"
|
|
||||||
NEWUTS NamespaceType = "NEWUTS"
|
|
||||||
NEWIPC NamespaceType = "NEWIPC"
|
|
||||||
NEWUSER NamespaceType = "NEWUSER"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
nsLock sync.Mutex
|
|
||||||
supportedNamespaces = make(map[NamespaceType]bool)
|
|
||||||
)
|
|
||||||
|
|
||||||
// NsName converts the namespace type to its filename
|
|
||||||
func NsName(ns NamespaceType) string {
|
|
||||||
switch ns {
|
|
||||||
case NEWNET:
|
|
||||||
return "net"
|
|
||||||
case NEWNS:
|
|
||||||
return "mnt"
|
|
||||||
case NEWPID:
|
|
||||||
return "pid"
|
|
||||||
case NEWIPC:
|
|
||||||
return "ipc"
|
|
||||||
case NEWUSER:
|
|
||||||
return "user"
|
|
||||||
case NEWUTS:
|
|
||||||
return "uts"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNamespaceSupported returns whether a namespace is available or
|
|
||||||
// not
|
|
||||||
func IsNamespaceSupported(ns NamespaceType) bool {
|
|
||||||
nsLock.Lock()
|
|
||||||
defer nsLock.Unlock()
|
|
||||||
supported, ok := supportedNamespaces[ns]
|
|
||||||
if ok {
|
|
||||||
return supported
|
|
||||||
}
|
|
||||||
nsFile := NsName(ns)
|
|
||||||
// if the namespace type is unknown, just return false
|
|
||||||
if nsFile == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
_, err := os.Stat(fmt.Sprintf("/proc/self/ns/%s", nsFile))
|
|
||||||
// a namespace is supported if it exists and we have permissions to read it
|
|
||||||
supported = err == nil
|
|
||||||
supportedNamespaces[ns] = supported
|
|
||||||
return supported
|
|
||||||
}
|
|
||||||
|
|
||||||
func NamespaceTypes() []NamespaceType {
|
|
||||||
return []NamespaceType{
|
|
||||||
NEWNET,
|
|
||||||
NEWPID,
|
|
||||||
NEWNS,
|
|
||||||
NEWUTS,
|
|
||||||
NEWIPC,
|
|
||||||
NEWUSER,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Namespace defines configuration for each namespace. It specifies an
|
|
||||||
// alternate path that is able to be joined via setns.
|
|
||||||
type Namespace struct {
|
|
||||||
Type NamespaceType `json:"type"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespace) GetPath(pid int) string {
|
|
||||||
if n.Path != "" {
|
|
||||||
return n.Path
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("/proc/%d/ns/%s", pid, NsName(n.Type))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Remove(t NamespaceType) bool {
|
|
||||||
i := n.index(t)
|
|
||||||
if i == -1 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
*n = append((*n)[:i], (*n)[i+1:]...)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Add(t NamespaceType, path string) {
|
|
||||||
i := n.index(t)
|
|
||||||
if i == -1 {
|
|
||||||
*n = append(*n, Namespace{Type: t, Path: path})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
(*n)[i].Path = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) index(t NamespaceType) int {
|
|
||||||
for i, ns := range *n {
|
|
||||||
if ns.Type == t {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) Contains(t NamespaceType) bool {
|
|
||||||
return n.index(t) != -1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Namespaces) PathOf(t NamespaceType) string {
|
|
||||||
i := n.index(t)
|
|
||||||
if i == -1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return (*n)[i].Path
|
|
||||||
}
|
|
8
vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go
generated
vendored
8
vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go
generated
vendored
|
@ -1,8 +0,0 @@
|
||||||
// +build !linux,!freebsd
|
|
||||||
|
|
||||||
package configs
|
|
||||||
|
|
||||||
// Namespace defines configuration for each namespace. It specifies an
|
|
||||||
// alternate path that is able to be joined via setns.
|
|
||||||
type Namespace struct {
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
// Network defines configuration for a container's networking stack
|
|
||||||
//
|
|
||||||
// The network configuration can be omitted from a container causing the
|
|
||||||
// container to be setup with the host's networking stack
|
|
||||||
type Network struct {
|
|
||||||
// Type sets the networks type, commonly veth and loopback
|
|
||||||
Type string `json:"type"`
|
|
||||||
|
|
||||||
// Name of the network interface
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// The bridge to use.
|
|
||||||
Bridge string `json:"bridge"`
|
|
||||||
|
|
||||||
// MacAddress contains the MAC address to set on the network interface
|
|
||||||
MacAddress string `json:"mac_address"`
|
|
||||||
|
|
||||||
// Address contains the IPv4 and mask to set on the network interface
|
|
||||||
Address string `json:"address"`
|
|
||||||
|
|
||||||
// Gateway sets the gateway address that is used as the default for the interface
|
|
||||||
Gateway string `json:"gateway"`
|
|
||||||
|
|
||||||
// IPv6Address contains the IPv6 and mask to set on the network interface
|
|
||||||
IPv6Address string `json:"ipv6_address"`
|
|
||||||
|
|
||||||
// IPv6Gateway sets the ipv6 gateway address that is used as the default for the interface
|
|
||||||
IPv6Gateway string `json:"ipv6_gateway"`
|
|
||||||
|
|
||||||
// Mtu sets the mtu value for the interface and will be mirrored on both the host and
|
|
||||||
// container's interfaces if a pair is created, specifically in the case of type veth
|
|
||||||
// Note: This does not apply to loopback interfaces.
|
|
||||||
Mtu int `json:"mtu"`
|
|
||||||
|
|
||||||
// TxQueueLen sets the tx_queuelen value for the interface and will be mirrored on both the host and
|
|
||||||
// container's interfaces if a pair is created, specifically in the case of type veth
|
|
||||||
// Note: This does not apply to loopback interfaces.
|
|
||||||
TxQueueLen int `json:"txqueuelen"`
|
|
||||||
|
|
||||||
// HostInterfaceName is a unique name of a veth pair that resides on in the host interface of the
|
|
||||||
// container.
|
|
||||||
HostInterfaceName string `json:"host_interface_name"`
|
|
||||||
|
|
||||||
// HairpinMode specifies if hairpin NAT should be enabled on the virtual interface
|
|
||||||
// bridge port in the case of type veth
|
|
||||||
// Note: This is unsupported on some systems.
|
|
||||||
// Note: This does not apply to loopback interfaces.
|
|
||||||
HairpinMode bool `json:"hairpin_mode"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routes can be specified to create entries in the route table as the container is started
|
|
||||||
//
|
|
||||||
// All of destination, source, and gateway should be either IPv4 or IPv6.
|
|
||||||
// One of the three options must be present, and omitted entries will use their
|
|
||||||
// IP family default for the route table. For IPv4 for example, setting the
|
|
||||||
// gateway to 1.2.3.4 and the interface to eth0 will set up a standard
|
|
||||||
// destination of 0.0.0.0(or *) when viewed in the route table.
|
|
||||||
type Route struct {
|
|
||||||
// Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6
|
|
||||||
Destination string `json:"destination"`
|
|
||||||
|
|
||||||
// Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6
|
|
||||||
Source string `json:"source"`
|
|
||||||
|
|
||||||
// Sets the gateway. Accepts IPv4 and IPv6
|
|
||||||
Gateway string `json:"gateway"`
|
|
||||||
|
|
||||||
// The device to set this route up for, for example: eth0
|
|
||||||
InterfaceName string `json:"interface_name"`
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
|
@ -1,7 +0,0 @@
|
||||||
# selinux
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/opencontainers/selinux?status.svg)](https://godoc.org/github.com/opencontainers/selinux) [![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/selinux)](https://goreportcard.com/report/github.com/opencontainers/selinux) [![Build Status](https://travis-ci.org/opencontainers/selinux.svg?branch=master)](https://travis-ci.org/opencontainers/selinux)
|
|
||||||
|
|
||||||
Common SELinux package used across the container ecosystem.
|
|
||||||
|
|
||||||
Please see the [godoc](https://godoc.org/github.com/opencontainers/selinux) for more information.
|
|
|
@ -1,84 +0,0 @@
|
||||||
// +build !selinux !linux
|
|
||||||
|
|
||||||
package label
|
|
||||||
|
|
||||||
// InitLabels returns the process label and file labels to be used within
|
|
||||||
// the container. A list of options can be passed into this function to alter
|
|
||||||
// the labels.
|
|
||||||
func InitLabels(options []string) (string, string, error) {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetROMountLabel() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenLabels(options string) (string, string, error) {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMountLabel(src string, mountLabel string) string {
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetProcessLabel(processLabel string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFileLabel(path string) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetFileLabel(path string, fileLabel string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetFileCreateLabel(fileLabel string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Relabel(path string, fileLabel string, shared bool) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPidLabel(pid int) (string, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReserveLabel(label string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReleaseLabel(label string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DupSecOpt takes a process label and returns security options that
|
|
||||||
// can be used to set duplicate labels on future container processes
|
|
||||||
func DupSecOpt(src string) []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableSecOpt returns a security opt that can disable labeling
|
|
||||||
// support for future container processes
|
|
||||||
func DisableSecOpt() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks that the label does not include unexpected options
|
|
||||||
func Validate(label string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelabelNeeded checks whether the user requested a relabel
|
|
||||||
func RelabelNeeded(label string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsShared checks that the label includes a "shared" mark
|
|
||||||
func IsShared(label string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,204 +0,0 @@
|
||||||
// +build selinux,linux
|
|
||||||
|
|
||||||
package label
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/opencontainers/selinux/go-selinux"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Valid Label Options
|
|
||||||
var validOptions = map[string]bool{
|
|
||||||
"disable": true,
|
|
||||||
"type": true,
|
|
||||||
"user": true,
|
|
||||||
"role": true,
|
|
||||||
"level": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrIncompatibleLabel = fmt.Errorf("Bad SELinux option z and Z can not be used together")
|
|
||||||
|
|
||||||
// InitLabels returns the process label and file labels to be used within
|
|
||||||
// the container. A list of options can be passed into this function to alter
|
|
||||||
// the labels. The labels returned will include a random MCS String, that is
|
|
||||||
// guaranteed to be unique.
|
|
||||||
func InitLabels(options []string) (string, string, error) {
|
|
||||||
if !selinux.GetEnabled() {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
processLabel, mountLabel := selinux.ContainerLabels()
|
|
||||||
if processLabel != "" {
|
|
||||||
pcon := selinux.NewContext(processLabel)
|
|
||||||
mcon := selinux.NewContext(mountLabel)
|
|
||||||
for _, opt := range options {
|
|
||||||
if opt == "disable" {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
if i := strings.Index(opt, ":"); i == -1 {
|
|
||||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type' followed by ':' and a value", opt)
|
|
||||||
}
|
|
||||||
con := strings.SplitN(opt, ":", 2)
|
|
||||||
if !validOptions[con[0]] {
|
|
||||||
return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type'", con[0])
|
|
||||||
|
|
||||||
}
|
|
||||||
pcon[con[0]] = con[1]
|
|
||||||
if con[0] == "level" || con[0] == "user" {
|
|
||||||
mcon[con[0]] = con[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processLabel = pcon.Get()
|
|
||||||
mountLabel = mcon.Get()
|
|
||||||
}
|
|
||||||
return processLabel, mountLabel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ROMountLabel() string {
|
|
||||||
return selinux.ROFileLabel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEPRECATED: The GenLabels function is only to be used during the transition to the official API.
|
|
||||||
func GenLabels(options string) (string, string, error) {
|
|
||||||
return InitLabels(strings.Fields(options))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatMountLabel returns a string to be used by the mount command.
|
|
||||||
// The format of this string will be used to alter the labeling of the mountpoint.
|
|
||||||
// The string returned is suitable to be used as the options field of the mount command.
|
|
||||||
// If you need to have additional mount point options, you can pass them in as
|
|
||||||
// the first parameter. Second parameter is the label that you wish to apply
|
|
||||||
// to all content in the mount point.
|
|
||||||
func FormatMountLabel(src, mountLabel string) string {
|
|
||||||
if mountLabel != "" {
|
|
||||||
switch src {
|
|
||||||
case "":
|
|
||||||
src = fmt.Sprintf("context=%q", mountLabel)
|
|
||||||
default:
|
|
||||||
src = fmt.Sprintf("%s,context=%q", src, mountLabel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetProcessLabel takes a process label and tells the kernel to assign the
|
|
||||||
// label to the next program executed by the current process.
|
|
||||||
func SetProcessLabel(processLabel string) error {
|
|
||||||
if processLabel == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return selinux.SetExecLabel(processLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessLabel returns the process label that the kernel will assign
|
|
||||||
// to the next program executed by the current process. If "" is returned
|
|
||||||
// this indicates that the default labeling will happen for the process.
|
|
||||||
func ProcessLabel() (string, error) {
|
|
||||||
return selinux.ExecLabel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileLabel returns the label for specified path
|
|
||||||
func FileLabel(path string) (string, error) {
|
|
||||||
return selinux.FileLabel(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFileLabel modifies the "path" label to the specified file label
|
|
||||||
func SetFileLabel(path string, fileLabel string) error {
|
|
||||||
if selinux.GetEnabled() && fileLabel != "" {
|
|
||||||
return selinux.SetFileLabel(path, fileLabel)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFileCreateLabel tells the kernel the label for all files to be created
|
|
||||||
func SetFileCreateLabel(fileLabel string) error {
|
|
||||||
if selinux.GetEnabled() {
|
|
||||||
return selinux.SetFSCreateLabel(fileLabel)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relabel changes the label of path to the filelabel string.
|
|
||||||
// It changes the MCS label to s0 if shared is true.
|
|
||||||
// This will allow all containers to share the content.
|
|
||||||
func Relabel(path string, fileLabel string, shared bool) error {
|
|
||||||
if !selinux.GetEnabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileLabel == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
exclude_paths := map[string]bool{"/": true, "/usr": true, "/etc": true}
|
|
||||||
if exclude_paths[path] {
|
|
||||||
return fmt.Errorf("SELinux relabeling of %s is not allowed", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if shared {
|
|
||||||
c := selinux.NewContext(fileLabel)
|
|
||||||
c["level"] = "s0"
|
|
||||||
fileLabel = c.Get()
|
|
||||||
}
|
|
||||||
if err := selinux.Chcon(path, fileLabel, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PidLabel will return the label of the process running with the specified pid
|
|
||||||
func PidLabel(pid int) (string, error) {
|
|
||||||
return selinux.PidLabel(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initialises the labeling system
|
|
||||||
func Init() {
|
|
||||||
selinux.GetEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReserveLabel will record the fact that the MCS label has already been used.
|
|
||||||
// This will prevent InitLabels from using the MCS label in a newly created
|
|
||||||
// container
|
|
||||||
func ReserveLabel(label string) error {
|
|
||||||
selinux.ReserveLabel(label)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReleaseLabel will remove the reservation of the MCS label.
|
|
||||||
// This will allow InitLabels to use the MCS label in a newly created
|
|
||||||
// containers
|
|
||||||
func ReleaseLabel(label string) error {
|
|
||||||
selinux.ReleaseLabel(label)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DupSecOpt takes a process label and returns security options that
|
|
||||||
// can be used to set duplicate labels on future container processes
|
|
||||||
func DupSecOpt(src string) []string {
|
|
||||||
return selinux.DupSecOpt(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableSecOpt returns a security opt that can disable labeling
|
|
||||||
// support for future container processes
|
|
||||||
func DisableSecOpt() []string {
|
|
||||||
return selinux.DisableSecOpt()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks that the label does not include unexpected options
|
|
||||||
func Validate(label string) error {
|
|
||||||
if strings.Contains(label, "z") && strings.Contains(label, "Z") {
|
|
||||||
return ErrIncompatibleLabel
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelabelNeeded checks whether the user requested a relabel
|
|
||||||
func RelabelNeeded(label string) bool {
|
|
||||||
return strings.Contains(label, "z") || strings.Contains(label, "Z")
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsShared checks that the label includes a "shared" mark
|
|
||||||
func IsShared(label string) bool {
|
|
||||||
return strings.Contains(label, "z")
|
|
||||||
}
|
|
|
@ -1,593 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package selinux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Enforcing constant indicate SELinux is in enforcing mode
|
|
||||||
Enforcing = 1
|
|
||||||
// Permissive constant to indicate SELinux is in permissive mode
|
|
||||||
Permissive = 0
|
|
||||||
// Disabled constant to indicate SELinux is disabled
|
|
||||||
Disabled = -1
|
|
||||||
selinuxDir = "/etc/selinux/"
|
|
||||||
selinuxConfig = selinuxDir + "config"
|
|
||||||
selinuxTypeTag = "SELINUXTYPE"
|
|
||||||
selinuxTag = "SELINUX"
|
|
||||||
selinuxPath = "/sys/fs/selinux"
|
|
||||||
xattrNameSelinux = "security.selinux"
|
|
||||||
stRdOnly = 0x01
|
|
||||||
)
|
|
||||||
|
|
||||||
type selinuxState struct {
|
|
||||||
enabledSet bool
|
|
||||||
enabled bool
|
|
||||||
selinuxfsSet bool
|
|
||||||
selinuxfs string
|
|
||||||
mcsList map[string]bool
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
|
|
||||||
state = selinuxState{
|
|
||||||
mcsList: make(map[string]bool),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Context is a representation of the SELinux label broken into 4 parts
|
|
||||||
type Context map[string]string
|
|
||||||
|
|
||||||
func (s *selinuxState) setEnable(enabled bool) bool {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
s.enabledSet = true
|
|
||||||
s.enabled = enabled
|
|
||||||
return s.enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *selinuxState) getEnabled() bool {
|
|
||||||
s.Lock()
|
|
||||||
enabled := s.enabled
|
|
||||||
enabledSet := s.enabledSet
|
|
||||||
s.Unlock()
|
|
||||||
if enabledSet {
|
|
||||||
return enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled = false
|
|
||||||
if fs := getSelinuxMountPoint(); fs != "" {
|
|
||||||
if con, _ := CurrentLabel(); con != "kernel" {
|
|
||||||
enabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.setEnable(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDisabled disables selinux support for the package
|
|
||||||
func SetDisabled() {
|
|
||||||
state.setEnable(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *selinuxState) setSELinuxfs(selinuxfs string) string {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
s.selinuxfsSet = true
|
|
||||||
s.selinuxfs = selinuxfs
|
|
||||||
return s.selinuxfs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *selinuxState) getSELinuxfs() string {
|
|
||||||
s.Lock()
|
|
||||||
selinuxfs := s.selinuxfs
|
|
||||||
selinuxfsSet := s.selinuxfsSet
|
|
||||||
s.Unlock()
|
|
||||||
if selinuxfsSet {
|
|
||||||
return selinuxfs
|
|
||||||
}
|
|
||||||
|
|
||||||
selinuxfs = ""
|
|
||||||
f, err := os.Open("/proc/self/mountinfo")
|
|
||||||
if err != nil {
|
|
||||||
return selinuxfs
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(f)
|
|
||||||
for scanner.Scan() {
|
|
||||||
txt := scanner.Text()
|
|
||||||
// Safe as mountinfo encodes mountpoints with spaces as \040.
|
|
||||||
sepIdx := strings.Index(txt, " - ")
|
|
||||||
if sepIdx == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !strings.Contains(txt[sepIdx:], "selinuxfs") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Split(txt, " ")
|
|
||||||
if len(fields) < 5 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
selinuxfs = fields[4]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if selinuxfs != "" {
|
|
||||||
var buf syscall.Statfs_t
|
|
||||||
syscall.Statfs(selinuxfs, &buf)
|
|
||||||
if (buf.Flags & stRdOnly) == 1 {
|
|
||||||
selinuxfs = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s.setSELinuxfs(selinuxfs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs
|
|
||||||
// filesystem or an empty string if no mountpoint is found. Selinuxfs is
|
|
||||||
// a proc-like pseudo-filesystem that exposes the selinux policy API to
|
|
||||||
// processes. The existence of an selinuxfs mount is used to determine
|
|
||||||
// whether selinux is currently enabled or not.
|
|
||||||
func getSelinuxMountPoint() string {
|
|
||||||
return state.getSELinuxfs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEnabled returns whether selinux is currently enabled.
|
|
||||||
func GetEnabled() bool {
|
|
||||||
return state.getEnabled()
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConfig(target string) (value string) {
|
|
||||||
var (
|
|
||||||
val, key string
|
|
||||||
bufin *bufio.Reader
|
|
||||||
)
|
|
||||||
|
|
||||||
in, err := os.Open(selinuxConfig)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
bufin = bufio.NewReader(in)
|
|
||||||
|
|
||||||
for done := false; !done; {
|
|
||||||
var line string
|
|
||||||
if line, err = bufin.ReadString('\n'); err != nil {
|
|
||||||
if err != io.EOF {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
done = true
|
|
||||||
}
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
// Skip blank lines
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if line[0] == ';' || line[0] == '#' {
|
|
||||||
// Skip comments
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
|
|
||||||
key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
|
|
||||||
if key == target {
|
|
||||||
return strings.Trim(val, "\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSELinuxPolicyRoot() string {
|
|
||||||
return selinuxDir + readConfig(selinuxTypeTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readCon(name string) (string, error) {
|
|
||||||
var val string
|
|
||||||
|
|
||||||
in, err := os.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
_, err = fmt.Fscanf(in, "%s", &val)
|
|
||||||
return val, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFileLabel sets the SELinux label for this path or returns an error.
|
|
||||||
func SetFileLabel(path string, label string) error {
|
|
||||||
return lsetxattr(path, xattrNameSelinux, []byte(label), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filecon returns the SELinux label for this path or returns an error.
|
|
||||||
func FileLabel(path string) (string, error) {
|
|
||||||
label, err := lgetxattr(path, xattrNameSelinux)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// Trim the NUL byte at the end of the byte buffer, if present.
|
|
||||||
if len(label) > 0 && label[len(label)-1] == '\x00' {
|
|
||||||
label = label[:len(label)-1]
|
|
||||||
}
|
|
||||||
return string(label), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
SetFSCreateLabel tells kernel the label to create all file system objects
|
|
||||||
created by this task. Setting label="" to return to default.
|
|
||||||
*/
|
|
||||||
func SetFSCreateLabel(label string) error {
|
|
||||||
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), label)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
FSCreateLabel returns the default label the kernel which the kernel is using
|
|
||||||
for file system objects created by this task. "" indicates default.
|
|
||||||
*/
|
|
||||||
func FSCreateLabel() (string, error) {
|
|
||||||
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentLabel returns the SELinux label of the current process thread, or an error.
|
|
||||||
func CurrentLabel() (string, error) {
|
|
||||||
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PidLabel returns the SELinux label of the given pid, or an error.
|
|
||||||
func PidLabel(pid int) (string, error) {
|
|
||||||
return readCon(fmt.Sprintf("/proc/%d/attr/current", pid))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ExecLabel returns the SELinux label that the kernel will use for any programs
|
|
||||||
that are executed by the current process thread, or an error.
|
|
||||||
*/
|
|
||||||
func ExecLabel() (string, error) {
|
|
||||||
return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCon(name string, val string) error {
|
|
||||||
out, err := os.OpenFile(name, os.O_WRONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer out.Close()
|
|
||||||
|
|
||||||
if val != "" {
|
|
||||||
_, err = out.Write([]byte(val))
|
|
||||||
} else {
|
|
||||||
_, err = out.Write(nil)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
SetExecLabel sets the SELinux label that the kernel will use for any programs
|
|
||||||
that are executed by the current process thread, or an error.
|
|
||||||
*/
|
|
||||||
func SetExecLabel(label string) error {
|
|
||||||
return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the Context as a string
|
|
||||||
func (c Context) Get() string {
|
|
||||||
return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"])
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext creates a new Context struct from the specified label
|
|
||||||
func NewContext(label string) Context {
|
|
||||||
c := make(Context)
|
|
||||||
|
|
||||||
if len(label) != 0 {
|
|
||||||
con := strings.SplitN(label, ":", 4)
|
|
||||||
c["user"] = con[0]
|
|
||||||
c["role"] = con[1]
|
|
||||||
c["type"] = con[2]
|
|
||||||
c["level"] = con[3]
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReserveLabel reserves the MLS/MCS level component of the specified label
|
|
||||||
func ReserveLabel(label string) {
|
|
||||||
if len(label) != 0 {
|
|
||||||
con := strings.SplitN(label, ":", 4)
|
|
||||||
mcsAdd(con[3])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func selinuxEnforcePath() string {
|
|
||||||
return fmt.Sprintf("%s/enforce", selinuxPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled
|
|
||||||
func EnforceMode() int {
|
|
||||||
var enforce int
|
|
||||||
|
|
||||||
enforceS, err := readCon(selinuxEnforcePath())
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
enforce, err = strconv.Atoi(string(enforceS))
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return enforce
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
SetEnforce sets the current SELinux mode Enforcing, Permissive.
|
|
||||||
Disabled is not valid, since this needs to be set at boot time.
|
|
||||||
*/
|
|
||||||
func SetEnforceMode(mode int) error {
|
|
||||||
return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
DefaultEnforceMode returns the systems default SELinux mode Enforcing,
|
|
||||||
Permissive or Disabled. Note this is is just the default at boot time.
|
|
||||||
EnforceMode tells you the systems current mode.
|
|
||||||
*/
|
|
||||||
func DefaultEnforceMode() int {
|
|
||||||
switch readConfig(selinuxTag) {
|
|
||||||
case "enforcing":
|
|
||||||
return Enforcing
|
|
||||||
case "permissive":
|
|
||||||
return Permissive
|
|
||||||
}
|
|
||||||
return Disabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func mcsAdd(mcs string) error {
|
|
||||||
state.Lock()
|
|
||||||
defer state.Unlock()
|
|
||||||
if state.mcsList[mcs] {
|
|
||||||
return fmt.Errorf("MCS Label already exists")
|
|
||||||
}
|
|
||||||
state.mcsList[mcs] = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mcsDelete(mcs string) {
|
|
||||||
state.Lock()
|
|
||||||
defer state.Unlock()
|
|
||||||
state.mcsList[mcs] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func intToMcs(id int, catRange uint32) string {
|
|
||||||
var (
|
|
||||||
SETSIZE = int(catRange)
|
|
||||||
TIER = SETSIZE
|
|
||||||
ORD = id
|
|
||||||
)
|
|
||||||
|
|
||||||
if id < 1 || id > 523776 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
for ORD > TIER {
|
|
||||||
ORD = ORD - TIER
|
|
||||||
TIER--
|
|
||||||
}
|
|
||||||
TIER = SETSIZE - TIER
|
|
||||||
ORD = ORD + TIER
|
|
||||||
return fmt.Sprintf("s0:c%d,c%d", TIER, ORD)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uniqMcs(catRange uint32) string {
|
|
||||||
var (
|
|
||||||
n uint32
|
|
||||||
c1, c2 uint32
|
|
||||||
mcs string
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &n)
|
|
||||||
c1 = n % catRange
|
|
||||||
binary.Read(rand.Reader, binary.LittleEndian, &n)
|
|
||||||
c2 = n % catRange
|
|
||||||
if c1 == c2 {
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
if c1 > c2 {
|
|
||||||
c1, c2 = c2, c1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2)
|
|
||||||
if err := mcsAdd(mcs); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return mcs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ReleaseLabel will unreserve the MLS/MCS Level field of the specified label.
|
|
||||||
Allowing it to be used by another process.
|
|
||||||
*/
|
|
||||||
func ReleaseLabel(label string) {
|
|
||||||
if len(label) != 0 {
|
|
||||||
con := strings.SplitN(label, ":", 4)
|
|
||||||
mcsDelete(con[3])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var roFileLabel string
|
|
||||||
|
|
||||||
// ROFileLabel returns the specified SELinux readonly file label
|
|
||||||
func ROFileLabel() (fileLabel string) {
|
|
||||||
return roFileLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ContainerLabels returns an allocated processLabel and fileLabel to be used for
|
|
||||||
container labeling by the calling process.
|
|
||||||
*/
|
|
||||||
func ContainerLabels() (processLabel string, fileLabel string) {
|
|
||||||
var (
|
|
||||||
val, key string
|
|
||||||
bufin *bufio.Reader
|
|
||||||
)
|
|
||||||
|
|
||||||
if !GetEnabled() {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot())
|
|
||||||
in, err := os.Open(lxcPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
bufin = bufio.NewReader(in)
|
|
||||||
|
|
||||||
for done := false; !done; {
|
|
||||||
var line string
|
|
||||||
if line, err = bufin.ReadString('\n'); err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
done = true
|
|
||||||
} else {
|
|
||||||
goto exit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
if len(line) == 0 {
|
|
||||||
// Skip blank lines
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if line[0] == ';' || line[0] == '#' {
|
|
||||||
// Skip comments
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if groups := assignRegex.FindStringSubmatch(line); groups != nil {
|
|
||||||
key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2])
|
|
||||||
if key == "process" {
|
|
||||||
processLabel = strings.Trim(val, "\"")
|
|
||||||
}
|
|
||||||
if key == "file" {
|
|
||||||
fileLabel = strings.Trim(val, "\"")
|
|
||||||
}
|
|
||||||
if key == "ro_file" {
|
|
||||||
roFileLabel = strings.Trim(val, "\"")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if processLabel == "" || fileLabel == "" {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if roFileLabel == "" {
|
|
||||||
roFileLabel = fileLabel
|
|
||||||
}
|
|
||||||
exit:
|
|
||||||
mcs := uniqMcs(1024)
|
|
||||||
scon := NewContext(processLabel)
|
|
||||||
scon["level"] = mcs
|
|
||||||
processLabel = scon.Get()
|
|
||||||
scon = NewContext(fileLabel)
|
|
||||||
scon["level"] = mcs
|
|
||||||
fileLabel = scon.Get()
|
|
||||||
return processLabel, fileLabel
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecurityCheckContext validates that the SELinux label is understood by the kernel
|
|
||||||
func SecurityCheckContext(val string) error {
|
|
||||||
return writeCon(fmt.Sprintf("%s.context", selinuxPath), val)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
CopyLevel returns a label with the MLS/MCS level from src label replaces on
|
|
||||||
the dest label.
|
|
||||||
*/
|
|
||||||
func CopyLevel(src, dest string) (string, error) {
|
|
||||||
if src == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
if err := SecurityCheckContext(src); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if err := SecurityCheckContext(dest); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
scon := NewContext(src)
|
|
||||||
tcon := NewContext(dest)
|
|
||||||
mcsDelete(tcon["level"])
|
|
||||||
mcsAdd(scon["level"])
|
|
||||||
tcon["level"] = scon["level"]
|
|
||||||
return tcon.Get(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent users from relabing system files
|
|
||||||
func badPrefix(fpath string) error {
|
|
||||||
var badprefixes = []string{"/usr"}
|
|
||||||
|
|
||||||
for _, prefix := range badprefixes {
|
|
||||||
if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) {
|
|
||||||
return fmt.Errorf("relabeling content in %s is not allowed", prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chcon changes the fpath file object to the SELinux label label.
|
|
||||||
// If the fpath is a directory and recurse is true Chcon will walk the
|
|
||||||
// directory tree setting the label
|
|
||||||
func Chcon(fpath string, label string, recurse bool) error {
|
|
||||||
if label == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := badPrefix(fpath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
callback := func(p string, info os.FileInfo, err error) error {
|
|
||||||
return SetFileLabel(p, label)
|
|
||||||
}
|
|
||||||
|
|
||||||
if recurse {
|
|
||||||
return filepath.Walk(fpath, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
return SetFileLabel(fpath, label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DupSecOpt takes an SELinux process label and returns security options that
|
|
||||||
// can will set the SELinux Type and Level for future container processes
|
|
||||||
func DupSecOpt(src string) []string {
|
|
||||||
if src == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
con := NewContext(src)
|
|
||||||
if con["user"] == "" ||
|
|
||||||
con["role"] == "" ||
|
|
||||||
con["type"] == "" ||
|
|
||||||
con["level"] == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []string{"user:" + con["user"],
|
|
||||||
"role:" + con["role"],
|
|
||||||
"type:" + con["type"],
|
|
||||||
"level:" + con["level"]}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableSecOpt returns a security opt that can be used to disabling SELinux
|
|
||||||
// labeling support for future container processes
|
|
||||||
func DisableSecOpt() []string {
|
|
||||||
return []string{"disable"}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package selinux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _zero uintptr
|
|
||||||
|
|
||||||
// Returns a []byte slice if the xattr is set and nil otherwise
|
|
||||||
// Requires path and its attribute as arguments
|
|
||||||
func lgetxattr(path string, attr string) ([]byte, error) {
|
|
||||||
var sz int
|
|
||||||
pathBytes, err := syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
attrBytes, err := syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with a 128 length byte array
|
|
||||||
sz = 128
|
|
||||||
dest := make([]byte, sz)
|
|
||||||
destBytes := unsafe.Pointer(&dest[0])
|
|
||||||
_sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case errno == syscall.ENODATA:
|
|
||||||
return nil, errno
|
|
||||||
case errno == syscall.ENOTSUP:
|
|
||||||
return nil, errno
|
|
||||||
case errno == syscall.ERANGE:
|
|
||||||
// 128 byte array might just not be good enough,
|
|
||||||
// A dummy buffer is used ``uintptr(0)`` to get real size
|
|
||||||
// of the xattrs on disk
|
|
||||||
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(unsafe.Pointer(nil)), uintptr(0), 0, 0)
|
|
||||||
sz = int(_sz)
|
|
||||||
if sz < 0 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
dest = make([]byte, sz)
|
|
||||||
destBytes := unsafe.Pointer(&dest[0])
|
|
||||||
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
case errno != 0:
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
sz = int(_sz)
|
|
||||||
return dest[:sz], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lsetxattr(path string, attr string, data []byte, flags int) error {
|
|
||||||
pathBytes, err := syscall.BytePtrFromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
attrBytes, err := syscall.BytePtrFromString(attr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var dataBytes unsafe.Pointer
|
|
||||||
if len(data) > 0 {
|
|
||||||
dataBytes = unsafe.Pointer(&data[0])
|
|
||||||
} else {
|
|
||||||
dataBytes = unsafe.Pointer(&_zero)
|
|
||||||
}
|
|
||||||
_, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0)
|
|
||||||
if errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue