Split out into a small stub command line and a library

- this is pretty much the smallest change to split this out and it
  exposes a few things that can be improved later
- no change to logging yet

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
Justin Cormack
2017-06-21 16:19:31 -07:00
parent a7e3fd29b4
commit e7ebabdb05
16 changed files with 564 additions and 498 deletions

View File

@@ -1,23 +1,20 @@
package main
import (
"archive/tar"
"bytes"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/moby/tool/src/moby"
)
const defaultNameForStdin = "moby"
@@ -36,52 +33,32 @@ func (o *outputList) Set(value string) error {
return nil
}
var streamable = map[string]bool{
"docker": true,
"tar": true,
}
type addFun func(*tar.Writer) error
const dockerfile = `
FROM scratch
COPY . ./
RUN rm -f Dockerfile
ENTRYPOINT ["/sbin/tini", "--", "/bin/rc.init"]
`
var additions = map[string]addFun{
"docker": func(tw *tar.Writer) error {
log.Infof(" Adding Dockerfile")
hdr := &tar.Header{
Name: "Dockerfile",
Mode: 0644,
Size: int64(len(dockerfile)),
// Parse a string which is either a number in MB, or a number with
// either M (for Megabytes) or G (for GigaBytes) as a suffix and
// returns the number in MB. Return 0 if string is empty.
func getDiskSizeMB(s string) (int, error) {
if s == "" {
return 0, nil
}
sz := len(s)
if strings.HasSuffix(s, "G") {
i, err := strconv.Atoi(s[:sz-1])
if err != nil {
return 0, err
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := tw.Write([]byte(dockerfile)); err != nil {
return err
}
return nil
},
return i * 1024, nil
}
if strings.HasSuffix(s, "M") {
s = s[:sz-1]
}
return strconv.Atoi(s)
}
// Process the build arguments and execute build
func build(args []string) {
var buildOut outputList
outputTypes := []string{}
for k := range streamable {
outputTypes = append(outputTypes, k)
}
for k := range outFuns {
outputTypes = append(outputTypes, k)
}
sort.Strings(outputTypes)
outputTypes := moby.OutputTypes()
buildCmd := flag.NewFlagSet("build", flag.ExitOnError)
buildCmd.Usage = func() {
@@ -135,13 +112,13 @@ func build(args []string) {
if len(buildOut) > 1 {
for _, o := range buildOut {
if streamable[o] {
if moby.Streamable(o) {
log.Fatalf("Output type %s must be the only output specified", o)
}
}
}
if len(buildOut) == 1 && streamable[buildOut[0]] {
if len(buildOut) == 1 && moby.Streamable(buildOut[0]) {
if *buildOutputFile == "" {
*buildOutputFile = filepath.Join(*buildDir, name+"."+buildOut[0])
// stop the errors in the validation below
@@ -150,7 +127,7 @@ func build(args []string) {
}
} else {
err := validateOutputs(buildOut)
err := moby.ValidateOutputs(buildOut)
if err != nil {
log.Errorf("Error parsing outputs: %v", err)
buildCmd.Usage()
@@ -159,7 +136,6 @@ func build(args []string) {
}
var outputFile *os.File
var addition addFun
if *buildOutputFile != "" {
if len(buildOut) > 1 {
log.Fatal("The -output option can only be specified when generating a single output format")
@@ -170,7 +146,7 @@ func build(args []string) {
if *buildDir != "" {
log.Fatal("The -output option cannot be specified with -dir")
}
if !streamable[buildOut[0]] {
if !moby.Streamable(buildOut[0]) {
log.Fatalf("The -output option cannot be specified for build type %s as it cannot be streamed", buildOut[0])
}
if *buildOutputFile == "-" {
@@ -183,7 +159,6 @@ func build(args []string) {
}
defer outputFile.Close()
}
addition = additions[buildOut[0]]
}
size, err := getDiskSizeMB(*buildSize)
@@ -191,7 +166,7 @@ func build(args []string) {
log.Fatalf("Unable to parse disk size: %v", err)
}
var moby Moby
var m moby.Moby
for _, arg := range remArgs {
var config []byte
if conf := arg; conf == "-" {
@@ -220,16 +195,16 @@ func build(args []string) {
}
}
m, err := NewConfig(config)
c, err := moby.NewConfig(config)
if err != nil {
log.Fatalf("Invalid config: %v", err)
}
moby = AppendConfig(moby, m)
m = moby.AppendConfig(m, c)
}
if *buildDisableTrust {
log.Debugf("Disabling content trust checks for this build")
moby.Trust = TrustConfig{}
m.Trust = moby.TrustConfig{}
}
var buf *bytes.Buffer
@@ -240,7 +215,13 @@ func build(args []string) {
buf = new(bytes.Buffer)
w = buf
}
err = buildInternal(moby, w, *buildPull, addition)
// this is a weird interface, but currently only streamable types can have additional files
// need to split up the base tarball outputs from the secondary stages
var tp string
if moby.Streamable(buildOut[0]) {
tp = buildOut[0]
}
err = moby.Build(m, w, *buildPull, tp)
if err != nil {
log.Fatalf("%v", err)
}
@@ -248,418 +229,9 @@ func build(args []string) {
if outputFile == nil {
image := buf.Bytes()
log.Infof("Create outputs:")
err = outputs(filepath.Join(*buildDir, name), image, buildOut, size, *buildHyperkit)
err = moby.Outputs(filepath.Join(*buildDir, name), image, buildOut, size, *buildHyperkit)
if err != nil {
log.Fatalf("Error writing outputs: %v", err)
}
}
}
// Parse a string which is either a number in MB, or a number with
// either M (for Megabytes) or G (for GigaBytes) as a suffix and
// returns the number in MB. Return 0 if string is empty.
func getDiskSizeMB(s string) (int, error) {
if s == "" {
return 0, nil
}
sz := len(s)
if strings.HasSuffix(s, "G") {
i, err := strconv.Atoi(s[:sz-1])
if err != nil {
return 0, err
}
return i * 1024, nil
}
if strings.HasSuffix(s, "M") {
s = s[:sz-1]
}
return strconv.Atoi(s)
}
func enforceContentTrust(fullImageName string, config *TrustConfig) bool {
for _, img := range config.Image {
// First check for an exact name match
if img == fullImageName {
return true
}
// Also check for an image name only match
// by removing a possible tag (with possibly added digest):
imgAndTag := strings.Split(fullImageName, ":")
if len(imgAndTag) >= 2 && img == imgAndTag[0] {
return true
}
// and by removing a possible digest:
imgAndDigest := strings.Split(fullImageName, "@sha256:")
if len(imgAndDigest) >= 2 && img == imgAndDigest[0] {
return true
}
}
for _, org := range config.Org {
var imgOrg string
splitName := strings.Split(fullImageName, "/")
switch len(splitName) {
case 0:
// if the image is empty, return false
return false
case 1:
// for single names like nginx, use library
imgOrg = "library"
case 2:
// for names that assume docker hub, like linxukit/alpine, take the first split
imgOrg = splitName[0]
default:
// for names that include the registry, the second piece is the org, ex: docker.io/library/alpine
imgOrg = splitName[1]
}
if imgOrg == org {
return true
}
}
return false
}
// Perform the actual build process
func buildInternal(m Moby, w io.Writer, pull bool, addition addFun) error {
iw := tar.NewWriter(w)
if m.Kernel.Image != "" {
// get kernel and initrd tarball from container
log.Infof("Extract kernel image: %s", m.Kernel.Image)
kf := newKernelFilter(iw, m.Kernel.Cmdline)
err := ImageTar(m.Kernel.Image, "", kf, enforceContentTrust(m.Kernel.Image, &m.Trust), pull)
if err != nil {
return fmt.Errorf("Failed to extract kernel image and tarball: %v", err)
}
err = kf.Close()
if err != nil {
return fmt.Errorf("Close error: %v", err)
}
}
// convert init images to tarballs
if len(m.Init) != 0 {
log.Infof("Add init containers:")
}
for _, ii := range m.Init {
log.Infof("Process init image: %s", ii)
err := ImageTar(ii, "", iw, enforceContentTrust(ii, &m.Trust), pull)
if err != nil {
return fmt.Errorf("Failed to build init tarball from %s: %v", ii, err)
}
}
if len(m.Onboot) != 0 {
log.Infof("Add onboot containers:")
}
for i, image := range m.Onboot {
log.Infof(" Create OCI config for %s", image.Image)
useTrust := enforceContentTrust(image.Image, &m.Trust)
config, err := ConfigToOCI(image, useTrust)
if err != nil {
return fmt.Errorf("Failed to create config.json for %s: %v", image.Image, err)
}
so := fmt.Sprintf("%03d", i)
path := "containers/onboot/" + so + "-" + image.Name
err = ImageBundle(path, image.Image, config, iw, useTrust, pull)
if err != nil {
return fmt.Errorf("Failed to extract root filesystem for %s: %v", image.Image, err)
}
}
if len(m.Services) != 0 {
log.Infof("Add service containers:")
}
for _, image := range m.Services {
log.Infof(" Create OCI config for %s", image.Image)
useTrust := enforceContentTrust(image.Image, &m.Trust)
config, err := ConfigToOCI(image, useTrust)
if err != nil {
return fmt.Errorf("Failed to create config.json for %s: %v", image.Image, err)
}
path := "containers/services/" + image.Name
err = ImageBundle(path, image.Image, config, iw, useTrust, pull)
if err != nil {
return fmt.Errorf("Failed to extract root filesystem for %s: %v", image.Image, err)
}
}
// add files
err := filesystem(m, iw)
if err != nil {
return fmt.Errorf("failed to add filesystem parts: %v", err)
}
// add anything additional for this output type
if addition != nil {
err = addition(iw)
if err != nil {
return fmt.Errorf("Failed to add additional files: %v", err)
}
}
err = iw.Close()
if err != nil {
return fmt.Errorf("initrd close error: %v", err)
}
return nil
}
// kernelFilter is a tar.Writer that transforms a kernel image into the output we want on underlying tar writer
type kernelFilter struct {
tw *tar.Writer
buffer *bytes.Buffer
cmdline string
discard bool
foundKernel bool
foundKTar bool
}
func newKernelFilter(tw *tar.Writer, cmdline string) *kernelFilter {
return &kernelFilter{tw: tw, cmdline: cmdline}
}
func (k *kernelFilter) finishTar() error {
if k.buffer == nil {
return nil
}
tr := tar.NewReader(k.buffer)
err := tarAppend(k.tw, tr)
k.buffer = nil
return err
}
func (k *kernelFilter) Close() error {
if !k.foundKernel {
return errors.New("did not find kernel in kernel image")
}
if !k.foundKTar {
return errors.New("did not find kernel.tar in kernel image")
}
return k.finishTar()
}
func (k *kernelFilter) Flush() error {
err := k.finishTar()
if err != nil {
return err
}
return k.tw.Flush()
}
func (k *kernelFilter) Write(b []byte) (n int, err error) {
if k.discard {
return len(b), nil
}
if k.buffer != nil {
return k.buffer.Write(b)
}
return k.tw.Write(b)
}
func (k *kernelFilter) WriteHeader(hdr *tar.Header) error {
err := k.finishTar()
if err != nil {
return err
}
tw := k.tw
switch hdr.Name {
case "kernel":
if k.foundKernel {
return errors.New("found more than one possible kernel image")
}
k.foundKernel = true
k.discard = false
whdr := &tar.Header{
Name: "boot",
Mode: 0755,
Typeflag: tar.TypeDir,
}
if err := tw.WriteHeader(whdr); err != nil {
return err
}
// add the cmdline in /boot/cmdline
whdr = &tar.Header{
Name: "boot/cmdline",
Mode: 0644,
Size: int64(len(k.cmdline)),
}
if err := tw.WriteHeader(whdr); err != nil {
return err
}
buf := bytes.NewBufferString(k.cmdline)
_, err = io.Copy(tw, buf)
if err != nil {
return err
}
whdr = &tar.Header{
Name: "boot/kernel",
Mode: hdr.Mode,
Size: hdr.Size,
}
if err := tw.WriteHeader(whdr); err != nil {
return err
}
case "kernel.tar":
k.foundKTar = true
k.discard = false
k.buffer = new(bytes.Buffer)
default:
k.discard = true
}
return nil
}
func tarAppend(iw *tar.Writer, tr *tar.Reader) error {
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
err = iw.WriteHeader(hdr)
if err != nil {
return err
}
_, err = io.Copy(iw, tr)
if err != nil {
return err
}
}
return nil
}
func filesystem(m Moby, tw *tar.Writer) error {
// TODO also include the files added in other parts of the build
var addedFiles = map[string]bool{}
if len(m.Files) != 0 {
log.Infof("Add files:")
}
for _, f := range m.Files {
log.Infof(" %s", f.Path)
if f.Path == "" {
return errors.New("Did not specify path for file")
}
// tar archives should not have absolute paths
if f.Path[0] == os.PathSeparator {
f.Path = f.Path[1:]
}
mode := int64(0600)
if f.Directory {
mode = 0700
}
if f.Mode != "" {
var err error
mode, err = strconv.ParseInt(f.Mode, 8, 32)
if err != nil {
return fmt.Errorf("Cannot parse file mode as octal value: %v", err)
}
}
dirMode := mode
if dirMode&0700 != 0 {
dirMode |= 0100
}
if dirMode&0070 != 0 {
dirMode |= 0010
}
if dirMode&0007 != 0 {
dirMode |= 0001
}
var contents []byte
if f.Contents != nil {
contents = []byte(*f.Contents)
}
if !f.Directory && f.Contents == nil && f.Symlink == "" {
if f.Source == "" {
return errors.New("Contents of file not specified")
}
if len(f.Source) > 2 && f.Source[:2] == "~/" {
f.Source = homeDir() + f.Source[1:]
}
if f.Optional {
_, err := os.Stat(f.Source)
if err != nil {
// skip if not found or readable
log.Debugf("Skipping file [%s] as not readable and marked optional", f.Source)
continue
}
}
var err error
contents, err = ioutil.ReadFile(f.Source)
if err != nil {
return err
}
}
// 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
}
if !addedFiles[root] {
hdr := &tar.Header{
Name: root,
Typeflag: tar.TypeDir,
Mode: dirMode,
}
err := tw.WriteHeader(hdr)
if err != nil {
return err
}
addedFiles[root] = true
}
}
addedFiles[f.Path] = true
if f.Directory {
if f.Contents != nil {
return errors.New("Directory with contents not allowed")
}
hdr := &tar.Header{
Name: f.Path,
Typeflag: tar.TypeDir,
Mode: mode,
}
err := tw.WriteHeader(hdr)
if err != nil {
return err
}
} else if f.Symlink != "" {
hdr := &tar.Header{
Name: f.Path,
Typeflag: tar.TypeSymlink,
Mode: mode,
Linkname: f.Symlink,
}
err := tw.WriteHeader(hdr)
if err != nil {
return err
}
} else {
hdr := &tar.Header{
Name: f.Path,
Mode: mode,
Size: int64(len(contents)),
}
err := tw.WriteHeader(hdr)
if err != nil {
return err
}
_, err = tw.Write(contents)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,758 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v2"
)
// Moby is the type of a Moby config file
type Moby struct {
Kernel struct {
Image string
Cmdline string
}
Init []string
Onboot []MobyImage
Services []MobyImage
Trust TrustConfig
Files []struct {
Path string
Directory bool
Symlink string
Contents *string
Source string
Optional bool
Mode string
}
}
// TrustConfig is the type of a content trust config
type TrustConfig struct {
Image []string
Org []string
}
// MobyImage is the type of an image config
type MobyImage struct {
Name string `yaml:"name" json:"name"`
Image string `yaml:"image" json:"image"`
Capabilities *[]string `yaml:"capabilities" json:"capabilities,omitempty"`
Mounts *[]specs.Mount `yaml:"mounts" json:"mounts,omitempty"`
Binds *[]string `yaml:"binds" json:"binds,omitempty"`
Tmpfs *[]string `yaml:"tmpfs" json:"tmpfs,omitempty"`
Command *[]string `yaml:"command" json:"command,omitempty"`
Env *[]string `yaml:"env" json:"env,omitempty"`
Cwd string `yaml:"cwd" json:"cwd"`
Net string `yaml:"net" json:"net"`
Pid string `yaml:"pid" json:"pid"`
Ipc string `yaml:"ipc" json:"ipc"`
Uts string `yaml:"uts" json:"uts"`
Hostname string `yaml:"hostname" json:"hostname"`
Readonly *bool `yaml:"readonly" json:"readonly,omitempty"`
MaskedPaths *[]string `yaml:"maskedPaths" json:"maskedPaths,omitempty"`
ReadonlyPaths *[]string `yaml:"readonlyPaths" json:"readonlyPaths,omitempty"`
UID *uint32 `yaml:"uid" json:"uid,omitempty"`
GID *uint32 `yaml:"gid" json:"gid,omitempty"`
AdditionalGids *[]uint32 `yaml:"additionalGids" json:"additionalGids,omitempty"`
NoNewPrivileges *bool `yaml:"noNewPrivileges" json:"noNewPrivileges,omitempty"`
OOMScoreAdj *int `yaml:"oomScoreAdj" json:"oomScoreAdj,omitempty"`
DisableOOMKiller *bool `yaml:"disableOOMKiller" json:"disableOOMKiller,omitempty"`
RootfsPropagation *string `yaml:"rootfsPropagation" json:"rootfsPropagation,omitempty"`
CgroupsPath *string `yaml:"cgroupsPath" json:"cgroupsPath,omitempty"`
Sysctl *map[string]string `yaml:"sysctl" json:"sysctl,omitempty"`
Rlimits *[]string `yaml:"rlimits" json:"rlimits,omitempty"`
}
// github.com/go-yaml/yaml treats map keys as interface{} while encoding/json
// requires them to be strings, integers or to implement encoding.TextMarshaler.
// Fix this up by recursively mapping all map[interface{}]interface{} types into
// map[string]interface{}.
// see http://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang#answer-40737676
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
}
}
return i
}
// NewConfig parses a config file
func NewConfig(config []byte) (Moby, error) {
m := Moby{}
// Parse raw yaml
var rawYaml interface{}
err := yaml.Unmarshal(config, &rawYaml)
if err != nil {
return m, err
}
// Convert to raw JSON
rawJSON := convert(rawYaml)
// Validate raw yaml with JSON schema
schemaLoader := gojsonschema.NewStringLoader(schema)
documentLoader := gojsonschema.NewGoLoader(rawJSON)
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return m, err
}
if !result.Valid() {
fmt.Printf("The configuration file is invalid:\n")
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
return m, fmt.Errorf("invalid configuration file")
}
// Parse yaml
err = yaml.Unmarshal(config, &m)
if err != nil {
return m, err
}
return m, nil
}
// AppendConfig appends two configs.
func AppendConfig(m0, m1 Moby) Moby {
moby := m0
if m1.Kernel.Image != "" {
moby.Kernel.Image = m1.Kernel.Image
}
if m1.Kernel.Cmdline != "" {
moby.Kernel.Cmdline = m1.Kernel.Cmdline
}
moby.Init = append(moby.Init, m1.Init...)
moby.Onboot = append(moby.Onboot, m1.Onboot...)
moby.Services = append(moby.Services, m1.Services...)
moby.Files = append(moby.Files, m1.Files...)
moby.Trust.Image = append(moby.Trust.Image, m1.Trust.Image...)
moby.Trust.Org = append(moby.Trust.Org, m1.Trust.Org...)
return moby
}
// 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
func ConfigToOCI(image MobyImage, trust bool) ([]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, trust)
if err != nil {
return []byte{}, err
}
oci, err := ConfigInspectToOCI(image, inspect)
if err != nil {
return []byte{}, err
}
return json.MarshalIndent(oci, "", " ")
}
func defaultMountpoint(tp string) string {
switch tp {
case "proc":
return "/proc"
case "devpts":
return "/dev/pts"
case "sysfs":
return "/sys"
case "cgroup":
return "/sys/fs/cgroup"
case "mqueue":
return "/dev/mqueue"
default:
return ""
}
}
// Sort mounts by number of path components so /dev/pts is listed after /dev
type mlist []specs.Mount
func (m mlist) Len() int {
return len(m)
}
func (m mlist) Less(i, j int) bool {
return m.parts(i) < m.parts(j)
}
func (m mlist) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func (m mlist) parts(i int) int {
return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
}
// assignBool does ordered overrides from JSON bool pointers
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
}
// assignStringEmpty3 does ordered overrides if strings are empty, for
// values where there is always an explicit override eg "none"
func assignStringEmpty3(v1, v2, v3 string) string {
if v3 != "" {
return v3
}
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
}
// 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{}
var inspectConfig container.Config
if inspect.Config != nil {
inspectConfig = *inspect.Config
}
// look for org.mobyproject.config label
var label MobyImage
labelString := inspectConfig.Labels["org.mobyproject.config"]
if labelString != "" {
var err error
label, err = NewImage([]byte(labelString))
if err != nil {
return oci, err
}
}
// 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
procOptions := []string{"nosuid", "nodev", "noexec", "relatime"}
devOptions := []string{"nosuid", "strictatime", "mode=755", "size=65536k"}
if readonly {
devOptions = append(devOptions, "ro")
}
ptsOptions := []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}
sysOptions := []string{"nosuid", "noexec", "nodev"}
if readonly {
sysOptions = append(sysOptions, "ro")
}
cgroupOptions := []string{"nosuid", "noexec", "nodev", "relatime", "ro"}
// note omits "standard" /dev/shm and /dev/mqueue
mounts := map[string]specs.Mount{
"/proc": {Destination: "/proc", Type: "proc", Source: "proc", Options: procOptions},
"/dev": {Destination: "/dev", Type: "tmpfs", Source: "tmpfs", Options: devOptions},
"/dev/pts": {Destination: "/dev/pts", Type: "devpts", Source: "devpts", Options: ptsOptions},
"/sys": {Destination: "/sys", Type: "sysfs", Source: "sysfs", Options: sysOptions},
"/sys/fs/cgroup": {Destination: "/sys/fs/cgroup", Type: "cgroup", Source: "cgroup", Options: cgroupOptions},
}
for _, t := range assignStrings(label.Tmpfs, yaml.Tmpfs) {
parts := strings.Split(t, ":")
if len(parts) > 2 {
return oci, fmt.Errorf("Cannot parse tmpfs, too many ':': %s", t)
}
dest := parts[0]
opts := []string{}
if len(parts) == 2 {
opts = strings.Split(parts[1], ",")
}
mounts[dest] = specs.Mount{Destination: dest, Type: "tmpfs", Source: "tmpfs", Options: opts}
}
for _, b := range assignStrings(label.Binds, yaml.Binds) {
parts := strings.Split(b, ":")
if len(parts) < 2 {
return oci, fmt.Errorf("Cannot parse bind, missing ':': %s", b)
}
if len(parts) > 3 {
return oci, 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 = append(strings.Split(parts[2], ","), "rbind")
}
mounts[dest] = specs.Mount{Destination: dest, Type: "bind", Source: src, Options: opts}
}
for _, m := range assignBinds(label.Mounts, yaml.Mounts) {
tp := m.Type
src := m.Source
dest := m.Destination
opts := m.Options
if tp == "" {
switch src {
case "mqueue", "devpts", "proc", "sysfs", "cgroup":
tp = src
}
}
if tp == "" && dest == "/dev" {
tp = "tmpfs"
}
if tp == "" {
return oci, fmt.Errorf("Mount for destination %s is missing type", dest)
}
if src == "" {
// usually sane, eg proc, tmpfs etc
src = tp
}
if dest == "" {
dest = defaultMountpoint(tp)
}
if dest == "" {
return oci, fmt.Errorf("Mount type %s is missing destination", tp)
}
mounts[dest] = specs.Mount{Destination: dest, Type: tp, Source: src, Options: opts}
}
mountList := mlist{}
for _, m := range mounts {
mountList = append(mountList, m)
}
sort.Sort(mountList)
namespaces := []specs.LinuxNamespace{}
// to attach to an existing namespace, easiest to bind mount with nsfs in a system container
// net, ipc and uts namespaces: default to not creating a new namespace (usually host namespace)
netNS := assignStringEmpty3("root", label.Net, yaml.Net)
if netNS != "host" && netNS != "root" {
if netNS == "none" || netNS == "new" {
netNS = ""
}
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: netNS})
}
ipcNS := assignStringEmpty3("root", label.Ipc, yaml.Ipc)
if ipcNS != "host" && ipcNS != "root" {
if ipcNS == "new" {
ipcNS = ""
}
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.IPCNamespace, Path: ipcNS})
}
utsNS := assignStringEmpty3("root", label.Uts, yaml.Uts)
if utsNS != "host" && utsNS != "root" {
if utsNS == "new" {
utsNS = ""
}
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UTSNamespace, Path: utsNS})
}
// default to creating a new pid namespace
pidNS := assignStringEmpty(label.Pid, yaml.Pid)
if pidNS != "host" && pidNS != "root" {
if pidNS == "new" {
pidNS = ""
}
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.PIDNamespace, Path: pidNS})
}
// Always create a new mount namespace
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.MountNamespace})
// TODO user, cgroup namespaces
caps := assignStrings(label.Capabilities, yaml.Capabilities)
if len(caps) == 1 {
switch cap := strings.ToLower(caps[0]); cap {
case "none":
caps = []string{}
case "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",
}
}
}
rlimitsString := assignStrings(label.Rlimits, yaml.Rlimits)
rlimits := []specs.LinuxRlimit{}
for _, limitString := range rlimitsString {
rs := strings.SplitN(limitString, ",", 3)
var limit string
var soft, hard uint64
switch len(rs) {
case 3:
origLimit := limit
limit = strings.ToUpper(strings.TrimSpace(rs[0]))
if !strings.HasPrefix(limit, "RLIMIT_") {
limit = "RLIMIT_" + limit
}
softString := strings.TrimSpace(rs[1])
if strings.ToLower(softString) == "unlimited" {
soft = 18446744073709551615
} else {
var err error
soft, err = strconv.ParseUint(softString, 10, 64)
if err != nil {
return oci, fmt.Errorf("Cannot parse %s as uint64: %v", softString, err)
}
}
hardString := strings.TrimSpace(rs[2])
if strings.ToLower(hardString) == "unlimited" {
hard = 18446744073709551615
} else {
var err error
hard, err = strconv.ParseUint(hardString, 10, 64)
if err != nil {
return oci, fmt.Errorf("Cannot parse %s as uint64: %v", hardString, err)
}
}
switch limit {
case
"RLIMIT_CPU",
"RLIMIT_FSIZE",
"RLIMIT_DATA",
"RLIMIT_STACK",
"RLIMIT_CORE",
"RLIMIT_RSS",
"RLIMIT_NPROC",
"RLIMIT_NOFILE",
"RLIMIT_MEMLOCK",
"RLIMIT_AS",
"RLIMIT_LOCKS",
"RLIMIT_SIGPENDING",
"RLIMIT_MSGQUEUE",
"RLIMIT_NICE",
"RLIMIT_RTPRIO",
"RLIMIT_RTTIME":
rlimits = append(rlimits, specs.LinuxRlimit{Type: limit, Soft: soft, Hard: hard})
default:
return oci, fmt.Errorf("Unknown limit: %s", origLimit)
}
default:
return oci, fmt.Errorf("Cannot parse rlimit: %s", rlimitsString)
}
}
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: assignUint32(label.UID, yaml.UID),
GID: assignUint32(label.GID, yaml.GID),
AdditionalGids: assignUint32Array(label.AdditionalGids, yaml.AdditionalGids),
// Username (Windows)
},
Args: args,
Env: env,
Cwd: cwd,
Capabilities: &specs.LinuxCapabilities{
Bounding: caps,
Effective: caps,
Inheritable: caps,
Permitted: caps,
Ambient: []string{},
},
Rlimits: rlimits,
NoNewPrivileges: assignBool(label.NoNewPrivileges, yaml.NoNewPrivileges),
// ApparmorProfile
// TODO FIXME this has moved in runc spec and needs a revendor and update
//OOMScoreAdj: assignIntPtr(label.OOMScoreAdj, yaml.OOMScoreAdj),
// SelinuxLabel
}
oci.Root = specs.Root{
Path: "rootfs",
Readonly: readonly,
}
oci.Hostname = assignStringEmpty(label.Hostname, yaml.Hostname)
oci.Mounts = mountList
oci.Linux = &specs.Linux{
// UIDMappings
// GIDMappings
Sysctl: assignMaps(label.Sysctl, yaml.Sysctl),
Resources: &specs.LinuxResources{
// Devices
DisableOOMKiller: assignBoolPtr(label.DisableOOMKiller, yaml.DisableOOMKiller),
// Memory
// CPU
// Pids
// BlockIO
// HugepageLimits
// Network
},
CgroupsPath: assignString(label.CgroupsPath, yaml.CgroupsPath),
Namespaces: namespaces,
// Devices
// Seccomp
RootfsPropagation: assignString(label.RootfsPropagation, yaml.RootfsPropagation),
MaskedPaths: assignStrings(label.MaskedPaths, yaml.MaskedPaths),
ReadonlyPaths: assignStrings(label.ReadonlyPaths, yaml.ReadonlyPaths),
// MountLabel
// IntelRdt
}
return oci, nil
}

