1
0
mirror of https://github.com/rancher/os.git synced 2025-08-23 00:55:41 +00:00
os/vendor/github.com/docker/libcompose/docker/builder.go
2015-12-07 18:17:57 +05:00

180 lines
5.1 KiB
Go

package docker
import (
"encoding/json"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/utils"
"github.com/docker/libcompose/project"
dockerclient "github.com/fsouza/go-dockerclient"
)
// DefaultDockerfileName is the default name of a Dockerfile
const DefaultDockerfileName = "Dockerfile"
// Builder defines methods to provide a docker builder. This makes libcompose
// not tied up to the docker daemon builder.
type Builder interface {
Build(p *project.Project, service project.Service) (string, error)
}
// DaemonBuilder is the daemon "docker build" Builder implementation.
type DaemonBuilder struct {
context *Context
}
// NewDaemonBuilder creates a DaemonBuilder based on the specified context.
func NewDaemonBuilder(context *Context) *DaemonBuilder {
return &DaemonBuilder{
context: context,
}
}
type builderWriter struct {
out io.Writer
}
func (w *builderWriter) Write(bytes []byte) (int, error) {
data := map[string]interface{}{}
err := json.Unmarshal(bytes, &data)
if stream, ok := data["stream"]; err == nil && ok {
fmt.Fprint(w.out, stream)
}
return len(bytes), nil
}
// Build implements Builder. It consumes the docker build API endpoint and sends
// a tar of the specified service build context.
func (d *DaemonBuilder) Build(p *project.Project, service project.Service) (string, error) {
if service.Config().Build == "" {
return service.Config().Image, nil
}
tag := fmt.Sprintf("%s_%s", p.Name, service.Name())
context, err := CreateTar(p, service.Name())
if err != nil {
return "", err
}
defer context.Close()
client := d.context.ClientFactory.Create(service)
logrus.Infof("Building %s...", tag)
err = client.BuildImage(dockerclient.BuildImageOptions{
InputStream: context,
OutputStream: &builderWriter{out: os.Stderr},
RawJSONStream: true,
Name: tag,
RmTmpContainer: true,
Dockerfile: service.Config().Dockerfile,
})
if err != nil {
return "", err
}
return tag, nil
}
// CreateTar create a build context tar for the specified project and service name.
func CreateTar(p *project.Project, name string) (io.ReadCloser, error) {
// This code was ripped off from docker/api/client/build.go
serviceConfig := p.Configs[name]
root := serviceConfig.Build
dockerfileName := filepath.Join(root, serviceConfig.Dockerfile)
absRoot, err := filepath.Abs(root)
if err != nil {
return nil, err
}
filename := dockerfileName
if dockerfileName == "" {
// No -f/--file was specified so use the default
dockerfileName = DefaultDockerfileName
filename = filepath.Join(absRoot, dockerfileName)
// Just to be nice ;-) look for 'dockerfile' too but only
// use it if we found it, otherwise ignore this check
if _, err = os.Lstat(filename); os.IsNotExist(err) {
tmpFN := path.Join(absRoot, strings.ToLower(dockerfileName))
if _, err = os.Lstat(tmpFN); err == nil {
dockerfileName = strings.ToLower(dockerfileName)
filename = tmpFN
}
}
}
origDockerfile := dockerfileName // used for error msg
if filename, err = filepath.Abs(filename); err != nil {
return nil, err
}
// Now reset the dockerfileName to be relative to the build context
dockerfileName, err = filepath.Rel(absRoot, filename)
if err != nil {
return nil, err
}
// And canonicalize dockerfile name to a platform-independent one
dockerfileName, err = archive.CanonicalTarNameForPath(dockerfileName)
if err != nil {
return nil, fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err)
}
if _, err = os.Lstat(filename); os.IsNotExist(err) {
return nil, fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
}
var includes = []string{"."}
var excludes []string
dockerIgnorePath := path.Join(root, ".dockerignore")
dockerIgnore, err := os.Open(dockerIgnorePath)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
logrus.Warnf("Error while reading .dockerignore (%s) : %s", dockerIgnorePath, err.Error())
excludes = make([]string, 0)
} else {
excludes, err = utils.ReadDockerIgnore(dockerIgnore)
if err != nil {
return nil, err
}
}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The deamon will remove them for us, if needed, after it
// parses the Dockerfile.
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
keepThem2, _ := fileutils.Matches(dockerfileName, excludes)
if keepThem1 || keepThem2 {
includes = append(includes, ".dockerignore", dockerfileName)
}
if err := utils.ValidateContextDirectory(root, excludes); err != nil {
return nil, fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
}
options := &archive.TarOptions{
Compression: archive.Uncompressed,
ExcludePatterns: excludes,
IncludeFiles: includes,
}
return archive.TarWithOptions(root, options)
}