mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-21 18:11:35 +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)
|
log.Infof(" Create OCI config for %s", image.Image)
|
||||||
config, err := ConfigToOCI(&image)
|
config, err := ConfigToOCI(&image)
|
||||||
if err != nil {
|
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)
|
so := fmt.Sprintf("%03d", i)
|
||||||
path := "containers/system/" + so + "-" + image.Name
|
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)
|
log.Infof(" Create OCI config for %s", image.Image)
|
||||||
config, err := ConfigToOCI(&image)
|
config, err := ConfigToOCI(&image)
|
||||||
if err != nil {
|
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
|
path := "containers/daemon/" + image.Name
|
||||||
out, err := ImageBundle(path, image.Image, config)
|
out, err := ImageBundle(path, image.Image, config)
|
||||||
|
@ -3,13 +3,14 @@ package main
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"gopkg.in/yaml.v2"
|
"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 {
|
type MobyImage struct {
|
||||||
Name string
|
Name string
|
||||||
Image string
|
Image string
|
||||||
Capabilities []string
|
Capabilities []string
|
||||||
|
Mounts []specs.Mount
|
||||||
Binds []string
|
Binds []string
|
||||||
OomScoreAdj int64 `yaml:"oom_score_adj"`
|
Tmpfs []string
|
||||||
Command []string
|
Args []string
|
||||||
NetworkMode string `yaml:"network_mode"`
|
Env []string
|
||||||
|
Cwd string
|
||||||
|
Net string
|
||||||
Pid string
|
Pid string
|
||||||
Ipc string
|
Ipc string
|
||||||
Uts string
|
Uts string
|
||||||
ReadOnly bool `yaml:"read_only"`
|
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
|
// NewConfig parses a config file
|
||||||
func NewConfig(config []byte) (*Moby, error) {
|
func NewConfig(config []byte) (*Moby, error) {
|
||||||
m := Moby{}
|
m := Moby{}
|
||||||
@ -66,53 +75,183 @@ func NewConfig(config []byte) (*Moby, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConfigToOCI converts a config specification to an OCI config file
|
// ConfigToOCI converts a config specification to an OCI config file
|
||||||
func ConfigToOCI(image *MobyImage) (string, error) {
|
func ConfigToOCI(image *MobyImage) ([]byte, error) {
|
||||||
// riddler arguments
|
oci := specs.Spec{}
|
||||||
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...)
|
|
||||||
|
|
||||||
config, err := dockerRun(args...)
|
// TODO pass through same docker client to all functions
|
||||||
|
cli, err := dockerClient()
|
||||||
if err != nil {
|
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) {
|
func filesystem(m *Moby) (*bytes.Buffer, error) {
|
||||||
|
@ -8,10 +8,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
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) {
|
func dockerRun(args ...string) ([]byte, error) {
|
||||||
@ -274,3 +278,36 @@ func dockerPull(image string) error {
|
|||||||
log.Debugf("docker pull: %s...Done", image)
|
log.Debugf("docker pull: %s...Done", image)
|
||||||
return nil
|
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
|
// 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) {
|
func ImageBundle(path string, image string, config []byte) ([]byte, error) {
|
||||||
log.Debugf("image bundle: %s %s cfg: %s", path, image, config)
|
log.Debugf("image bundle: %s %s cfg: %s", path, image, string(config))
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
tw := tar.NewWriter(out)
|
tw := tar.NewWriter(out)
|
||||||
err := tarPrefix(path+"/rootfs/", tw)
|
err := tarPrefix(path+"/rootfs/", tw)
|
||||||
@ -160,7 +160,7 @@ func ImageBundle(path, image, config string) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
buf := bytes.NewBufferString(config)
|
buf := bytes.NewBuffer(config)
|
||||||
_, err = io.Copy(tw, buf)
|
_, err = io.Copy(tw, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
|
Loading…
Reference in New Issue
Block a user