View File

@@ -1,50 +0,0 @@
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{
Name: "test",
Image: "testimage",
Capabilities: &yamlCaps,
}
var labelCaps = []string{"CAP_SYS_CHROOT"}
var label = 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)
}
}

View File

@@ -1,200 +0,0 @@
package main
// We want to replace much of this with use of containerd tools
// and also using the Docker API not shelling out
import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
func dockerRun(input io.Reader, args ...string) ([]byte, error) {
log.Debugf("docker run (input): %s", strings.Join(args, " "))
docker, err := exec.LookPath("docker")
if err != nil {
return []byte{}, errors.New("Docker does not seem to be installed")
}
args = append([]string{"run", "--rm", "-i"}, args...)
cmd := exec.Command(docker, args...)
cmd.Stdin = input
stderrPipe, err := cmd.StderrPipe()
if err != nil {
return []byte{}, err
}
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return []byte{}, err
}
err = cmd.Start()
if err != nil {
return []byte{}, err
}
stdout, err := ioutil.ReadAll(stdoutPipe)
if err != nil {
return []byte{}, err
}
stderr, err := ioutil.ReadAll(stderrPipe)
if err != nil {
return []byte{}, err
}
err = cmd.Wait()
if err != nil {
return []byte{}, fmt.Errorf("%v: %s", err, stderr)
}
log.Debugf("docker run (input): %s...Done", strings.Join(args, " "))
return stdout, nil
}
func dockerCreate(image string) (string, error) {
log.Debugf("docker create: %s", image)
cli, err := dockerClient()
if err != nil {
return "", errors.New("could not initialize Docker API client")
}
// we do not ever run the container, so /dev/null is used as command
config := &container.Config{
Cmd: []string{"/dev/null"},
Image: image,
}
respBody, err := cli.ContainerCreate(context.Background(), config, nil, nil, "")
if err != nil {
return "", err
}
log.Debugf("docker create: %s...Done", image)
return respBody.ID, nil
}
func dockerExport(container string) ([]byte, error) {
log.Debugf("docker export: %s", container)
cli, err := dockerClient()
if err != nil {
return []byte{}, errors.New("could not initialize Docker API client")
}
responseBody, err := cli.ContainerExport(context.Background(), container)
if err != nil {
return []byte{}, err
}
defer responseBody.Close()
output := bytes.NewBuffer(nil)
_, err = io.Copy(output, responseBody)
if err != nil {
return []byte{}, err
}
return output.Bytes(), nil
}
func dockerRm(container string) error {
log.Debugf("docker rm: %s", container)
cli, err := dockerClient()
if err != nil {
return errors.New("could not initialize Docker API client")
}
if err = cli.ContainerRemove(context.Background(), container, types.ContainerRemoveOptions{}); err != nil {
return err
}
log.Debugf("docker rm: %s...Done", container)
return nil
}
func dockerPull(image string, forcePull, trustedPull bool) error {
log.Debugf("docker pull: %s", image)
cli, err := dockerClient()
if err != nil {
return errors.New("could not initialize Docker API client")
}
if trustedPull {
log.Debugf("pulling %s with content trust", image)
trustedImg, err := TrustedReference(image)
if err != nil {
return fmt.Errorf("Trusted pull for %s failed: %v", image, err)
}
// tag the image on a best-effort basis after pulling with content trust,
// ensuring that docker picks up the tag and digest fom the canonical format
defer func(src, dst string) {
if err := cli.ImageTag(context.Background(), src, dst); err != nil {
log.Debugf("could not tag trusted image %s to %s", src, dst)
}
}(trustedImg.String(), image)
log.Debugf("successfully verified trusted reference %s from notary", trustedImg.String())
image = trustedImg.String()
imageSearchArg := filters.NewArgs()
imageSearchArg.Add("reference", trustedImg.String())
if _, err := cli.ImageList(context.Background(), types.ImageListOptions{Filters: imageSearchArg}); err == nil && !forcePull {
log.Debugf("docker pull: trusted image %s already cached...Done", trustedImg.String())
return nil
}
}
log.Infof("Pull image: %s", image)
r, err := cli.ImagePull(context.Background(), image, types.ImagePullOptions{})
if err != nil {
return err
}
defer r.Close()
_, err = io.Copy(ioutil.Discard, r)
if err != nil {
return err
}
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, trustedPull bool) (types.ImageInspect, error) {
log.Debugf("docker inspect image: %s", image)
inspect, _, err := cli.ImageInspectWithRaw(context.Background(), image)
if err != nil {
if client.IsErrImageNotFound(err) {
pullErr := dockerPull(image, true, trustedPull)
if pullErr != nil {
return types.ImageInspect{}, pullErr
}
inspect, _, err = cli.ImageInspectWithRaw(context.Background(), image)
if err != nil {
return types.ImageInspect{}, err
}
} else {
return types.ImageInspect{}, err
}
}
log.Debugf("docker inspect image: %s...Done", image)
return inspect, nil
}

