mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 01:29:07 +00:00
Replace riddler with code that constructs config.json directly
Generated largely from the specified config; small parts taken from `docker image inspect`, such as the command line. Renamed some of the yaml keys to match the OCI spec rather than Docker Compose as we decided they are more readable, no more underscores. Add some extra functionality - tmpfs specification - fully general mount specification - no new privileges can be specified now For nostalgic reasons, using engine-api to talk to the docker cli as we only need an old API version, and it is nice and easy to vendor... Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
parent
1477639e09
commit
d293eeadf6
@ -117,7 +117,7 @@ func buildInternal(name string, pull bool, conf string) {
|
||||
log.Infof(" Create OCI config for %s", image.Image)
|
||||
config, err := ConfigToOCI(&image)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to run riddler to get config.json for %s: %v", image.Image, err)
|
||||
log.Fatalf("Failed to create config.json for %s: %v", image.Image, err)
|
||||
}
|
||||
so := fmt.Sprintf("%03d", i)
|
||||
path := "containers/system/" + so + "-" + image.Name
|
||||
@ -141,7 +141,7 @@ func buildInternal(name string, pull bool, conf string) {
|
||||
log.Infof(" Create OCI config for %s", image.Image)
|
||||
config, err := ConfigToOCI(&image)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to run riddler to get config.json for %s: %v", image.Image, err)
|
||||
log.Fatalf("Failed to create config.json for %s: %v", image.Image, err)
|
||||
}
|
||||
path := "containers/daemon/" + image.Name
|
||||
out, err := ImageBundle(path, image.Image, config)
|
||||
|
@ -3,13 +3,14 @@ package main
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@ -36,23 +37,31 @@ type Moby struct {
|
||||
}
|
||||
}
|
||||
|
||||
// MobyImage is the type of an image config, based on Compose
|
||||
// MobyImage is the type of an image config
|
||||
type MobyImage struct {
|
||||
Name string
|
||||
Image string
|
||||
Capabilities []string
|
||||
Binds []string
|
||||
OomScoreAdj int64 `yaml:"oom_score_adj"`
|
||||
Command []string
|
||||
NetworkMode string `yaml:"network_mode"`
|
||||
Pid string
|
||||
Ipc string
|
||||
Uts string
|
||||
ReadOnly bool `yaml:"read_only"`
|
||||
Name string
|
||||
Image string
|
||||
Capabilities []string
|
||||
Mounts []specs.Mount
|
||||
Binds []string
|
||||
Tmpfs []string
|
||||
Args []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"`
|
||||
}
|
||||
|
||||
const riddler = "mobylinux/riddler:decf6c9e24b579175a038a76f9721e7aca507abd@sha256:9d24a7c48204b94b5d76cc3d6cf70f779d87d08d8a893169292c98d0e19ab579"
|
||||
|
||||
// NewConfig parses a config file
|
||||
func NewConfig(config []byte) (*Moby, error) {
|
||||
m := Moby{}
|
||||
@ -66,53 +75,183 @@ func NewConfig(config []byte) (*Moby, error) {
|
||||
}
|
||||
|
||||
// ConfigToOCI converts a config specification to an OCI config file
|
||||
func ConfigToOCI(image *MobyImage) (string, error) {
|
||||
// riddler arguments
|
||||
args := []string{"-v", "/var/run/docker.sock:/var/run/docker.sock", riddler, image.Image}
|
||||
// docker arguments
|
||||
args = append(args, "--cap-drop", "all")
|
||||
for _, cap := range image.Capabilities {
|
||||
if strings.ToUpper(cap)[0:4] == "CAP_" {
|
||||
cap = cap[4:]
|
||||
}
|
||||
args = append(args, "--cap-add", cap)
|
||||
}
|
||||
if image.OomScoreAdj != 0 {
|
||||
args = append(args, "--oom-score-adj", strconv.FormatInt(image.OomScoreAdj, 10))
|
||||
}
|
||||
if image.NetworkMode != "" {
|
||||
// TODO only "host" supported
|
||||
args = append(args, "--net="+image.NetworkMode)
|
||||
}
|
||||
if image.Pid != "" {
|
||||
// TODO only "host" supported
|
||||
args = append(args, "--pid="+image.Pid)
|
||||
}
|
||||
if image.Ipc != "" {
|
||||
// TODO only "host" supported
|
||||
args = append(args, "--ipc="+image.Ipc)
|
||||
}
|
||||
if image.Uts != "" {
|
||||
// TODO only "host" supported
|
||||
args = append(args, "--uts="+image.Uts)
|
||||
}
|
||||
for _, bind := range image.Binds {
|
||||
args = append(args, "-v", bind)
|
||||
}
|
||||
if image.ReadOnly {
|
||||
args = append(args, "--read-only")
|
||||
}
|
||||
// image
|
||||
args = append(args, image.Image)
|
||||
// command
|
||||
args = append(args, image.Command...)
|
||||
func ConfigToOCI(image *MobyImage) ([]byte, error) {
|
||||
oci := specs.Spec{}
|
||||
|
||||
config, err := dockerRun(args...)
|
||||
// TODO pass through same docker client to all functions
|
||||
cli, err := dockerClient()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to run riddler to get config.json: %v", err)
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
return string(config), nil
|
||||
inspect, err := dockerInspectImage(cli, image.Image)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
config := inspect.Config
|
||||
if config == nil {
|
||||
return []byte{}, errors.New("empty image config")
|
||||
}
|
||||
|
||||
args := append(config.Entrypoint, config.Cmd...)
|
||||
if len(image.Args) != 0 {
|
||||
args = image.Args
|
||||
}
|
||||
env := config.Env
|
||||
if len(image.Env) != 0 {
|
||||
env = image.Env
|
||||
}
|
||||
cwd := config.WorkingDir
|
||||
if image.Cwd != "" {
|
||||
cwd = image.Cwd
|
||||
}
|
||||
if cwd == "" {
|
||||
cwd = "/"
|
||||
}
|
||||
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"},
|
||||
{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})
|
||||
|
||||
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: image.Capabilities,
|
||||
Effective: image.Capabilities,
|
||||
Inheritable: image.Capabilities,
|
||||
Permitted: image.Capabilities,
|
||||
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) {
|
||||
|
@ -8,10 +8,14 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func dockerRun(args ...string) ([]byte, error) {
|
||||
@ -274,3 +278,36 @@ func dockerPull(image string) error {
|
||||
log.Debugf("docker pull: %s...Done", image)
|
||||
return nil
|
||||
}
|
||||
|
||||
func dockerClient() (*client.Client, error) {
|
||||
// for maximum compatibility as we use nothing new
|
||||
err := os.Setenv("DOCKER_API_VERSION", "1.23")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client.NewEnvClient()
|
||||
}
|
||||
|
||||
func dockerInspectImage(cli *client.Client, image string) (types.ImageInspect, error) {
|
||||
log.Debugf("docker inspect image: %s", image)
|
||||
|
||||
inspect, _, err := cli.ImageInspectWithRaw(context.Background(), image, false)
|
||||
if err != nil {
|
||||
if client.IsErrImageNotFound(err) {
|
||||
pullErr := dockerPull(image)
|
||||
if pullErr != nil {
|
||||
return types.ImageInspect{}, pullErr
|
||||
}
|
||||
inspect, _, err = cli.ImageInspectWithRaw(context.Background(), image, false)
|
||||
if err != nil {
|
||||
return types.ImageInspect{}, err
|
||||
}
|
||||
} else {
|
||||
return types.ImageInspect{}, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("docker inspect image: %s...Done", image)
|
||||
|
||||
return inspect, nil
|
||||
}
|
||||
|
@ -143,8 +143,8 @@ func imageTar(image, prefix string, tw *tar.Writer) error {
|
||||
}
|
||||
|
||||
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
|
||||
func ImageBundle(path, image, config string) ([]byte, error) {
|
||||
log.Debugf("image bundle: %s %s cfg: %s", path, image, config)
|
||||
func ImageBundle(path string, image string, config []byte) ([]byte, error) {
|
||||
log.Debugf("image bundle: %s %s cfg: %s", path, image, string(config))
|
||||
out := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(out)
|
||||
err := tarPrefix(path+"/rootfs/", tw)
|
||||
@ -160,7 +160,7 @@ func ImageBundle(path, image, config string) ([]byte, error) {
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
buf := bytes.NewBufferString(config)
|
||||
buf := bytes.NewBuffer(config)
|
||||
_, err = io.Copy(tw, buf)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
|
Loading…
Reference in New Issue
Block a user