mirror of
				https://github.com/linuxkit/linuxkit.git
				synced 2025-10-31 03:40:16 +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:
		| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user