View File

@@ -1,194 +0,0 @@
package main
import (
"archive/tar"
"bytes"
"fmt"
"io"
"io/ioutil"
"strings"
log "github.com/Sirupsen/logrus"
)
type tarWriter interface {
Close() error
Flush() error
Write(b []byte) (n int, err error)
WriteHeader(hdr *tar.Header) error
}
// This uses Docker to convert a Docker image into a tarball. It would be an improvement if we
// used the containerd libraries to do this instead locally direct from a local image
// cache as it would be much simpler.
var exclude = map[string]bool{
".dockerenv": true,
"Dockerfile": true,
"dev/console": true,
"dev/pts": true,
"dev/shm": true,
"etc/hostname": true,
}
var replace = map[string]string{
"etc/hosts": `127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`,
"etc/resolv.conf": `nameserver 8.8.8.8
nameserver 8.8.4.4
nameserver 2001:4860:4860::8888
nameserver 2001:4860:4860::8844
`,
}
// tarPrefix creates the leading directories for a path
func tarPrefix(path string, tw tarWriter) error {
if path == "" {
return nil
}
if path[len(path)-1] != byte('/') {
return fmt.Errorf("path does not end with /: %s", path)
}
path = path[:len(path)-1]
if path[0] == byte('/') {
return fmt.Errorf("path should be relative: %s", path)
}
mkdir := ""
for _, dir := range strings.Split(path, "/") {
mkdir = mkdir + dir
hdr := &tar.Header{
Name: mkdir,
Mode: 0755,
Typeflag: tar.TypeDir,
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
mkdir = mkdir + "/"
}
return nil
}
// ImageTar takes a Docker image and outputs it to a tar stream
func ImageTar(image, prefix string, tw tarWriter, trust bool, pull bool) error {
log.Debugf("image tar: %s %s", image, prefix)
if prefix != "" && prefix[len(prefix)-1] != byte('/') {
return fmt.Errorf("prefix does not end with /: %s", prefix)
}
err := tarPrefix(prefix, tw)
if err != nil {
return err
}
if pull || trust {
err := dockerPull(image, pull, trust)
if err != nil {
return fmt.Errorf("Could not pull image %s: %v", image, err)
}
}
container, err := dockerCreate(image)
if err != nil {
// if the image wasn't found, pull it down. Bail on other errors.
if strings.Contains(err.Error(), "No such image") {
err := dockerPull(image, true, trust)
if err != nil {
return fmt.Errorf("Could not pull image %s: %v", image, err)
}
container, err = dockerCreate(image)
if err != nil {
return fmt.Errorf("Failed to docker create image %s: %v", image, err)
}
} else {
return fmt.Errorf("Failed to create docker image %s: %v", image, err)
}
}
contents, err := dockerExport(container)
if err != nil {
return fmt.Errorf("Failed to docker export container from container %s: %v", container, err)
}
err = dockerRm(container)
if err != nil {
return fmt.Errorf("Failed to docker rm container %s: %v", container, err)
}
// now we need to filter out some files from the resulting tar archive
r := bytes.NewReader(contents)
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if exclude[hdr.Name] {
log.Debugf("image tar: %s %s exclude %s", image, prefix, hdr.Name)
_, err = io.Copy(ioutil.Discard, tr)
if err != nil {
return err
}
} else if replace[hdr.Name] != "" {
contents := replace[hdr.Name]
hdr.Size = int64(len(contents))
hdr.Name = prefix + hdr.Name
log.Debugf("image tar: %s %s add %s", image, prefix, hdr.Name)
if err := tw.WriteHeader(hdr); err != nil {
return err
}
buf := bytes.NewBufferString(contents)
_, err = io.Copy(tw, buf)
if err != nil {
return err
}
_, err = io.Copy(ioutil.Discard, tr)
if err != nil {
return err
}
} else {
log.Debugf("image tar: %s %s add %s", image, prefix, hdr.Name)
hdr.Name = prefix + hdr.Name
if err := tw.WriteHeader(hdr); err != nil {
return err
}
_, err = io.Copy(tw, tr)
if err != nil {
return err
}
}
}
return nil
}
// ImageBundle produces an OCI bundle at the given path in a tarball, given an image and a config.json
func ImageBundle(path string, image string, config []byte, tw tarWriter, trust bool, pull bool) error {
log.Debugf("image bundle: %s %s cfg: %s", path, image, string(config))
err := ImageTar(image, path+"/rootfs/", tw, trust, pull)
if err != nil {
return err
}
hdr := &tar.Header{
Name: path + "/" + "config.json",
Mode: 0644,
Size: int64(len(config)),
}
err = tw.WriteHeader(hdr)
if err != nil {
return err
}
buf := bytes.NewBuffer(config)
_, err = io.Copy(tw, buf)
if err != nil {
return err
}
return nil
}

