e3f04298 |
package docker
import (
"bufio"
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io" |
54db1862 |
"io/ioutil" |
e3f04298 |
"os" |
54db1862 |
"path" |
e3f04298 |
"reflect"
"strings"
)
type BuildFile interface {
Build(io.Reader, io.Reader) (string, error)
CmdFrom(string) error
CmdRun(string) error
}
type buildFile struct {
runtime *Runtime
builder *Builder
srv *Server
image string
maintainer string
config *Config |
6ae38001 |
context string |
e3f04298 |
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
out io.Writer
}
func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
for c := range containers {
tmp := b.runtime.Get(c)
b.runtime.Destroy(tmp)
utils.Debugf("Removing container %s", c)
}
for i := range images {
b.runtime.graph.Delete(i)
utils.Debugf("Removing image %s", i)
}
}
func (b *buildFile) CmdFrom(name string) error {
image, err := b.runtime.repositories.LookupImage(name)
if err != nil {
if b.runtime.graph.IsNotExist(err) {
var tag, remote string
if strings.Contains(name, ":") {
remoteParts := strings.Split(name, ":")
tag = remoteParts[1]
remote = remoteParts[0]
} else {
remote = name
}
|
62c78696 |
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil { |
e3f04298 |
return err
}
image, err = b.runtime.repositories.LookupImage(name)
if err != nil {
return err
}
} else {
return err
}
} |
fd224ee5 |
b.image = image.ID |
e3f04298 |
b.config = &Config{}
return nil
}
func (b *buildFile) CmdMaintainer(name string) error {
b.maintainer = name |
b6165daa |
return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name)) |
e3f04298 |
}
func (b *buildFile) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
|
ae0d5550 |
cmd := b.config.Cmd |
e3f04298 |
b.config.Cmd = nil
MergeConfig(b.config, config)
|
dcab408f |
utils.Debugf("Command to be executed: %v", b.config.Cmd) |
ae0d5550 |
|
560a74af |
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil { |
e3f04298 |
return err
} else if cache != nil { |
cb9d0fd3 |
fmt.Fprintf(b.out, " ---> Using cache\n") |
560a74af |
utils.Debugf("[BUILDER] Use cached version") |
fd224ee5 |
b.image = cache.ID |
e3f04298 |
return nil |
560a74af |
} else {
utils.Debugf("[BUILDER] Cache miss") |
e3f04298 |
}
cid, err := b.run()
if err != nil {
return err
} |
b6165daa |
if err := b.commit(cid, cmd, "run"); err != nil { |
ae0d5550 |
return err
}
b.config.Cmd = cmd
return nil |
e3f04298 |
}
func (b *buildFile) CmdEnv(args string) error {
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
}
key := strings.Trim(tmp[0], " ")
value := strings.Trim(tmp[1], " ")
for i, elem := range b.config.Env {
if strings.HasPrefix(elem, key+"=") {
b.config.Env[i] = key + "=" + value
return nil
}
}
b.config.Env = append(b.config.Env, key+"="+value) |
b6165daa |
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value)) |
e3f04298 |
}
func (b *buildFile) CmdCmd(args string) error {
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err) |
b6165daa |
cmd = []string{"/bin/sh", "-c", args} |
e3f04298 |
} |
6d2e3d2e |
if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
return err |
e3f04298 |
} |
6d2e3d2e |
b.config.Cmd = cmd |
e3f04298 |
return nil
}
func (b *buildFile) CmdExpose(args string) error {
ports := strings.Split(args, " ")
b.config.PortSpecs = append(ports, b.config.PortSpecs...) |
b6165daa |
return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports)) |
e3f04298 |
}
func (b *buildFile) CmdInsert(args string) error { |
a4e6025c |
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
} |
e3f04298 |
|
a4e6025c |
func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead") |
e3f04298 |
}
|
6ae38001 |
func (b *buildFile) CmdAdd(args string) error { |
cfb8cbe5 |
if b.context == "" {
return fmt.Errorf("No context given. Impossible to use ADD")
} |
6ae38001 |
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 { |
dcab408f |
return fmt.Errorf("Invalid ADD format") |
6ae38001 |
}
orig := strings.Trim(tmp[0], " ")
dest := strings.Trim(tmp[1], " ")
|
ae0d5550 |
cmd := b.config.Cmd |
452128f0 |
// Create the container and start it |
080f35fe |
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
b.config.Image = b.image |
452128f0 |
container, err := b.builder.Create(b.config) |
6ae38001 |
if err != nil {
return err
} |
452128f0 |
b.tmpContainers[container.ID] = struct{}{} |
cb9d0fd3 |
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID)) |
6ae38001 |
|
faafbf21 |
if err := container.EnsureMounted(); err != nil { |
6ae38001 |
return err
} |
faafbf21 |
defer container.Unmount() |
6ae38001 |
|
2897cb04 |
origPath := path.Join(b.context, orig) |
faafbf21 |
destPath := path.Join(container.RootfsPath(), dest) |
5be7b9af |
// Preserve the trailing '/'
if dest[len(dest)-1] == '/' {
destPath = destPath + "/"
} |
2897cb04 |
fi, err := os.Stat(origPath)
if err != nil { |
6ae38001 |
return err
} |
2897cb04 |
if fi.IsDir() { |
5b828761 |
if err := CopyWithTar(origPath, destPath); err != nil { |
2897cb04 |
return err
} |
5b828761 |
// First try to unpack the source as an archive
} else if err := UntarPath(origPath, destPath); err != nil {
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
// If that fails, just copy it as a regular file |
9a394041 |
if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
return err
} |
5b828761 |
if err := CopyWithTar(origPath, destPath); err != nil { |
2897cb04 |
return err
}
} |
452128f0 |
if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil { |
6d2e3d2e |
return err
}
b.config.Cmd = cmd
return nil |
6ae38001 |
}
|
e3f04298 |
func (b *buildFile) run() (string, error) {
if b.image == "" {
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
}
b.config.Image = b.image
// Create the container and start it
c, err := b.builder.Create(b.config)
if err != nil {
return "", err
} |
fd224ee5 |
b.tmpContainers[c.ID] = struct{}{} |
cb9d0fd3 |
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) |
e3f04298 |
//start the container
if err := c.Start(); err != nil {
return "", err
}
// Wait for it to finish
if ret := c.Wait(); ret != 0 {
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
}
|
fd224ee5 |
return c.ID, nil |
e3f04298 |
}
|
ae0d5550 |
// Commit the container <id> with the autorun command <autoCmd> |
b6165daa |
func (b *buildFile) commit(id string, autoCmd []string, comment string) error { |
e3f04298 |
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to commit")
}
b.config.Image = b.image
if id == "" { |
b6165daa |
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} |
560a74af |
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return err
} else if cache != nil { |
cb9d0fd3 |
fmt.Fprintf(b.out, " ---> Using cache\n") |
560a74af |
utils.Debugf("[BUILDER] Use cached version") |
fd224ee5 |
b.image = cache.ID |
560a74af |
return nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
|
452128f0 |
// Create the container and start it
container, err := b.builder.Create(b.config) |
86ada2fa |
if err != nil { |
e3f04298 |
return err
} |
452128f0 |
b.tmpContainers[container.ID] = struct{}{} |
cb9d0fd3 |
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID)) |
452128f0 |
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
id = container.ID |
e3f04298 |
}
container := b.runtime.Get(id)
if container == nil {
return fmt.Errorf("An error occured while creating the container")
}
|
ae0d5550 |
// Note: Actually copy the struct
autoConfig := *b.config
autoConfig.Cmd = autoCmd |
e3f04298 |
// Commit the container |
ae0d5550 |
image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig) |
e3f04298 |
if err != nil {
return err
} |
fd224ee5 |
b.tmpImages[image.ID] = struct{}{}
b.image = image.ID |
e3f04298 |
return nil
}
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) { |
6ae38001 |
if context != nil {
name, err := ioutil.TempDir("/tmp", "docker-build")
if err != nil {
return "", err
}
if err := Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
b.context = name
} |
e3f04298 |
file := bufio.NewReader(dockerfile) |
cb9d0fd3 |
stepN := 0 |
e3f04298 |
for {
line, err := file.ReadString('\n')
if err != nil { |
2e9403b0 |
if err == io.EOF && line == "" { |
e3f04298 |
break |
2e9403b0 |
} else if err != io.EOF {
return "", err |
e3f04298 |
}
}
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
// Skip comments and empty line
if len(line) == 0 || line[0] == '#' {
continue
}
tmp := strings.SplitN(line, " ", 2)
if len(tmp) != 2 {
return "", fmt.Errorf("Invalid Dockerfile format")
}
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
arguments := strings.Trim(tmp[1], " ") |
cb9d0fd3 |
stepN += 1
// FIXME: only count known instructions as build steps
fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments) |
e3f04298 |
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists { |
cb9d0fd3 |
fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction)) |
6cbc7757 |
continue |
e3f04298 |
}
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
}
|
cb9d0fd3 |
fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image)) |
e3f04298 |
}
if b.image != "" { |
cb9d0fd3 |
fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image)) |
e3f04298 |
return b.image, nil
}
return "", fmt.Errorf("An error occured during the build\n")
}
func NewBuildFile(srv *Server, out io.Writer) BuildFile {
return &buildFile{
builder: NewBuilder(srv.runtime),
runtime: srv.runtime,
srv: srv,
config: &Config{}, |
6ae38001 |
out: out, |
e3f04298 |
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}),
}
} |