Files
linuxkit/src/cmd/moby/config.go
Dave Tucker af16e13672 build: Use older GCP API and support service account auth
This commit uses the older GCP API as it supports both compute and
storage. As a result, we can now use either Application Default
Credentials that are generated using the `gcloud` tool or by supplying the
service account credentials in JSON format

Signed-off-by: Dave Tucker <dt@docker.com>
2017-04-05 14:50:16 +01:00

361 lines
8.6 KiB
Go

package main
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"path"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/engine-api/types"
"github.com/opencontainers/runtime-spec/specs-go"
"gopkg.in/yaml.v2"
)
// Moby is the type of a Moby config file
type Moby struct {
Kernel struct {
Image string
Cmdline string
}
Init string
System []MobyImage
Daemon []MobyImage
Files []struct {
Path string
Contents string
}
Outputs []struct {
Format string
Project string
Bucket string
Family string
Keys string
Public bool
Replace bool
}
}
// MobyImage is the type of an image config
type MobyImage struct {
Name string
Image string
Capabilities []string
Mounts []specs.Mount
Binds []string
Tmpfs []string
Command []string
Env []string
Cwd string
Net string
Pid string
Ipc string
Uts string
Readonly bool
UID uint32 `yaml:"uid"`
GID uint32 `yaml:"gid"`
AdditionalGids []uint32 `yaml:"additionalGids"`
NoNewPrivileges bool `yaml:"noNewPrivileges"`
Hostname string
OomScoreAdj int `yaml:"oomScoreAdj"`
DisableOOMKiller bool `yaml:"disableOOMKiller"`
}
// NewConfig parses a config file
func NewConfig(config []byte) (*Moby, error) {
m := Moby{}
err := yaml.Unmarshal(config, &m)
if err != nil {
return &m, err
}
return &m, nil
}
// ConfigToOCI converts a config specification to an OCI config file
func ConfigToOCI(image *MobyImage) ([]byte, error) {
// TODO pass through same docker client to all functions
cli, err := dockerClient()
if err != nil {
return []byte{}, err
}
inspect, err := dockerInspectImage(cli, image.Image)
if err != nil {
return []byte{}, err
}
return ConfigInspectToOCI(image, inspect)
}
// ConfigInspectToOCI converts a config and the output of image inspect to an OCI config file
func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, error) {
oci := specs.Spec{}
config := inspect.Config
if config == nil {
return []byte{}, errors.New("empty image config")
}
args := append(config.Entrypoint, config.Cmd...)
if len(image.Command) != 0 {
args = image.Command
}
env := config.Env
if len(image.Env) != 0 {
env = image.Env
}
cwd := config.WorkingDir
if image.Cwd != "" {
cwd = image.Cwd
}
if cwd == "" {
cwd = "/"
}
procOptions := []string{"nosuid", "nodev", "noexec", "relatime"}
devOptions := []string{"nosuid", "strictatime", "mode=755", "size=65536k"}
if image.Readonly {
devOptions = append(devOptions, "ro")
}
ptsOptions := []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}
sysOptions := []string{"nosuid", "noexec", "nodev"}
if image.Readonly {
sysOptions = append(sysOptions, "ro")
}
cgroupOptions := []string{"nosuid", "noexec", "nodev", "relatime", "ro"}
// note omits "standard" /dev/shm and /dev/mqueue
mounts := []specs.Mount{
{Destination: "/proc", Type: "proc", Source: "proc", Options: procOptions},
{Destination: "/dev", Type: "tmpfs", Source: "tmpfs", Options: devOptions},
{Destination: "/dev/pts", Type: "devpts", Source: "devpts", Options: ptsOptions},
{Destination: "/sys", Type: "sysfs", Source: "sysfs", Options: sysOptions},
{Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", Options: cgroupOptions},
}
// TODO if any standard mount points supplied, remove from above, so can change options
mounts = append(mounts, image.Mounts...)
for _, t := range image.Tmpfs {
parts := strings.Split(t, ":")
if len(parts) > 2 {
return []byte{}, fmt.Errorf("Cannot parse tmpfs, too many ':': %s", t)
}
dest := parts[0]
opts := []string{}
if len(parts) == 2 {
opts = strings.Split(parts[2], ",")
}
mounts = append(mounts, specs.Mount{Destination: dest, Type: "tmpfs", Source: "tmpfs", Options: opts})
}
for _, b := range image.Binds {
parts := strings.Split(b, ":")
if len(parts) < 2 {
return []byte{}, fmt.Errorf("Cannot parse bind, missing ':': %s", b)
}
if len(parts) > 3 {
return []byte{}, fmt.Errorf("Cannot parse bind, too many ':': %s", b)
}
src := parts[0]
dest := parts[1]
opts := []string{"rw", "rbind", "rprivate"}
if len(parts) == 3 {
opts = strings.Split(parts[2], ",")
}
mounts = append(mounts, specs.Mount{Destination: dest, Type: "bind", Source: src, Options: opts})
}
namespaces := []specs.LinuxNamespace{}
if image.Net != "" && image.Net != "host" {
return []byte{}, fmt.Errorf("invalid net namespace: %s", image.Net)
}
if image.Net == "" {
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace})
}
if image.Pid != "" && image.Pid != "host" {
return []byte{}, fmt.Errorf("invalid pid namespace: %s", image.Pid)
}
if image.Pid == "" {
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.PIDNamespace})
}
if image.Ipc != "" && image.Ipc != "host" {
return []byte{}, fmt.Errorf("invalid ipc namespace: %s", image.Ipc)
}
if image.Ipc == "" {
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.IPCNamespace})
}
if image.Uts != "" && image.Uts != "host" {
return []byte{}, fmt.Errorf("invalid uts namespace: %s", image.Uts)
}
if image.Uts == "" {
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UTSNamespace})
}
// TODO user, cgroup namespaces, maybe mount=host if useful
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.MountNamespace})
caps := image.Capabilities
if len(caps) == 1 && strings.ToLower(caps[0]) == "all" {
caps = []string{
"CAP_AUDIT_CONTROL",
"CAP_AUDIT_READ",
"CAP_AUDIT_WRITE",
"CAP_BLOCK_SUSPEND",
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_DAC_READ_SEARCH",
"CAP_FOWNER",
"CAP_FSETID",
"CAP_IPC_LOCK",
"CAP_IPC_OWNER",
"CAP_KILL",
"CAP_LEASE",
"CAP_LINUX_IMMUTABLE",
"CAP_MAC_ADMIN",
"CAP_MAC_OVERRIDE",
"CAP_MKNOD",
"CAP_NET_ADMIN",
"CAP_NET_BIND_SERVICE",
"CAP_NET_BROADCAST",
"CAP_NET_RAW",
"CAP_SETFCAP",
"CAP_SETGID",
"CAP_SETPCAP",
"CAP_SETUID",
"CAP_SYSLOG",
"CAP_SYS_ADMIN",
"CAP_SYS_BOOT",
"CAP_SYS_CHROOT",
"CAP_SYS_MODULE",
"CAP_SYS_NICE",
"CAP_SYS_PACCT",
"CAP_SYS_PTRACE",
"CAP_SYS_RAWIO",
"CAP_SYS_RESOURCE",
"CAP_SYS_TIME",
"CAP_SYS_TTY_CONFIG",
"CAP_WAKE_ALARM",
}
}
oci.Version = specs.Version
oci.Platform = specs.Platform{
OS: inspect.Os,
Arch: inspect.Architecture,
}
oci.Process = specs.Process{
Terminal: false,
//ConsoleSize
User: specs.User{
UID: image.UID,
GID: image.GID,
AdditionalGids: image.AdditionalGids,
// Username (Windows)
},
Args: args,
Env: env,
Cwd: cwd,
Capabilities: &specs.LinuxCapabilities{
Bounding: caps,
Effective: caps,
Inheritable: caps,
Permitted: caps,
Ambient: []string{},
},
Rlimits: []specs.LinuxRlimit{},
NoNewPrivileges: image.NoNewPrivileges,
// ApparmorProfile
// SelinuxLabel
}
oci.Root = specs.Root{
Path: "rootfs",
Readonly: image.Readonly,
}
oci.Hostname = image.Hostname
oci.Mounts = mounts
oci.Linux = &specs.Linux{
// UIDMappings
// GIDMappings
// Sysctl
Resources: &specs.LinuxResources{
// Devices
DisableOOMKiller: &image.DisableOOMKiller,
// Memory
// CPU
// Pids
// BlockIO
// HugepageLimits
// Network
},
// CgroupsPath
Namespaces: namespaces,
// Devices
// Seccomp
// RootfsPropagation
// MaskedPaths
// ReadonlyPaths
// MountLabel
// IntelRdt
}
return json.MarshalIndent(oci, "", " ")
}
func filesystem(m *Moby) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
log.Infof("Add files:")
for _, f := range m.Files {
log.Infof(" %s", f.Path)
if f.Path == "" {
return buf, errors.New("Did not specify path for file")
}
if f.Contents == "" {
return buf, errors.New("Contents of file not specified")
}
// we need all the leading directories
parts := strings.Split(path.Dir(f.Path), "/")
root := ""
for _, p := range parts {
if p == "." || p == "/" {
continue
}
if root == "" {
root = p
} else {
root = root + "/" + p
}
hdr := &tar.Header{
Name: root,
Typeflag: tar.TypeDir,
Mode: 0700,
}
err := tw.WriteHeader(hdr)
if err != nil {
return buf, err
}
}
hdr := &tar.Header{
Name: f.Path,
Mode: 0600,
Size: int64(len(f.Contents)),
}
err := tw.WriteHeader(hdr)
if err != nil {
return buf, err
}
_, err = tw.Write([]byte(f.Contents))
if err != nil {
return buf, err
}
}
return buf, nil
}