mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-23 11:00:25 +00:00
Add support for override of parameters using a label
Using the label `org.mobyproject.config` will use that JSON (or yaml, but it is very hard to get yaml into a label as newlines are not respected) for parameters that are not explicitly set in the yaml file. Had to change parameter definitions so override behaves as expected. fix #16 Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
parent
df7f50a755
commit
c734b47e9c
3
Makefile
3
Makefile
@ -9,6 +9,7 @@ PREFIX?=/usr/local
|
|||||||
moby: $(DEPS) lint
|
moby: $(DEPS) lint
|
||||||
go build --ldflags "-X main.GitCommit=$(GIT_COMMIT) -X main.Version=$(VERSION)" -o $@ github.com/moby/tool/cmd/moby
|
go build --ldflags "-X main.GitCommit=$(GIT_COMMIT) -X main.Version=$(VERSION)" -o $@ github.com/moby/tool/cmd/moby
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
@echo "+ $@: golint, gofmt, go vet"
|
@echo "+ $@: golint, gofmt, go vet"
|
||||||
# golint
|
# golint
|
||||||
@ -17,6 +18,8 @@ lint:
|
|||||||
@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
|
@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
|
||||||
# govet
|
# govet
|
||||||
@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
|
@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
|
||||||
|
# go test
|
||||||
|
@go test github.com/moby/tool/cmd/moby
|
||||||
|
|
||||||
test: moby
|
test: moby
|
||||||
./moby build test/test.yml
|
./moby build test/test.yml
|
||||||
|
@ -125,7 +125,7 @@ func enforceContentTrust(fullImageName string, config *TrustConfig) bool {
|
|||||||
|
|
||||||
// Perform the actual build process
|
// Perform the actual build process
|
||||||
// TODO return error not panic
|
// TODO return error not panic
|
||||||
func buildInternal(m *Moby, name string, pull bool) []byte {
|
func buildInternal(m Moby, name string, pull bool) []byte {
|
||||||
w := new(bytes.Buffer)
|
w := new(bytes.Buffer)
|
||||||
iw := tar.NewWriter(w)
|
iw := tar.NewWriter(w)
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ func buildInternal(m *Moby, name string, pull bool) []byte {
|
|||||||
}
|
}
|
||||||
for i, image := range m.Onboot {
|
for i, image := range m.Onboot {
|
||||||
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 create config.json for %s: %v", image.Image, err)
|
log.Fatalf("Failed to create config.json for %s: %v", image.Image, err)
|
||||||
}
|
}
|
||||||
@ -196,7 +196,7 @@ func buildInternal(m *Moby, name string, pull bool) []byte {
|
|||||||
}
|
}
|
||||||
for _, image := range m.Services {
|
for _, image := range m.Services {
|
||||||
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 create config.json for %s: %v", image.Image, err)
|
log.Fatalf("Failed to create config.json for %s: %v", image.Image, err)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/xeipuuv/gojsonschema"
|
"github.com/xeipuuv/gojsonschema"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@ -48,32 +49,32 @@ type TrustConfig struct {
|
|||||||
|
|
||||||
// MobyImage is the type of an image config
|
// MobyImage is the type of an image config
|
||||||
type MobyImage struct {
|
type MobyImage struct {
|
||||||
Name string
|
Name string `yaml:"name" json:"name"`
|
||||||
Image string
|
Image string `yaml:"image" json:"image"`
|
||||||
Capabilities []string
|
Capabilities *[]string `yaml:"capabilities" json:"capabilities,omitempty"`
|
||||||
Mounts []specs.Mount
|
Mounts *[]specs.Mount `yaml:"mounts" json:"mounts,omitempty"`
|
||||||
Binds []string
|
Binds *[]string `yaml:"binds" json:"binds,omitempty"`
|
||||||
Tmpfs []string
|
Tmpfs *[]string `yaml:"tmpfs" json:"tmpfs,omitempty"`
|
||||||
Command []string
|
Command *[]string `yaml:"command" json:"command,omitempty"`
|
||||||
Env []string
|
Env *[]string `yaml:"env" json:"env,omitempty"`
|
||||||
Cwd string
|
Cwd string `yaml:"cwd" json:"cwd"`
|
||||||
Net string
|
Net string `yaml:"net" json:"net"`
|
||||||
Pid string
|
Pid string `yaml:"pid" json:"pid"`
|
||||||
Ipc string
|
Ipc string `yaml:"ipc" json:"ipc"`
|
||||||
Uts string
|
Uts string `yaml:"uts" json:"uts"`
|
||||||
Readonly bool
|
Hostname string `yaml:"hostname" json:"hostname"`
|
||||||
MaskedPaths []string `yaml:"maskedPaths"`
|
Readonly *bool `yaml:"readonly" json:"readonly,omitempty"`
|
||||||
ReadonlyPaths []string `yaml:"readonlyPaths"`
|
MaskedPaths *[]string `yaml:"maskedPaths" json:"maskedPaths,omitempty"`
|
||||||
UID uint32 `yaml:"uid"`
|
ReadonlyPaths *[]string `yaml:"readonlyPaths" json:"readonlyPaths,omitempty"`
|
||||||
GID uint32 `yaml:"gid"`
|
UID *uint32 `yaml:"uid" json:"uid,omitempty"`
|
||||||
AdditionalGids []uint32 `yaml:"additionalGids"`
|
GID *uint32 `yaml:"gid" json:"gid,omitempty"`
|
||||||
NoNewPrivileges bool `yaml:"noNewPrivileges"`
|
AdditionalGids *[]uint32 `yaml:"additionalGids" json:"additionalGids,omitempty"`
|
||||||
Hostname string
|
NoNewPrivileges *bool `yaml:"noNewPrivileges" json:"noNewPrivileges,omitempty"`
|
||||||
OomScoreAdj int `yaml:"oomScoreAdj"`
|
OOMScoreAdj *int `yaml:"oomScoreAdj" json:"oomScoreAdj,omitempty"`
|
||||||
DisableOOMKiller bool `yaml:"disableOOMKiller"`
|
DisableOOMKiller *bool `yaml:"disableOOMKiller" json:"disableOOMKiller,omitempty"`
|
||||||
RootfsPropagation string `yaml:"rootfsPropagation"`
|
RootfsPropagation *string `yaml:"rootfsPropagation" json:"rootfsPropagation,omitempty"`
|
||||||
CgroupsPath string `yaml:"cgroupsPath"`
|
CgroupsPath *string `yaml:"cgroupsPath" json:"cgroupsPath,omitempty"`
|
||||||
Sysctl map[string]string
|
Sysctl *map[string]string `yaml:"sysctl" json:"sysctl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// github.com/go-yaml/yaml treats map keys as interface{} while encoding/json
|
// github.com/go-yaml/yaml treats map keys as interface{} while encoding/json
|
||||||
@ -98,14 +99,14 @@ func convert(i interface{}) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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{}
|
||||||
|
|
||||||
// Parse raw yaml
|
// Parse raw yaml
|
||||||
var rawYaml interface{}
|
var rawYaml interface{}
|
||||||
err := yaml.Unmarshal(config, &rawYaml)
|
err := yaml.Unmarshal(config, &rawYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to raw JSON
|
// Convert to raw JSON
|
||||||
@ -116,27 +117,93 @@ func NewConfig(config []byte) (*Moby, error) {
|
|||||||
documentLoader := gojsonschema.NewGoLoader(rawJSON)
|
documentLoader := gojsonschema.NewGoLoader(rawJSON)
|
||||||
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &m, err
|
return m, err
|
||||||
}
|
}
|
||||||
if !result.Valid() {
|
if !result.Valid() {
|
||||||
fmt.Printf("The configuration file is invalid:\n")
|
fmt.Printf("The configuration file is invalid:\n")
|
||||||
for _, desc := range result.Errors() {
|
for _, desc := range result.Errors() {
|
||||||
fmt.Printf("- %s\n", desc)
|
fmt.Printf("- %s\n", desc)
|
||||||
}
|
}
|
||||||
return &m, fmt.Errorf("invalid configuration file")
|
return m, fmt.Errorf("invalid configuration file")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse yaml
|
// Parse yaml
|
||||||
err = yaml.Unmarshal(config, &m)
|
err = yaml.Unmarshal(config, &m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &m, nil
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImage validates an parses yaml or json for a MobyImage
|
||||||
|
func NewImage(config []byte) (MobyImage, error) {
|
||||||
|
log.Debugf("Reading label config: %s", string(config))
|
||||||
|
|
||||||
|
mi := MobyImage{}
|
||||||
|
|
||||||
|
// Parse raw yaml
|
||||||
|
var rawYaml interface{}
|
||||||
|
err := yaml.Unmarshal(config, &rawYaml)
|
||||||
|
if err != nil {
|
||||||
|
return mi, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to raw JSON
|
||||||
|
rawJSON := convert(rawYaml)
|
||||||
|
|
||||||
|
// check it is an object not an array
|
||||||
|
jsonObject, ok := rawJSON.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return mi, fmt.Errorf("JSON is an array not an object: %s", string(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a dummy name and image to pass validation
|
||||||
|
var dummyName interface{}
|
||||||
|
var dummyImage interface{}
|
||||||
|
dummyName = "dummyname"
|
||||||
|
dummyImage = "dummyimage"
|
||||||
|
jsonObject["name"] = dummyName
|
||||||
|
jsonObject["image"] = dummyImage
|
||||||
|
|
||||||
|
// Validate it as {"services": [config]}
|
||||||
|
var services [1]interface{}
|
||||||
|
services[0] = rawJSON
|
||||||
|
serviceJSON := map[string]interface{}{"services": services}
|
||||||
|
|
||||||
|
// Validate serviceJSON with JSON schema
|
||||||
|
schemaLoader := gojsonschema.NewStringLoader(schema)
|
||||||
|
documentLoader := gojsonschema.NewGoLoader(serviceJSON)
|
||||||
|
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
||||||
|
if err != nil {
|
||||||
|
return mi, err
|
||||||
|
}
|
||||||
|
if !result.Valid() {
|
||||||
|
fmt.Printf("The org.mobyproject.config label is invalid:\n")
|
||||||
|
for _, desc := range result.Errors() {
|
||||||
|
fmt.Printf("- %s\n", desc)
|
||||||
|
}
|
||||||
|
return mi, fmt.Errorf("invalid configuration label")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse yaml
|
||||||
|
err = yaml.Unmarshal(config, &mi)
|
||||||
|
if err != nil {
|
||||||
|
return mi, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if mi.Name != "" {
|
||||||
|
return mi, fmt.Errorf("name cannot be set in metadata label")
|
||||||
|
}
|
||||||
|
if mi.Image != "" {
|
||||||
|
return mi, fmt.Errorf("image cannot be set in metadata label")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigToOCI converts a config specification to an OCI config file
|
// ConfigToOCI converts a config specification to an OCI config file
|
||||||
func ConfigToOCI(image *MobyImage) ([]byte, error) {
|
func ConfigToOCI(image MobyImage) ([]byte, error) {
|
||||||
|
|
||||||
// TODO pass through same docker client to all functions
|
// TODO pass through same docker client to all functions
|
||||||
cli, err := dockerClient()
|
cli, err := dockerClient()
|
||||||
@ -149,7 +216,12 @@ func ConfigToOCI(image *MobyImage) ([]byte, error) {
|
|||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConfigInspectToOCI(image, inspect)
|
oci, err := ConfigInspectToOCI(image, inspect)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.MarshalIndent(oci, "", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultMountpoint(tp string) string {
|
func defaultMountpoint(tp string) string {
|
||||||
@ -185,39 +257,192 @@ func (m mlist) parts(i int) int {
|
|||||||
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
|
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigInspectToOCI converts a config and the output of image inspect to an OCI config file
|
// assignBool does ordered overrides from JSON bool pointers
|
||||||
func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, error) {
|
func assignBool(v1, v2 *bool) bool {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignBoolPtr does ordered overrides from JSON bool pointers
|
||||||
|
func assignBoolPtr(v1, v2 *bool) *bool {
|
||||||
|
if v2 != nil {
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignIntPtr does ordered overrides from JSON int pointers
|
||||||
|
func assignIntPtr(v1, v2 *int) *int {
|
||||||
|
if v2 != nil {
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignUint32 does ordered overrides from JSON uint32 pointers
|
||||||
|
func assignUint32(v1, v2 *uint32) uint32 {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignUint32Array does ordered overrides from JSON uint32 array pointers
|
||||||
|
func assignUint32Array(v1, v2 *[]uint32) []uint32 {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return []uint32{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignStrings does ordered overrides from JSON string array pointers
|
||||||
|
func assignStrings(v1, v2 *[]string) []string {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignStrings3 does ordered overrides from JSON string array pointers
|
||||||
|
func assignStrings3(v1 []string, v2, v3 *[]string) []string {
|
||||||
|
if v3 != nil {
|
||||||
|
return *v3
|
||||||
|
}
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignMaps does ordered overrides from JSON string map pointers
|
||||||
|
func assignMaps(v1, v2 *map[string]string) map[string]string {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignBinds does ordered overrides from JSON Bind array pointers
|
||||||
|
func assignBinds(v1, v2 *[]specs.Mount) []specs.Mount {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return []specs.Mount{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignString does ordered overrides from JSON string pointers
|
||||||
|
func assignString(v1, v2 *string) string {
|
||||||
|
if v2 != nil {
|
||||||
|
return *v2
|
||||||
|
}
|
||||||
|
if v1 != nil {
|
||||||
|
return *v1
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// assignStringEmpty does ordered overrides if strings are empty, for
|
||||||
|
// values where there is always an explicit override eg "none"
|
||||||
|
func assignStringEmpty(v1, v2 string) string {
|
||||||
|
if v2 != "" {
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign StringEmpty4 does ordered overrides if strings are empty, for
|
||||||
|
// values where there is always an explicit override eg "none"
|
||||||
|
func assignStringEmpty4(v1, v2, v3, v4 string) string {
|
||||||
|
if v4 != "" {
|
||||||
|
return v4
|
||||||
|
}
|
||||||
|
if v3 != "" {
|
||||||
|
return v3
|
||||||
|
}
|
||||||
|
if v2 != "" {
|
||||||
|
return v2
|
||||||
|
}
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
// emptyNone replaces "none" with the empty string
|
||||||
|
func emptyNone(v string) string {
|
||||||
|
if v == "none" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigInspectToOCI converts a config and the output of image inspect to an OCI config
|
||||||
|
func ConfigInspectToOCI(yaml MobyImage, inspect types.ImageInspect) (specs.Spec, error) {
|
||||||
oci := specs.Spec{}
|
oci := specs.Spec{}
|
||||||
|
|
||||||
config := inspect.Config
|
var inspectConfig container.Config
|
||||||
if config == nil {
|
if inspect.Config != nil {
|
||||||
return []byte{}, errors.New("empty image config")
|
inspectConfig = *inspect.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
args := append(config.Entrypoint, config.Cmd...)
|
// look for org.mobyproject.config label
|
||||||
if len(image.Command) != 0 {
|
var label MobyImage
|
||||||
args = image.Command
|
labelString := inspectConfig.Labels["org.mobyproject.config"]
|
||||||
|
if labelString != "" {
|
||||||
|
var err error
|
||||||
|
label, err = NewImage([]byte(labelString))
|
||||||
|
if err != nil {
|
||||||
|
return oci, err
|
||||||
}
|
}
|
||||||
env := config.Env
|
|
||||||
if len(image.Env) != 0 {
|
|
||||||
env = image.Env
|
|
||||||
}
|
|
||||||
cwd := config.WorkingDir
|
|
||||||
if image.Cwd != "" {
|
|
||||||
cwd = image.Cwd
|
|
||||||
}
|
|
||||||
if cwd == "" {
|
|
||||||
cwd = "/"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// command, env and cwd can be taken from image, as they are commonly specified in Dockerfile
|
||||||
|
|
||||||
|
// TODO we could handle entrypoint and cmd independently more like Docker
|
||||||
|
inspectCommand := append(inspectConfig.Entrypoint, inspect.Config.Cmd...)
|
||||||
|
args := assignStrings3(inspectCommand, label.Command, yaml.Command)
|
||||||
|
|
||||||
|
env := assignStrings3(inspectConfig.Env, label.Env, yaml.Env)
|
||||||
|
|
||||||
|
// empty Cwd not allowed in OCI, must be / in that case
|
||||||
|
cwd := assignStringEmpty4("/", inspectConfig.WorkingDir, label.Cwd, yaml.Cwd)
|
||||||
|
|
||||||
|
// the other options will never be in the image config, but may be in label or yaml
|
||||||
|
|
||||||
|
readonly := assignBool(label.Readonly, yaml.Readonly)
|
||||||
|
|
||||||
// default options match what Docker does
|
// default options match what Docker does
|
||||||
procOptions := []string{"nosuid", "nodev", "noexec", "relatime"}
|
procOptions := []string{"nosuid", "nodev", "noexec", "relatime"}
|
||||||
devOptions := []string{"nosuid", "strictatime", "mode=755", "size=65536k"}
|
devOptions := []string{"nosuid", "strictatime", "mode=755", "size=65536k"}
|
||||||
if image.Readonly {
|
if readonly {
|
||||||
devOptions = append(devOptions, "ro")
|
devOptions = append(devOptions, "ro")
|
||||||
}
|
}
|
||||||
ptsOptions := []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}
|
ptsOptions := []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}
|
||||||
sysOptions := []string{"nosuid", "noexec", "nodev"}
|
sysOptions := []string{"nosuid", "noexec", "nodev"}
|
||||||
if image.Readonly {
|
if readonly {
|
||||||
sysOptions = append(sysOptions, "ro")
|
sysOptions = append(sysOptions, "ro")
|
||||||
}
|
}
|
||||||
cgroupOptions := []string{"nosuid", "noexec", "nodev", "relatime", "ro"}
|
cgroupOptions := []string{"nosuid", "noexec", "nodev", "relatime", "ro"}
|
||||||
@ -229,10 +454,10 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
"/sys": {Destination: "/sys", Type: "sysfs", Source: "sysfs", Options: sysOptions},
|
"/sys": {Destination: "/sys", Type: "sysfs", Source: "sysfs", Options: sysOptions},
|
||||||
"/sys/fs/cgroup": {Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", Options: cgroupOptions},
|
"/sys/fs/cgroup": {Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", Options: cgroupOptions},
|
||||||
}
|
}
|
||||||
for _, t := range image.Tmpfs {
|
for _, t := range assignStrings(label.Tmpfs, yaml.Tmpfs) {
|
||||||
parts := strings.Split(t, ":")
|
parts := strings.Split(t, ":")
|
||||||
if len(parts) > 2 {
|
if len(parts) > 2 {
|
||||||
return []byte{}, fmt.Errorf("Cannot parse tmpfs, too many ':': %s", t)
|
return oci, fmt.Errorf("Cannot parse tmpfs, too many ':': %s", t)
|
||||||
}
|
}
|
||||||
dest := parts[0]
|
dest := parts[0]
|
||||||
opts := []string{}
|
opts := []string{}
|
||||||
@ -241,13 +466,13 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
}
|
}
|
||||||
mounts[dest] = specs.Mount{Destination: dest, Type: "tmpfs", Source: "tmpfs", Options: opts}
|
mounts[dest] = specs.Mount{Destination: dest, Type: "tmpfs", Source: "tmpfs", Options: opts}
|
||||||
}
|
}
|
||||||
for _, b := range image.Binds {
|
for _, b := range assignStrings(label.Binds, yaml.Binds) {
|
||||||
parts := strings.Split(b, ":")
|
parts := strings.Split(b, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return []byte{}, fmt.Errorf("Cannot parse bind, missing ':': %s", b)
|
return oci, fmt.Errorf("Cannot parse bind, missing ':': %s", b)
|
||||||
}
|
}
|
||||||
if len(parts) > 3 {
|
if len(parts) > 3 {
|
||||||
return []byte{}, fmt.Errorf("Cannot parse bind, too many ':': %s", b)
|
return oci, fmt.Errorf("Cannot parse bind, too many ':': %s", b)
|
||||||
}
|
}
|
||||||
src := parts[0]
|
src := parts[0]
|
||||||
dest := parts[1]
|
dest := parts[1]
|
||||||
@ -257,7 +482,7 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
}
|
}
|
||||||
mounts[dest] = specs.Mount{Destination: dest, Type: "bind", Source: src, Options: opts}
|
mounts[dest] = specs.Mount{Destination: dest, Type: "bind", Source: src, Options: opts}
|
||||||
}
|
}
|
||||||
for _, m := range image.Mounts {
|
for _, m := range assignBinds(label.Mounts, yaml.Mounts) {
|
||||||
tp := m.Type
|
tp := m.Type
|
||||||
src := m.Source
|
src := m.Source
|
||||||
dest := m.Destination
|
dest := m.Destination
|
||||||
@ -272,7 +497,7 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
tp = "tmpfs"
|
tp = "tmpfs"
|
||||||
}
|
}
|
||||||
if tp == "" {
|
if tp == "" {
|
||||||
return []byte{}, fmt.Errorf("Mount for destination %s is missing type", dest)
|
return oci, fmt.Errorf("Mount for destination %s is missing type", dest)
|
||||||
}
|
}
|
||||||
if src == "" {
|
if src == "" {
|
||||||
// usually sane, eg proc, tmpfs etc
|
// usually sane, eg proc, tmpfs etc
|
||||||
@ -282,7 +507,7 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
dest = defaultMountpoint(tp)
|
dest = defaultMountpoint(tp)
|
||||||
}
|
}
|
||||||
if dest == "" {
|
if dest == "" {
|
||||||
return []byte{}, fmt.Errorf("Mount type %s is missing destination", tp)
|
return oci, fmt.Errorf("Mount type %s is missing destination", tp)
|
||||||
}
|
}
|
||||||
mounts[dest] = specs.Mount{Destination: dest, Type: tp, Source: src, Options: opts}
|
mounts[dest] = specs.Mount{Destination: dest, Type: tp, Source: src, Options: opts}
|
||||||
}
|
}
|
||||||
@ -294,22 +519,26 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
|
|
||||||
namespaces := []specs.LinuxNamespace{}
|
namespaces := []specs.LinuxNamespace{}
|
||||||
// to attach to an existing namespace, easiest to bind mount with nsfs in a system container
|
// to attach to an existing namespace, easiest to bind mount with nsfs in a system container
|
||||||
if image.Net != "host" {
|
netNS := assignStringEmpty(label.Net, yaml.Net)
|
||||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: image.Net})
|
if netNS != "host" {
|
||||||
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: emptyNone(netNS)})
|
||||||
}
|
}
|
||||||
if image.Pid != "host" {
|
pidNS := assignStringEmpty(label.Pid, yaml.Pid)
|
||||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.PIDNamespace, Path: image.Pid})
|
if pidNS != "host" {
|
||||||
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.PIDNamespace, Path: emptyNone(pidNS)})
|
||||||
}
|
}
|
||||||
if image.Ipc != "host" {
|
ipcNS := assignStringEmpty(label.Ipc, yaml.Ipc)
|
||||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.IPCNamespace, Path: image.Ipc})
|
if ipcNS != "host" {
|
||||||
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.IPCNamespace, Path: emptyNone(ipcNS)})
|
||||||
}
|
}
|
||||||
if image.Uts != "host" {
|
utsNS := assignStringEmpty(label.Uts, yaml.Uts)
|
||||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UTSNamespace, Path: image.Uts})
|
if utsNS != "host" {
|
||||||
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UTSNamespace, Path: emptyNone(utsNS)})
|
||||||
}
|
}
|
||||||
// TODO user, cgroup namespaces, maybe mount=host if useful
|
// TODO user, cgroup namespaces, maybe mount=host if useful
|
||||||
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.MountNamespace})
|
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.MountNamespace})
|
||||||
|
|
||||||
caps := image.Capabilities
|
caps := assignStrings(label.Capabilities, yaml.Capabilities)
|
||||||
if len(caps) == 1 {
|
if len(caps) == 1 {
|
||||||
switch cap := strings.ToLower(caps[0]); cap {
|
switch cap := strings.ToLower(caps[0]); cap {
|
||||||
case "none":
|
case "none":
|
||||||
@ -369,9 +598,9 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
Terminal: false,
|
Terminal: false,
|
||||||
//ConsoleSize
|
//ConsoleSize
|
||||||
User: specs.User{
|
User: specs.User{
|
||||||
UID: image.UID,
|
UID: assignUint32(label.UID, yaml.UID),
|
||||||
GID: image.GID,
|
GID: assignUint32(label.GID, yaml.GID),
|
||||||
AdditionalGids: image.AdditionalGids,
|
AdditionalGids: assignUint32Array(label.AdditionalGids, yaml.AdditionalGids),
|
||||||
// Username (Windows)
|
// Username (Windows)
|
||||||
},
|
},
|
||||||
Args: args,
|
Args: args,
|
||||||
@ -385,26 +614,28 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
Ambient: []string{},
|
Ambient: []string{},
|
||||||
},
|
},
|
||||||
Rlimits: []specs.LinuxRlimit{},
|
Rlimits: []specs.LinuxRlimit{},
|
||||||
NoNewPrivileges: image.NoNewPrivileges,
|
NoNewPrivileges: assignBool(label.NoNewPrivileges, yaml.NoNewPrivileges),
|
||||||
// ApparmorProfile
|
// ApparmorProfile
|
||||||
|
// TODO FIXME this has moved in runc spec and needs a revendor and update
|
||||||
|
//OOMScoreAdj: assignIntPtr(label.OOMScoreAdj, yaml.OOMScoreAdj),
|
||||||
// SelinuxLabel
|
// SelinuxLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
oci.Root = specs.Root{
|
oci.Root = specs.Root{
|
||||||
Path: "rootfs",
|
Path: "rootfs",
|
||||||
Readonly: image.Readonly,
|
Readonly: readonly,
|
||||||
}
|
}
|
||||||
|
|
||||||
oci.Hostname = image.Hostname
|
oci.Hostname = assignStringEmpty(label.Hostname, yaml.Hostname)
|
||||||
oci.Mounts = mountList
|
oci.Mounts = mountList
|
||||||
|
|
||||||
oci.Linux = &specs.Linux{
|
oci.Linux = &specs.Linux{
|
||||||
// UIDMappings
|
// UIDMappings
|
||||||
// GIDMappings
|
// GIDMappings
|
||||||
Sysctl: image.Sysctl,
|
Sysctl: assignMaps(label.Sysctl, yaml.Sysctl),
|
||||||
Resources: &specs.LinuxResources{
|
Resources: &specs.LinuxResources{
|
||||||
// Devices
|
// Devices
|
||||||
DisableOOMKiller: &image.DisableOOMKiller,
|
DisableOOMKiller: assignBoolPtr(label.DisableOOMKiller, yaml.DisableOOMKiller),
|
||||||
// Memory
|
// Memory
|
||||||
// CPU
|
// CPU
|
||||||
// Pids
|
// Pids
|
||||||
@ -412,21 +643,21 @@ func ConfigInspectToOCI(image *MobyImage, inspect types.ImageInspect) ([]byte, e
|
|||||||
// HugepageLimits
|
// HugepageLimits
|
||||||
// Network
|
// Network
|
||||||
},
|
},
|
||||||
CgroupsPath: image.CgroupsPath,
|
CgroupsPath: assignString(label.CgroupsPath, yaml.CgroupsPath),
|
||||||
Namespaces: namespaces,
|
Namespaces: namespaces,
|
||||||
// Devices
|
// Devices
|
||||||
// Seccomp
|
// Seccomp
|
||||||
RootfsPropagation: image.RootfsPropagation,
|
RootfsPropagation: assignString(label.RootfsPropagation, yaml.RootfsPropagation),
|
||||||
MaskedPaths: image.MaskedPaths,
|
MaskedPaths: assignStrings(label.MaskedPaths, yaml.MaskedPaths),
|
||||||
ReadonlyPaths: image.ReadonlyPaths,
|
ReadonlyPaths: assignStrings(label.ReadonlyPaths, yaml.ReadonlyPaths),
|
||||||
// MountLabel
|
// MountLabel
|
||||||
// IntelRdt
|
// IntelRdt
|
||||||
}
|
}
|
||||||
|
|
||||||
return json.MarshalIndent(oci, "", " ")
|
return oci, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func filesystem(m *Moby) (*bytes.Buffer, error) {
|
func filesystem(m Moby) (*bytes.Buffer, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
tw := tar.NewWriter(buf)
|
tw := tar.NewWriter(buf)
|
||||||
defer tw.Close()
|
defer tw.Close()
|
||||||
|
50
cmd/moby/config_test.go
Normal file
50
cmd/moby/config_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOverrides(t *testing.T) {
|
||||||
|
var yamlCaps = []string{"CAP_SYS_ADMIN"}
|
||||||
|
|
||||||
|
var yaml MobyImage = MobyImage{
|
||||||
|
Name: "test",
|
||||||
|
Image: "testimage",
|
||||||
|
Capabilities: &yamlCaps,
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelCaps = []string{"CAP_SYS_CHROOT"}
|
||||||
|
|
||||||
|
var label MobyImage = MobyImage{
|
||||||
|
Capabilities: &labelCaps,
|
||||||
|
Cwd: "/label/directory",
|
||||||
|
}
|
||||||
|
|
||||||
|
var inspect types.ImageInspect
|
||||||
|
var config container.Config
|
||||||
|
|
||||||
|
labelJSON, err := json.Marshal(label)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
config.Labels = map[string]string{"org.mobyproject.config": string(labelJSON)}
|
||||||
|
|
||||||
|
inspect.Config = &config
|
||||||
|
|
||||||
|
oci, err := ConfigInspectToOCI(yaml, inspect)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(oci.Process.Capabilities.Bounding, yamlCaps) {
|
||||||
|
t.Error("Expected yaml capabilities to override but got", oci.Process.Capabilities.Bounding)
|
||||||
|
}
|
||||||
|
if oci.Process.Cwd != label.Cwd {
|
||||||
|
t.Error("Expected label Cwd to be applied, got", oci.Process.Cwd)
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ const (
|
|||||||
vmdk = "linuxkit/mkimage-vmdk:182b541474ca7965c8e8f987389b651859f760da@sha256:99638c5ddb17614f54c6b8e11bd9d49d1dea9d837f38e0f6c1a5f451085d449b"
|
vmdk = "linuxkit/mkimage-vmdk:182b541474ca7965c8e8f987389b651859f760da@sha256:99638c5ddb17614f54c6b8e11bd9d49d1dea9d837f38e0f6c1a5f451085d449b"
|
||||||
)
|
)
|
||||||
|
|
||||||
func outputs(m *Moby, base string, image []byte) error {
|
func outputs(m Moby, base string, image []byte) error {
|
||||||
log.Debugf("output: %s %s", m.Outputs, base)
|
log.Debugf("output: %s %s", m.Outputs, base)
|
||||||
|
|
||||||
for _, o := range m.Outputs {
|
for _, o := range m.Outputs {
|
||||||
|
Loading…
Reference in New Issue
Block a user