View File

@@ -1,148 +0,0 @@
package main
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
log "github.com/Sirupsen/logrus"
)
var linuxkitYaml = map[string]string{"mkimage": `
kernel:
image: "linuxkit/kernel:4.9.x"
cmdline: "console=ttyS0"
init:
- linuxkit/init:1b8a7e394d2ec2f1fdb4d67645829d1b5bdca037
- linuxkit/runc:3a4e6cbf15470f62501b019b55e1caac5ee7689f
- linuxkit/containerd:b1766e4c4c09f63ac4925a6e4612852a93f7e73b
onboot:
- name: mkimage
image: "linuxkit/mkimage:5ad60299be03008f29c5caec3c5ea4ac0387aae6"
- name: poweroff
image: "linuxkit/poweroff:a8f1e4ad8d459f1fdaad9e4b007512cb3b504ae8"
trust:
org:
- linuxkit
`}
func imageFilename(name string) string {
yaml := linuxkitYaml[name]
hash := sha256.Sum256([]byte(yaml))
return filepath.Join(MobyDir, "linuxkit", name+"-"+fmt.Sprintf("%x", hash))
}
func ensureLinuxkitImage(name string) error {
filename := imageFilename(name)
_, err1 := os.Stat(filename + "-kernel")
_, err2 := os.Stat(filename + "-initrd.img")
_, err3 := os.Stat(filename + "-cmdline")
if err1 == nil && err2 == nil && err3 == nil {
return nil
}
err := os.MkdirAll(filepath.Join(MobyDir, "linuxkit"), 0755)
if err != nil {
return err
}
// TODO clean up old files
log.Infof("Building LinuxKit image %s to generate output formats", name)
yaml := linuxkitYaml[name]
m, err := NewConfig([]byte(yaml))
if err != nil {
return err
}
// TODO pass through --pull to here
buf := new(bytes.Buffer)
buildInternal(m, buf, false, nil)
image := buf.Bytes()
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = writeKernelInitrd(filename, kernel, initrd, cmdline)
if err != nil {
return err
}
return nil
}
func writeKernelInitrd(filename string, kernel []byte, initrd []byte, cmdline string) error {
err := ioutil.WriteFile(filename+"-kernel", kernel, 0600)
if err != nil {
return err
}
err = ioutil.WriteFile(filename+"-initrd.img", initrd, 0600)
if err != nil {
return err
}
err = ioutil.WriteFile(filename+"-cmdline", []byte(cmdline), 0600)
if err != nil {
return err
}
return nil
}
func outputLinuxKit(format string, filename string, kernel []byte, initrd []byte, cmdline string, size int, hyperkit bool) error {
log.Debugf("output linuxkit generated img: %s %s size %d", format, filename, size)
tmp, err := ioutil.TempDir(filepath.Join(MobyDir, "tmp"), "moby")
if err != nil {
return err
}
defer os.RemoveAll(tmp)
buf, err := tarInitrdKernel(kernel, initrd, cmdline)
if err != nil {
return err
}
tardisk := filepath.Join(tmp, "tardisk")
f, err := os.Create(tardisk)
if err != nil {
return err
}
_, err = io.Copy(f, buf)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
sizeString := fmt.Sprintf("%dM", size)
_ = os.Remove(filename)
_, err = os.Stat(filename)
if err == nil || !os.IsNotExist(err) {
return fmt.Errorf("Cannot remove existing file [%s]", filename)
}
linuxkit, err := exec.LookPath("linuxkit")
if err != nil {
return fmt.Errorf("Cannot find linuxkit executable, needed to build %s output type: %v", format, err)
}
commandLine := []string{"-q", "run", "qemu", "-disk", fmt.Sprintf("%s,size=%s,format=%s", filename, sizeString, format), "-disk", fmt.Sprintf("%s,format=raw", tardisk), "-kernel", imageFilename("mkimage")}
if hyperkit && format == "raw" {
state, err := ioutil.TempDir("", "s")
if err != nil {
return err
}
defer os.RemoveAll(state)
commandLine = []string{"-q", "run", "hyperkit", "-state", state, "-disk", fmt.Sprintf("%s,size=%s,format=%s", filename, sizeString, format), "-disk", fmt.Sprintf("%s,format=raw", tardisk), imageFilename("mkimage")}
}
log.Debugf("run %s: %v", linuxkit, commandLine)
cmd := exec.Command(linuxkit, commandLine...)
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"path/filepath"
log "github.com/Sirupsen/logrus"
"github.com/moby/tool/src/moby"
)
var (
@@ -17,9 +18,6 @@ var (
// GitCommit hash, set at compile time
GitCommit = "unknown"
// MobyDir is the location of the cache directory ~/.moby by default
MobyDir string
)
// infoFormatter overrides the default format for Info() log events to
@@ -91,16 +89,17 @@ func main() {
os.Exit(1)
}
MobyDir = *flagConfigDir
err := os.MkdirAll(MobyDir, 0755)
mobyDir := *flagConfigDir
err := os.MkdirAll(mobyDir, 0755)
if err != nil {
log.Fatalf("Could not create config directory [%s]: %v", MobyDir, err)
log.Fatalf("Could not create config directory [%s]: %v", mobyDir, err)
}
err = os.MkdirAll(filepath.Join(MobyDir, "tmp"), 0755)
err = os.MkdirAll(filepath.Join(mobyDir, "tmp"), 0755)
if err != nil {
log.Fatalf("Could not create config tmp directory [%s]: %v", filepath.Join(MobyDir, "tmp"), err)
log.Fatalf("Could not create config tmp directory [%s]: %v", filepath.Join(mobyDir, "tmp"), err)
}
moby.MobyDir = mobyDir
switch args[0] {
case "build":

View File

@@ -1,286 +0,0 @@
package main
import (
"archive/tar"
"bytes"
"fmt"
"io/ioutil"
"os"
log "github.com/Sirupsen/logrus"
"github.com/moby/tool/src/initrd"
)
const (
bios = "linuxkit/mkimage-iso-bios:db791abed6f2b5320feb6cec255a635aee3756f6@sha256:e57483075307bcea4a7257f87eee733d3e24e7a964ba15dcc01111df6729ab3b"
efi = "linuxkit/mkimage-iso-efi:5c2fc616bde288476a14f4f6dd0d273a66832822@sha256:876ef47ec2b30af40e70f1e98f496206eb430915867c4f9f400e1af47fd58d7c"
gcp = "linuxkit/mkimage-gcp:46716b3d3f7aa1a7607a3426fe0ccebc554b14ee@sha256:18d8e0482f65a2481f5b6ba1e7ce77723b246bf13bdb612be5e64df90297940c"
vhd = "linuxkit/mkimage-vhd:a04c8480d41ca9cef6b7710bd45a592220c3acb2@sha256:ba373dc8ae5dc72685dbe4b872d8f588bc68b2114abd8bdc6a74d82a2b62cce3"
vmdk = "linuxkit/mkimage-vmdk:182b541474ca7965c8e8f987389b651859f760da@sha256:99638c5ddb17614f54c6b8e11bd9d49d1dea9d837f38e0f6c1a5f451085d449b"
)
var outFuns = map[string]func(string, []byte, int, bool) error{
"kernel+initrd": func(base string, image []byte, size int, hyperkit bool) error {
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputKernelInitrd(base, kernel, initrd, cmdline)
if err != nil {
return fmt.Errorf("Error writing kernel+initrd output: %v", err)
}
return nil
},
"iso-bios": func(base string, image []byte, size int, hyperkit bool) error {
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputImg(bios, base+".iso", kernel, initrd, cmdline)
if err != nil {
return fmt.Errorf("Error writing iso-bios output: %v", err)
}
return nil
},
"iso-efi": func(base string, image []byte, size int, hyperkit bool) error {
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputImg(efi, base+"-efi.iso", kernel, initrd, cmdline)
if err != nil {
return fmt.Errorf("Error writing iso-efi output: %v", err)
}
return nil
},
"raw": func(base string, image []byte, size int, hyperkit bool) error {
filename := base + ".raw"
log.Infof(" %s", filename)
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputLinuxKit("raw", filename, kernel, initrd, cmdline, size, hyperkit)
if err != nil {
return fmt.Errorf("Error writing raw output: %v", err)
}
return nil
},
"gcp": func(base string, image []byte, size int, hyperkit bool) error {
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputImg(gcp, base+".img.tar.gz", kernel, initrd, cmdline)
if err != nil {
return fmt.Errorf("Error writing gcp output: %v", err)
}
return nil
},
"qcow2": func(base string, image []byte, size int, hyperkit bool) error {
filename := base + ".qcow2"
log.Infof(" %s", filename)
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputLinuxKit("qcow2", filename, kernel, initrd, cmdline, size, hyperkit)
if err != nil {
return fmt.Errorf("Error writing qcow2 output: %v", err)
}
return nil
},
"vhd": func(base string, image []byte, size int, hyperkit bool) error {
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputImg(vhd, base+".vhd", kernel, initrd, cmdline)
if err != nil {
return fmt.Errorf("Error writing vhd output: %v", err)
}
return nil
},
"vmdk": func(base string, image []byte, size int, hyperkit bool) error {
kernel, initrd, cmdline, err := tarToInitrd(image)
if err != nil {
return fmt.Errorf("Error converting to initrd: %v", err)
}
err = outputImg(vmdk, base+".vmdk", kernel, initrd, cmdline)
if err != nil {
return fmt.Errorf("Error writing vmdk output: %v", err)
}
return nil
},
}
var prereq = map[string]string{
"raw": "mkimage",
"qcow2": "mkimage",
}
func ensurePrereq(out string) error {
var err error
p := prereq[out]
if p != "" {
err = ensureLinuxkitImage(p)
}
return err
}
func validateOutputs(out outputList) error {
log.Debugf("validating output: %v", out)
for _, o := range out {
f := outFuns[o]
if f == nil {
return fmt.Errorf("Unknown output type %s", o)
}
err := ensurePrereq(o)
if err != nil {
return fmt.Errorf("Failed to set up output type %s: %v", o, err)
}
}
return nil
}
func outputs(base string, image []byte, out outputList, size int, hyperkit bool) error {
log.Debugf("output: %v %s", out, base)
err := validateOutputs(out)
if err != nil {
return err
}
for _, o := range out {
f := outFuns[o]
err := f(base, image, size, hyperkit)
if err != nil {
return err
}
}
return nil
}
func tarToInitrd(image []byte) ([]byte, []byte, string, error) {
w := new(bytes.Buffer)
iw := initrd.NewWriter(w)
r := bytes.NewReader(image)
tr := tar.NewReader(r)
kernel, cmdline, err := initrd.CopySplitTar(iw, tr)
if err != nil {
return []byte{}, []byte{}, "", err
}
iw.Close()
return kernel, w.Bytes(), cmdline, nil
}
func tarInitrdKernel(kernel, initrd []byte, cmdline string) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
hdr := &tar.Header{
Name: "kernel",
Mode: 0600,
Size: int64(len(kernel)),
}
err := tw.WriteHeader(hdr)
if err != nil {
return buf, err
}
_, err = tw.Write(kernel)
if err != nil {
return buf, err
}
hdr = &tar.Header{
Name: "initrd.img",
Mode: 0600,
Size: int64(len(initrd)),
}
err = tw.WriteHeader(hdr)
if err != nil {
return buf, err
}
_, err = tw.Write(initrd)
if err != nil {
return buf, err
}
hdr = &tar.Header{
Name: "cmdline",
Mode: 0600,
Size: int64(len(cmdline)),
}
err = tw.WriteHeader(hdr)
if err != nil {
return buf, err
}
_, err = tw.Write([]byte(cmdline))
if err != nil {
return buf, err
}
err = tw.Close()
if err != nil {
return buf, err
}
return buf, nil
}
func outputImg(image, filename string, kernel []byte, initrd []byte, cmdline string) error {
log.Debugf("output img: %s %s", image, filename)
log.Infof(" %s", filename)
buf, err := tarInitrdKernel(kernel, initrd, cmdline)
if err != nil {
return err
}
img, err := dockerRun(buf, image, cmdline)
if err != nil {
return err
}
err = ioutil.WriteFile(filename, img, os.FileMode(0644))
if err != nil {
return err
}
return nil
}
// this should replace the other version for types that can specify a size
func outputImgSize(image, filename string, kernel []byte, initrd []byte, cmdline string, size int) error {
log.Debugf("output img: %s %s size %d", image, filename, size)
log.Infof(" %s", filename)
buf, err := tarInitrdKernel(kernel, initrd, cmdline)
if err != nil {
return err
}
var img []byte
if size == 0 {
img, err = dockerRun(buf, image)
} else {
img, err = dockerRun(buf, image, fmt.Sprintf("%dM", size))
}
if err != nil {
return err
}
err = ioutil.WriteFile(filename, img, os.FileMode(0644))
if err != nil {
return err
}
return nil
}
func outputKernelInitrd(base string, kernel []byte, initrd []byte, cmdline string) error {
log.Debugf("output kernel/initrd: %s %s", base, cmdline)
log.Infof(" %s %s %s", base+"-kernel", base+"-initrd.img", base+"-cmdline")
err := ioutil.WriteFile(base+"-initrd.img", initrd, os.FileMode(0644))
if err != nil {
return err
}
err = ioutil.WriteFile(base+"-kernel", kernel, os.FileMode(0644))
if err != nil {
return err
}
err = ioutil.WriteFile(base+"-cmdline", []byte(cmdline), os.FileMode(0644))
if err != nil {
return err
}
return nil
}

View File

@@ -1,114 +0,0 @@
package main
var schema = string(`
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Moby Config",
"additionalProperties": false,
"definitions": {
"kernel": {
"type": "object",
"additionalProperties": false,
"properties": {
"image": { "type": "string"},
"cmdline": { "type": "string"}
}
},
"file": {
"type": "object",
"additionalProperties": false,
"properties": {
"path": {"type": "string"},
"directory": {"type": "boolean"},
"symlink": {"type": "string"},
"contents": {"type": "string"},
"source": {"type": "string"},
"optional": {"type": "boolean"},
"mode": {"type": "string"}
}
},
"files": {
"type": "array",
"items": { "$ref": "#/definitions/file" }
},
"trust": {
"type": "object",
"additionalProperties": false,
"properties": {
"image": { "$ref": "#/definitions/strings" },
"org": { "$ref": "#/definitions/strings" }
}
},
"strings": {
"type": "array",
"items": {"type": "string"}
},
"mount": {
"type": "object",
"additionalProperties": false,
"properties": {
"destination": { "type": "string" },
"type": { "type": "string" },
"source": { "type": "string" },
"options": { "$ref": "#/definitions/strings" }
}
},
"mounts": {
"type": "array",
"items": { "$ref": "#/definitions/mount" }
},
"image": {
"type": "object",
"additionalProperties": false,
"required": ["name", "image"],
"properties": {
"name": {"type": "string"},
"image": {"type": "string"},
"capabilities": { "$ref": "#/definitions/strings" },
"mounts": { "$ref": "#/definitions/mounts" },
"binds": { "$ref": "#/definitions/strings" },
"tmpfs": { "$ref": "#/definitions/strings" },
"command": { "$ref": "#/definitions/strings" },
"env": { "$ref": "#/definitions/strings" },
"cwd": { "type": "string"},
"net": { "type": "string"},
"pid": { "type": "string"},
"ipc": { "type": "string"},
"uts": { "type": "string"},
"readonly": { "type": "boolean"},
"maskedPaths": { "$ref": "#/definitions/strings" },
"readonlyPaths": { "$ref": "#/definitions/strings" },
"uid": {"type": "integer"},
"gid": {"type": "integer"},
"additionalGids": {
"type": "array",
"items": { "type": "integer" }
},
"noNewPrivileges": {"type": "boolean"},
"hostname": {"type": "string"},
"oomScoreAdj": {"type": "integer"},
"disableOOMKiller": {"type": "boolean"},
"rootfsPropagation": {"type": "string"},
"cgroupsPath": {"type": "string"},
"sysctl": {
"type": "array",
"items": { "$ref": "#/definitions/strings" }
},
"rlimits": { "$ref": "#/definitions/strings" }
}
},
"images": {
"type": "array",
"items": { "$ref": "#/definitions/image" }
}
},
"properties": {
"kernel": { "$ref": "#/definitions/kernel" },
"init": { "$ref": "#/definitions/strings" },
"onboot": { "$ref": "#/definitions/images" },
"services": { "$ref": "#/definitions/images" },
"trust": { "$ref": "#/definitions/trust" },
"files": { "$ref": "#/definitions/files" }
}
}
`)

View File

@@ -1,208 +0,0 @@
package main
import (
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"path/filepath"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/distribution/reference"
"github.com/docker/distribution/registry/client/auth"
"github.com/docker/distribution/registry/client/auth/challenge"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/docker/cli/trust"
notaryClient "github.com/docker/notary/client"
"github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf/data"
"github.com/opencontainers/go-digest"
)
// TrustedReference parses an image string, and does a notary lookup to verify and retrieve the signed digest reference
func TrustedReference(image string) (reference.Reference, error) {
ref, err := reference.ParseAnyReference(image)
if err != nil {
return nil, err
}
// to mimic docker pull: if we have a digest already, it's implicitly trusted
if digestRef, ok := ref.(reference.Digested); ok {
return digestRef, nil
}
// to mimic docker pull: if we have a digest already, it's implicitly trusted
if canonicalRef, ok := ref.(reference.Canonical); ok {
return canonicalRef, nil
}
namedRef, ok := ref.(reference.Named)
if !ok {
return nil, errors.New("failed to resolve image digest using content trust: reference is not named")
}
taggedRef, ok := namedRef.(reference.NamedTagged)
if !ok {
return nil, errors.New("failed to resolve image digest using content trust: reference is not tagged")
}
gun := taggedRef.Name()
targetName := taggedRef.Tag()
server, err := getTrustServer(gun)
if err != nil {
return nil, err
}
rt, err := GetReadOnlyAuthTransport(server, []string{gun}, "", "", "")
if err != nil {
log.Debugf("failed to reach %s notary server for repo: %s, falling back to cache: %v", server, gun, err)
rt = nil
}
nRepo, err := notaryClient.NewNotaryRepository(
trustDirectory(),
gun,
server,
rt,
nil,
trustpinning.TrustPinConfig{},
)
if err != nil {
return nil, err
}
target, err := nRepo.GetTargetByName(targetName, trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return nil, err
}
// Only get the tag if it's in the top level targets role or the releases delegation role
// ignore it if it's in any other delegation roles
if target.Role != trust.ReleasesRole && target.Role != data.CanonicalTargetsRole {
return nil, errors.New("not signed in valid role")
}
h, ok := target.Hashes["sha256"]
if !ok {
return nil, errors.New("no valid hash, expecting sha256")
}
dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h))
// Allow returning canonical reference with tag and digest
return reference.WithDigest(taggedRef, dgst)
}
func getTrustServer(gun string) (string, error) {
if strings.HasPrefix(gun, "docker.io/") {
return "https://notary.docker.io", nil
}
return "", errors.New("non-hub images not yet supported")
}
func trustDirectory() string {
return filepath.Join(MobyDir, "trust")
}
type credentialStore struct {
username string
password string
refreshTokens map[string]string
}
func (tcs *credentialStore) Basic(url *url.URL) (string, string) {
return tcs.username, tcs.password
}
// refresh tokens are the long lived tokens that can be used instead of a password
func (tcs *credentialStore) RefreshToken(u *url.URL, service string) string {
return tcs.refreshTokens[service]
}
func (tcs *credentialStore) SetRefreshToken(u *url.URL, service string, token string) {
if tcs.refreshTokens != nil {
tcs.refreshTokens[service] = token
}
}
// GetReadOnlyAuthTransport gets the Auth Transport used to communicate with notary
func GetReadOnlyAuthTransport(server string, scopes []string, username, password, rootCAPath string) (http.RoundTripper, error) {
httpsTransport, err := httpsTransport(rootCAPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v2/", server), nil)
if err != nil {
return nil, err
}
pingClient := &http.Client{
Transport: httpsTransport,
Timeout: 5 * time.Second,
}
resp, err := pingClient.Do(req)
if err != nil {
return nil, err
}
challengeManager := challenge.NewSimpleManager()
if err := challengeManager.AddResponse(resp); err != nil {
return nil, err
}
creds := credentialStore{
username: username,
password: password,
refreshTokens: make(map[string]string),
}
var scopeObjs []auth.Scope
for _, scopeName := range scopes {
scopeObjs = append(scopeObjs, auth.RepositoryScope{
Repository: scopeName,
Actions: []string{"pull"},
})
}
// allow setting multiple scopes so we don't have to reauth
tokenHandler := auth.NewTokenHandlerWithOptions(auth.TokenHandlerOptions{
Transport: httpsTransport,
Credentials: &creds,
Scopes: scopeObjs,
})
authedTransport := transport.NewTransport(httpsTransport, auth.NewAuthorizer(challengeManager, tokenHandler))
return authedTransport, nil
}
func httpsTransport(caFile string) (*http.Transport, error) {
tlsConfig := &tls.Config{}
transport := http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
}
// Override with the system cert pool if the caFile was empty
if caFile == "" {
systemCertPool, err := x509.SystemCertPool()
if err != nil {
return nil, err
}
transport.TLSClientConfig.RootCAs = systemCertPool
} else {
certPool := x509.NewCertPool()
pems, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
}
certPool.AppendCertsFromPEM(pems)
transport.TLSClientConfig.RootCAs = certPool
}
return &transport, nil
}

