Files
linuxkit/cmd/moby/build.go
Justin Cormack c734b47e9c 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>
2017-05-18 17:48:15 +01:00

306 lines
7.4 KiB
Go

package main
import (
"archive/tar"
"bytes"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
log "github.com/Sirupsen/logrus"
)
const defaultNameForStdin = "moby"
// Process the build arguments and execute build
func build(args []string) {
buildCmd := flag.NewFlagSet("build", flag.ExitOnError)
buildCmd.Usage = func() {
fmt.Printf("USAGE: %s build [options] <file>[.yml] | -\n\n", os.Args[0])
fmt.Printf("Options:\n")
buildCmd.PrintDefaults()
}
buildName := buildCmd.String("name", "", "Name to use for output files")
buildPull := buildCmd.Bool("pull", false, "Always pull images")
if err := buildCmd.Parse(args); err != nil {
log.Fatal("Unable to parse args")
}
remArgs := buildCmd.Args()
if len(remArgs) == 0 {
fmt.Println("Please specify a configuration file")
buildCmd.Usage()
os.Exit(1)
}
name := *buildName
var config []byte
if conf := remArgs[0]; conf == "-" {
var err error
config, err = ioutil.ReadAll(os.Stdin)
if err != nil {
log.Fatalf("Cannot read stdin: %v", err)
}
if name == "" {
name = defaultNameForStdin
}
} else {
if !(filepath.Ext(conf) == ".yml" || filepath.Ext(conf) == ".yaml") {
conf = conf + ".yml"
}
var err error
config, err = ioutil.ReadFile(conf)
if err != nil {
log.Fatalf("Cannot open config file: %v", err)
}
if name == "" {
name = strings.TrimSuffix(filepath.Base(conf), filepath.Ext(conf))
}
}
m, err := NewConfig(config)
if err != nil {
log.Fatalf("Invalid config: %v", err)
}
image := buildInternal(m, name, *buildPull)
log.Infof("Create outputs:")
err = outputs(m, name, image)
if err != nil {
log.Fatalf("Error writing outputs: %v", err)
}
}
func initrdAppend(iw *tar.Writer, r io.Reader) {
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalln(err)
}
err = iw.WriteHeader(hdr)
if err != nil {
log.Fatalln(err)
}
_, err = io.Copy(iw, tr)
if err != nil {
log.Fatalln(err)
}
}
}
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):
if img == strings.TrimSuffix(fullImageName, ":") {
return true
}
// and by removing a possible digest:
if img == strings.TrimSuffix(fullImageName, "@sha256:") {
return true
}
}
for _, org := range config.Org {
if strings.HasPrefix(fullImageName, org+"/") {
return true
}
}
return false
}
// Perform the actual build process
// TODO return error not panic
func buildInternal(m Moby, name string, pull bool) []byte {
w := new(bytes.Buffer)
iw := tar.NewWriter(w)
if pull || enforceContentTrust(m.Kernel.Image, &m.Trust) {
log.Infof("Pull kernel image: %s", m.Kernel.Image)
err := dockerPull(m.Kernel.Image, enforceContentTrust(m.Kernel.Image, &m.Trust))
if err != nil {
log.Fatalf("Could not pull image %s: %v", m.Kernel.Image, err)
}
}
if m.Kernel.Image != "" {
// get kernel and initrd tarball from container
log.Infof("Extract kernel image: %s", m.Kernel.Image)
const (
kernelName = "kernel"
kernelAltName = "bzImage"
ktarName = "kernel.tar"
)
out, err := ImageExtract(m.Kernel.Image, "", enforceContentTrust(m.Kernel.Image, &m.Trust), pull)
if err != nil {
log.Fatalf("Failed to extract kernel image and tarball: %v", err)
}
buf := bytes.NewBuffer(out)
kernel, ktar, err := untarKernel(buf, kernelName, kernelAltName, ktarName, m.Kernel.Cmdline)
if err != nil {
log.Fatalf("Could not extract kernel image and filesystem from tarball. %v", err)
}
initrdAppend(iw, kernel)
initrdAppend(iw, ktar)
}
// 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)
init, err := ImageExtract(ii, "", enforceContentTrust(ii, &m.Trust), pull)
if err != nil {
log.Fatalf("Failed to build init tarball from %s: %v", ii, err)
}
buffer := bytes.NewBuffer(init)
initrdAppend(iw, buffer)
}
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)
config, err := ConfigToOCI(image)
if err != nil {
log.Fatalf("Failed to create config.json for %s: %v", image.Image, err)
}
so := fmt.Sprintf("%03d", i)
path := "containers/onboot/" + so + "-" + image.Name
out, err := ImageBundle(path, image.Image, config, enforceContentTrust(image.Image, &m.Trust), pull)
if err != nil {
log.Fatalf("Failed to extract root filesystem for %s: %v", image.Image, err)
}
buffer := bytes.NewBuffer(out)
initrdAppend(iw, buffer)
}
if len(m.Services) != 0 {
log.Infof("Add service containers:")
}
for _, image := range m.Services {
log.Infof(" Create OCI config for %s", image.Image)
config, err := ConfigToOCI(image)
if err != nil {
log.Fatalf("Failed to create config.json for %s: %v", image.Image, err)
}
path := "containers/services/" + image.Name
out, err := ImageBundle(path, image.Image, config, enforceContentTrust(image.Image, &m.Trust), pull)
if err != nil {
log.Fatalf("Failed to extract root filesystem for %s: %v", image.Image, err)
}
buffer := bytes.NewBuffer(out)
initrdAppend(iw, buffer)
}
// add files
buffer, err := filesystem(m)
if err != nil {
log.Fatalf("failed to add filesystem parts: %v", err)
}
initrdAppend(iw, buffer)
err = iw.Close()
if err != nil {
log.Fatalf("initrd close error: %v", err)
}
return w.Bytes()
}
func untarKernel(buf *bytes.Buffer, kernelName, kernelAltName, ktarName string, cmdline string) (*bytes.Buffer, *bytes.Buffer, error) {
tr := tar.NewReader(buf)
var kernel, ktar *bytes.Buffer
foundKernel := false
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalln(err)
}
switch hdr.Name {
case kernelName, kernelAltName:
if foundKernel {
return nil, nil, errors.New("found more than one possible kernel image")
}
foundKernel = true
kernel = new(bytes.Buffer)
// make a new tarball with kernel in /boot/kernel
tw := tar.NewWriter(kernel)
whdr := &tar.Header{
Name: "boot",
Mode: 0700,
Typeflag: tar.TypeDir,
}
if err := tw.WriteHeader(whdr); err != nil {
return nil, nil, err
}
whdr = &tar.Header{
Name: "boot/kernel",
Mode: hdr.Mode,
Size: hdr.Size,
}
if err := tw.WriteHeader(whdr); err != nil {
return nil, nil, err
}
_, err = io.Copy(tw, tr)
if err != nil {
return nil, nil, err
}
// add the cmdline in /boot/cmdline
whdr = &tar.Header{
Name: "boot/cmdline",
Mode: 0700,
Size: int64(len(cmdline)),
}
if err := tw.WriteHeader(whdr); err != nil {
return nil, nil, err
}
buf := bytes.NewBufferString(cmdline)
_, err = io.Copy(tw, buf)
if err != nil {
return nil, nil, err
}
if err := tw.Close(); err != nil {
return nil, nil, err
}
case ktarName:
ktar = new(bytes.Buffer)
_, err := io.Copy(ktar, tr)
if err != nil {
return nil, nil, err
}
default:
continue
}
}
if kernel == nil {
return nil, nil, errors.New("did not find kernel in kernel image")
}
if ktar == nil {
return nil, nil, errors.New("did not find kernel.tar in kernel image")
}
return kernel, ktar, nil
}