e3f04298 |
package docker
import ( |
cd735496 |
"crypto/sha256"
"encoding/hex" |
e3f04298 |
"encoding/json" |
f7ba1c34 |
"errors" |
e3f04298 |
"fmt" |
96d1e9bb |
"github.com/dotcloud/docker/archive" |
228091c7 |
"github.com/dotcloud/docker/auth" |
e3f04298 |
"github.com/dotcloud/docker/utils"
"io" |
54db1862 |
"io/ioutil" |
2b0ebf5d |
"net/url" |
e3f04298 |
"os" |
54db1862 |
"path" |
3f9416b5 |
"path/filepath" |
e3f04298 |
"reflect" |
d86898b0 |
"regexp" |
894d4a23 |
"sort" |
e3f04298 |
"strings"
)
|
f7ba1c34 |
var (
ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty")
)
|
e3f04298 |
type BuildFile interface { |
38554fc2 |
Build(io.Reader) (string, error) |
e3f04298 |
CmdFrom(string) error
CmdRun(string) error
}
type buildFile struct {
runtime *Runtime
srv *Server
|
894d4a23 |
image string
maintainer string
config *Config
contextPath string
context *utils.TarSum
|
3a123bc4 |
verbose bool
utilizeCache bool |
b7a3fc68 |
rm bool |
e3f04298 |
|
228091c7 |
authConfig *auth.AuthConfig
|
e3f04298 |
tmpContainers map[string]struct{}
tmpImages map[string]struct{}
|
de4429f7 |
outStream io.Writer
errStream io.Writer
// Deprecated, original writer used for ImagePull. To be removed.
outOld io.Writer
sf *utils.StreamFormatter |
e3f04298 |
}
|
b7a3fc68 |
func (b *buildFile) clearTmp(containers map[string]struct{}) { |
e3f04298 |
for c := range containers {
tmp := b.runtime.Get(c)
b.runtime.Destroy(tmp) |
de4429f7 |
fmt.Fprintf(b.outStream, "Removing intermediate container %s\n", utils.TruncateID(c)) |
e3f04298 |
}
}
func (b *buildFile) CmdFrom(name string) error {
image, err := b.runtime.repositories.LookupImage(name)
if err != nil {
if b.runtime.graph.IsNotExist(err) { |
01932401 |
remote, tag := utils.ParseRepositoryTag(name) |
228091c7 |
if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, b.authConfig, nil, true); 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{} |
99141ea3 |
if image.Config != nil {
b.config = image.Config
} |
d86898b0 |
if b.config.Env == nil || len(b.config.Env) == 0 {
b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
} |
e3f04298 |
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 |
}
|
7afd7a82 |
// probeCache checks to see if image-caching is enabled (`b.utilizeCache`)
// and if so attempts to look up the current `b.image` and `b.config` pair
// in the current server `b.srv`. If an image is found, probeCache returns
// `(true, nil)`. If no image is found, it returns `(false, nil)`. If there
// is any error, it returns `(false, err)`.
func (b *buildFile) probeCache() (bool, error) {
if b.utilizeCache {
if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
return false, err
} else if cache != nil {
fmt.Fprintf(b.outStream, " ---> Using cache\n")
utils.Debugf("[BUILDER] Use cached version")
b.image = cache.ID
return true, nil
} else {
utils.Debugf("[BUILDER] Cache miss")
}
}
return false, nil
} |
3f9416b5 |
|
e3f04298 |
func (b *buildFile) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
} |
4fdf11b2 |
config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil) |
e3f04298 |
if err != nil {
return err
}
|
ae0d5550 |
cmd := b.config.Cmd |
e3f04298 |
b.config.Cmd = nil
MergeConfig(b.config, config)
|
0f249c85 |
defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
|
dcab408f |
utils.Debugf("Command to be executed: %v", b.config.Cmd) |
ae0d5550 |
|
7afd7a82 |
hit, err := b.probeCache()
if err != nil {
return err
}
if hit {
return nil |
e3f04298 |
}
cid, err := b.run()
if err != nil {
return err
} |
b6165daa |
if err := b.commit(cid, cmd, "run"); err != nil { |
ae0d5550 |
return err
} |
0f249c85 |
|
ae0d5550 |
return nil |
e3f04298 |
}
|
d86898b0 |
func (b *buildFile) FindEnvKey(key string) int {
for k, envVar := range b.config.Env {
envParts := strings.SplitN(envVar, "=", 2)
if key == envParts[0] {
return k
}
}
return -1
}
func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
if err != nil {
return value, err
}
matches := exp.FindAllString(value, -1)
for _, match := range matches {
match = match[strings.Index(match, "$"):]
matchKey := strings.Trim(match, "${}")
for _, envVar := range b.config.Env {
envParts := strings.SplitN(envVar, "=", 2)
envKey := envParts[0]
envValue := envParts[1]
if envKey == matchKey {
value = strings.Replace(value, match, envValue, -1)
break
}
}
}
return value, nil
}
|
e3f04298 |
func (b *buildFile) CmdEnv(args string) error {
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ENV format")
} |
b103ac70 |
key := strings.Trim(tmp[0], " \t")
value := strings.Trim(tmp[1], " \t") |
e3f04298 |
|
d86898b0 |
envKey := b.FindEnvKey(key)
replacedValue, err := b.ReplaceEnvMatches(value)
if err != nil {
return err |
e3f04298 |
} |
d86898b0 |
replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
if envKey >= 0 {
b.config.Env[envKey] = replacedVar |
6a6a2ad8 |
} else {
b.config.Env = append(b.config.Env, replacedVar) |
d86898b0 |
}
return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar)) |
e3f04298 |
}
|
323c4b52 |
func (b *buildFile) buildCmdFromJson(args string) []string { |
e3f04298 |
var cmd []string
if err := json.Unmarshal([]byte(args), &cmd); err != nil { |
323c4b52 |
utils.Debugf("Error unmarshalling: %s, setting to /bin/sh -c", err) |
b6165daa |
cmd = []string{"/bin/sh", "-c", args} |
e3f04298 |
} |
323c4b52 |
return cmd
}
func (b *buildFile) CmdCmd(args string) error {
cmd := b.buildCmdFromJson(args)
b.config.Cmd = cmd
if err := b.commit("", b.config.Cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
return err
}
return nil
}
func (b *buildFile) CmdEntrypoint(args string) error {
entrypoint := b.buildCmdFromJson(args)
b.config.Entrypoint = entrypoint
if err := b.commit("", b.config.Cmd, fmt.Sprintf("ENTRYPOINT %v", entrypoint)); err != nil { |
6d2e3d2e |
return err
}
return nil |
e3f04298 |
}
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 |
}
|
5ee3c58d |
func (b *buildFile) CmdUser(args string) error {
b.config.User = args
return b.commit("", b.config.Cmd, fmt.Sprintf("USER %v", args))
}
|
a4e6025c |
func (b *buildFile) CmdInsert(args string) error {
return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
}
func (b *buildFile) CmdCopy(args string) error {
return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
}
|
31998833 |
func (b *buildFile) CmdWorkdir(workdir string) error {
b.config.WorkingDir = workdir
return b.commit("", b.config.Cmd, fmt.Sprintf("WORKDIR %v", workdir))
}
|
eb9fef2c |
func (b *buildFile) CmdVolume(args string) error {
if args == "" {
return fmt.Errorf("Volume cannot be empty")
}
var volume []string
if err := json.Unmarshal([]byte(args), &volume); err != nil {
volume = []string{args}
}
if b.config.Volumes == nil { |
1ba11384 |
b.config.Volumes = map[string]struct{}{} |
eb9fef2c |
}
for _, v := range volume {
b.config.Volumes[v] = struct{}{}
}
if err := b.commit("", b.config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil {
return err
}
return nil
}
|
3f9416b5 |
func (b *buildFile) checkPathForAddition(orig string) error { |
894d4a23 |
origPath := path.Join(b.contextPath, orig) |
ad698362 |
if p, err := filepath.EvalSymlinks(origPath); err != nil { |
42fed841 |
if os.IsNotExist(err) {
return fmt.Errorf("%s: no such file or directory", orig)
} |
ad698362 |
return err
} else {
origPath = p
} |
894d4a23 |
if !strings.HasPrefix(origPath, b.contextPath) { |
3f9416b5 |
return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath) |
6ae38001 |
} |
3f9416b5 |
_, err := os.Stat(origPath)
if err != nil { |
42fed841 |
if os.IsNotExist(err) {
return fmt.Errorf("%s: no such file or directory", orig)
}
return err |
2b0ebf5d |
} |
3f9416b5 |
return nil |
31d2b258 |
} |
6ae38001 |
|
31d2b258 |
func (b *buildFile) addContext(container *Container, orig, dest string) error { |
894d4a23 |
var (
origPath = path.Join(b.contextPath, orig)
destPath = path.Join(container.RootfsPath(), dest)
) |
5be7b9af |
// Preserve the trailing '/' |
2b0ebf5d |
if strings.HasSuffix(dest, "/") { |
5be7b9af |
destPath = destPath + "/"
} |
2897cb04 |
fi, err := os.Stat(origPath)
if err != nil { |
42fed841 |
if os.IsNotExist(err) {
return fmt.Errorf("%s: no such file or directory", orig)
}
return err |
6ae38001 |
} |
2897cb04 |
if fi.IsDir() { |
96d1e9bb |
if err := archive.CopyWithTar(origPath, destPath); err != nil { |
2897cb04 |
return err
} |
5b828761 |
// First try to unpack the source as an archive |
96d1e9bb |
} else if err := archive.UntarPath(origPath, destPath); err != nil { |
cbc49d7d |
utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err) |
5b828761 |
// If that fails, just copy it as a regular file |
b15cfd35 |
if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil { |
9a394041 |
return err
} |
96d1e9bb |
if err := archive.CopyWithTar(origPath, destPath); err != nil { |
2897cb04 |
return err
}
} |
31d2b258 |
return nil
}
func (b *buildFile) CmdAdd(args string) error { |
894d4a23 |
if b.context == nil { |
31d2b258 |
return fmt.Errorf("No context given. Impossible to use ADD")
}
tmp := strings.SplitN(args, " ", 2)
if len(tmp) != 2 {
return fmt.Errorf("Invalid ADD format")
} |
d86898b0 |
orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
if err != nil {
return err
}
dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
if err != nil {
return err
} |
31d2b258 |
cmd := b.config.Cmd
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)} |
f03ebc20 |
b.config.Image = b.image |
3f9416b5 |
|
894d4a23 |
// FIXME: do we really need this?
var ( |
0fd9c98d |
origPath = orig
destPath = dest
remoteHash string |
894d4a23 |
) |
3f9416b5 |
if utils.IsURL(orig) {
resp, err := utils.Download(orig)
if err != nil {
return err
} |
894d4a23 |
tmpDirName, err := ioutil.TempDir(b.contextPath, "docker-remote") |
3f9416b5 |
if err != nil {
return err
}
tmpFileName := path.Join(tmpDirName, "tmp")
tmpFile, err := os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return err
}
defer os.RemoveAll(tmpDirName)
if _, err = io.Copy(tmpFile, resp.Body); err != nil { |
0fd9c98d |
tmpFile.Close() |
3f9416b5 |
return err
}
origPath = path.Join(filepath.Base(tmpDirName), filepath.Base(tmpFileName))
tmpFile.Close()
|
0fd9c98d |
// Process the checksum
r, err := archive.Tar(tmpFileName, archive.Uncompressed)
if err != nil {
return err
}
tarSum := utils.TarSum{Reader: r, DisableCompression: true}
remoteHash = tarSum.Sum(nil)
|
3f9416b5 |
// If the destination is a directory, figure out the filename.
if strings.HasSuffix(dest, "/") {
u, err := url.Parse(orig)
if err != nil {
return err
}
path := u.Path
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
parts := strings.Split(path, "/")
filename := parts[len(parts)-1]
if filename == "" {
return fmt.Errorf("cannot determine filename from url: %s", u)
}
destPath = dest + filename
}
}
if err := b.checkPathForAddition(origPath); err != nil {
return err
}
// Hash path and check the cache
if b.utilizeCache { |
894d4a23 |
var (
hash string
sums = b.context.GetSums()
) |
f3103e5c |
|
0fd9c98d |
if remoteHash != "" {
hash = remoteHash
} else if fi, err := os.Stat(path.Join(b.contextPath, origPath)); err != nil { |
3f9416b5 |
return err |
894d4a23 |
} else if fi.IsDir() {
var subfiles []string
for file, sum := range sums { |
93ff70a3 |
absFile := path.Join(b.contextPath, file)
absOrigPath := path.Join(b.contextPath, origPath)
if strings.HasPrefix(absFile, absOrigPath) { |
894d4a23 |
subfiles = append(subfiles, sum)
}
}
sort.Strings(subfiles) |
cd735496 |
hasher := sha256.New()
hasher.Write([]byte(strings.Join(subfiles, ",")))
hash = "dir:" + hex.EncodeToString(hasher.Sum(nil)) |
894d4a23 |
} else { |
e24e9c09 |
if origPath[0] == '/' && len(origPath) > 1 {
origPath = origPath[1:]
}
origPath = strings.TrimPrefix(origPath, "./")
if h, ok := sums[origPath]; ok {
hash = "file:" + h
} |
3f9416b5 |
}
b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", hash, dest)}
hit, err := b.probeCache()
if err != nil {
return err
} |
f3103e5c |
// If we do not have a hash, never use the cache
if hit && hash != "" { |
3f9416b5 |
return nil
}
}
|
d0084ce5 |
// Create the container and start it |
0d292440 |
container, _, err := b.runtime.Create(b.config, "") |
31d2b258 |
if err != nil {
return err
} |
f5fe3ce3 |
b.tmpContainers[container.ID] = struct{}{} |
31d2b258 |
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount()
|
3f9416b5 |
if err := b.addContext(container, origPath, destPath); err != nil {
return err |
31d2b258 |
}
|
f5fe3ce3 |
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 |
}
|
de4429f7 |
type StdoutFormater struct {
io.Writer
*utils.StreamFormatter
}
func (sf *StdoutFormater) Write(buf []byte) (int, error) { |
05f416d8 |
formattedBuf := sf.StreamFormatter.FormatStream(string(buf)) |
de4429f7 |
n, err := sf.Writer.Write(formattedBuf)
if n != len(formattedBuf) {
return n, io.ErrShortWrite
}
return len(buf), err
}
type StderrFormater struct {
io.Writer
*utils.StreamFormatter
}
func (sf *StderrFormater) Write(buf []byte) (int, error) { |
05f416d8 |
formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m") |
de4429f7 |
n, err := sf.Writer.Write(formattedBuf)
if n != len(formattedBuf) {
return n, io.ErrShortWrite
}
return len(buf), err
}
|
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 |
0d292440 |
c, _, err := b.runtime.Create(b.config, "") |
e3f04298 |
if err != nil {
return "", err
} |
fd224ee5 |
b.tmpContainers[c.ID] = struct{}{} |
de4429f7 |
fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(c.ID))
|
f64dbdbe |
// override the entry point that may have been picked up from the base image
c.Path = b.config.Cmd[0]
c.Args = b.config.Cmd[1:]
|
10e10c95 |
var errCh chan error
if b.verbose {
errCh = utils.Go(func() error { |
de4429f7 |
return <-c.Attach(nil, nil, b.outStream, b.errStream) |
10e10c95 |
})
}
|
e3f04298 |
//start the container |
31638ab2 |
if err := c.Start(); err != nil { |
e3f04298 |
return "", err
}
|
10e10c95 |
if errCh != nil {
if err := <-errCh; err != nil { |
474191dd |
return "", err
}
}
|
e3f04298 |
// Wait for it to finish
if ret := c.Wait(); ret != 0 { |
b04c6466 |
err := &utils.JSONError{
Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.config.Cmd, ret),
Code: ret,
}
return "", err |
e3f04298 |
}
|
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 == "" { |
eaa2183d |
cmd := b.config.Cmd |
b6165daa |
b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} |
eaa2183d |
defer func(cmd []string) { b.config.Cmd = cmd }(cmd) |
560a74af |
|
7afd7a82 |
hit, err := b.probeCache()
if err != nil {
return err
}
if hit {
return nil |
560a74af |
} |
3a123bc4 |
|
b2503a72 |
container, warnings, err := b.runtime.Create(b.config, "") |
86ada2fa |
if err != nil { |
e3f04298 |
return err
} |
b2503a72 |
for _, warning := range warnings { |
de4429f7 |
fmt.Fprintf(b.outStream, " ---> [Warning] %s\n", warning) |
b2503a72 |
} |
f5fe3ce3 |
b.tmpContainers[container.ID] = struct{}{} |
de4429f7 |
fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID)) |
f5fe3ce3 |
id = container.ID
if err := container.EnsureMounted(); err != nil {
return err
}
defer container.Unmount() |
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 |
24e02043 |
image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig) |
e3f04298 |
if err != nil {
return err
} |
fd224ee5 |
b.tmpImages[image.ID] = struct{}{}
b.image = image.ID |
e3f04298 |
return nil
}
|
ebb934c1 |
// Long lines can be split with a backslash |
6a4afb7f |
var lineContinuation = regexp.MustCompile(`\s*\\\s*\n`) |
6921ca48 |
|
38554fc2 |
func (b *buildFile) Build(context io.Reader) (string, error) { |
894d4a23 |
tmpdirPath, err := ioutil.TempDir("", "docker-build") |
38554fc2 |
if err != nil {
return "", err
} |
c6350bcc |
b.context = &utils.TarSum{Reader: context, DisableCompression: true} |
894d4a23 |
if err := archive.Untar(b.context, tmpdirPath, nil); err != nil { |
38554fc2 |
return "", err
} |
894d4a23 |
defer os.RemoveAll(tmpdirPath) |
c6350bcc |
|
894d4a23 |
b.contextPath = tmpdirPath
filename := path.Join(tmpdirPath, "Dockerfile") |
6921ca48 |
if _, err := os.Stat(filename); os.IsNotExist(err) { |
38554fc2 |
return "", fmt.Errorf("Can't build a directory with no Dockerfile") |
6ae38001 |
} |
6921ca48 |
fileBytes, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
} |
f7ba1c34 |
if len(fileBytes) == 0 {
return "", ErrDockerfileEmpty
} |
6921ca48 |
dockerfile := string(fileBytes) |
1e723bc9 |
dockerfile = lineContinuation.ReplaceAllString(dockerfile, "") |
cb9d0fd3 |
stepN := 0 |
6921ca48 |
for _, line := range strings.Split(dockerfile, "\n") { |
b103ac70 |
line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n") |
e3f04298 |
// 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], " ")
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
if !exists { |
de4429f7 |
fmt.Fprintf(b.errStream, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction)) |
6cbc7757 |
continue |
e3f04298 |
} |
4ff649a4 |
stepN += 1 |
de4429f7 |
fmt.Fprintf(b.outStream, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments) |
4ff649a4 |
|
e3f04298 |
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
if ret != nil {
return "", ret.(error)
}
|
de4429f7 |
fmt.Fprintf(b.outStream, " ---> %s\n", utils.TruncateID(b.image)) |
e3f04298 |
}
if b.image != "" { |
de4429f7 |
fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image)) |
b7a3fc68 |
if b.rm {
b.clearTmp(b.tmpContainers)
} |
e3f04298 |
return b.image, nil
} |
b8cd2bc9 |
return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n") |
e3f04298 |
}
|
228091c7 |
func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig) BuildFile { |
e3f04298 |
return &buildFile{
runtime: srv.runtime,
srv: srv,
config: &Config{}, |
de4429f7 |
outStream: outStream,
errStream: errStream, |
e3f04298 |
tmpContainers: make(map[string]struct{}),
tmpImages: make(map[string]struct{}), |
474191dd |
verbose: verbose, |
3a123bc4 |
utilizeCache: utilizeCache, |
b7a3fc68 |
rm: rm, |
b04c6466 |
sf: sf, |
228091c7 |
authConfig: auth, |
de4429f7 |
outOld: outOld, |
e3f04298 |
}
} |