View File

@@ -1,58 +0,0 @@
package main
import "testing"
func TestEnforceContentTrust(t *testing.T) {
type enforceContentTrustCase struct {
result bool
imageName string
trustConfig *TrustConfig
}
testCases := []enforceContentTrustCase{
// Simple positive and negative cases for Image subkey
{true, "image", &TrustConfig{Image: []string{"image"}}},
{true, "image", &TrustConfig{Image: []string{"more", "than", "one", "image"}}},
{true, "image", &TrustConfig{Image: []string{"more", "than", "one", "image"}, Org: []string{"random", "orgs"}}},
{false, "image", &TrustConfig{}},
{false, "image", &TrustConfig{Image: []string{"not", "in", "here!"}}},
{false, "image", &TrustConfig{Image: []string{"not", "in", "here!"}, Org: []string{""}}},
// Tests for Image subkey with tags
{true, "image:tag", &TrustConfig{Image: []string{"image:tag"}}},
{true, "image:tag", &TrustConfig{Image: []string{"image"}}},
{false, "image:tag", &TrustConfig{Image: []string{"image:otherTag"}}},
{false, "image:tag", &TrustConfig{Image: []string{"image@sha256:abc123"}}},
// Tests for Image subkey with digests
{true, "image@sha256:abc123", &TrustConfig{Image: []string{"image@sha256:abc123"}}},
{true, "image@sha256:abc123", &TrustConfig{Image: []string{"image"}}},
{false, "image@sha256:abc123", &TrustConfig{Image: []string{"image:Tag"}}},
{false, "image@sha256:abc123", &TrustConfig{Image: []string{"image@sha256:def456"}}},
// Tests for Image subkey with digests
{true, "image@sha256:abc123", &TrustConfig{Image: []string{"image@sha256:abc123"}}},
{true, "image@sha256:abc123", &TrustConfig{Image: []string{"image"}}},
{false, "image@sha256:abc123", &TrustConfig{Image: []string{"image:Tag"}}},
{false, "image@sha256:abc123", &TrustConfig{Image: []string{"image@sha256:def456"}}},
// Tests for Org subkey
{true, "linuxkit/image", &TrustConfig{Image: []string{"notImage"}, Org: []string{"linuxkit"}}},
{true, "linuxkit/differentImage", &TrustConfig{Image: []string{}, Org: []string{"linuxkit"}}},
{true, "linuxkit/differentImage:tag", &TrustConfig{Image: []string{}, Org: []string{"linuxkit"}}},
{true, "linuxkit/differentImage@sha256:abc123", &TrustConfig{Image: []string{}, Org: []string{"linuxkit"}}},
{false, "linuxkit/differentImage", &TrustConfig{Image: []string{}, Org: []string{"notlinuxkit"}}},
{false, "linuxkit/differentImage:tag", &TrustConfig{Image: []string{}, Org: []string{"notlinuxkit"}}},
{false, "linuxkit/differentImage@sha256:abc123", &TrustConfig{Image: []string{}, Org: []string{"notlinuxkit"}}},
// Tests for Org with library organization
{true, "nginx", &TrustConfig{Image: []string{}, Org: []string{"library"}}},
{true, "nginx:alpine", &TrustConfig{Image: []string{}, Org: []string{"library"}}},
{true, "library/nginx:alpine", &TrustConfig{Image: []string{}, Org: []string{"library"}}},
{false, "nginx", &TrustConfig{Image: []string{}, Org: []string{"notLibrary"}}},
}
for _, testCase := range testCases {
if enforceContentTrust(testCase.imageName, testCase.trustConfig) != testCase.result {
t.Errorf("incorrect trust enforcement result for %s against configuration %v, expected: %v", testCase.imageName, testCase.trustConfig, testCase.result)
}
}
}