github.com/skippbox/kompose-origin@v0.0.0-20160524133224-16a9dca7bac2/docker/builder/builder.go (about)

     1  package builder
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"golang.org/x/net/context"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/builder"
    15  	"github.com/docker/docker/builder/dockerignore"
    16  	"github.com/docker/docker/pkg/archive"
    17  	"github.com/docker/docker/pkg/fileutils"
    18  	"github.com/docker/docker/pkg/jsonmessage"
    19  	"github.com/docker/docker/pkg/progress"
    20  	"github.com/docker/docker/pkg/streamformatter"
    21  	"github.com/docker/docker/pkg/term"
    22  	"github.com/docker/engine-api/client"
    23  	"github.com/docker/engine-api/types"
    24  )
    25  
    26  // DefaultDockerfileName is the default name of a Dockerfile
    27  const DefaultDockerfileName = "Dockerfile"
    28  
    29  // Builder defines methods to provide a docker builder. This makes libcompose
    30  // not tied up to the docker daemon builder.
    31  type Builder interface {
    32  	Build(imageName string) error
    33  }
    34  
    35  // DaemonBuilder is the daemon "docker build" Builder implementation.
    36  type DaemonBuilder struct {
    37  	Client           client.APIClient
    38  	ContextDirectory string
    39  	Dockerfile       string
    40  	AuthConfigs      map[string]types.AuthConfig
    41  	NoCache          bool
    42  	ForceRemove      bool
    43  	Pull             bool
    44  }
    45  
    46  // Build implements Builder. It consumes the docker build API endpoint and sends
    47  // a tar of the specified service build context.
    48  func (d *DaemonBuilder) Build(ctx context.Context, imageName string) error {
    49  	buildCtx, err := createTar(d.ContextDirectory, d.Dockerfile)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	defer buildCtx.Close()
    54  
    55  	var progBuff io.Writer = os.Stdout
    56  	var buildBuff io.Writer = os.Stdout
    57  
    58  	// Setup an upload progress bar
    59  	progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
    60  
    61  	var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
    62  
    63  	logrus.Infof("Building %s...", imageName)
    64  
    65  	outFd, isTerminalOut := term.GetFdInfo(os.Stdout)
    66  
    67  	response, err := d.Client.ImageBuild(ctx, body, types.ImageBuildOptions{
    68  		Tags:        []string{imageName},
    69  		NoCache:     d.NoCache,
    70  		Remove:      true,
    71  		ForceRemove: d.ForceRemove,
    72  		PullParent:  d.Pull,
    73  		Dockerfile:  d.Dockerfile,
    74  		AuthConfigs: d.AuthConfigs,
    75  	})
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, outFd, isTerminalOut, nil)
    81  	if err != nil {
    82  		if jerr, ok := err.(*jsonmessage.JSONError); ok {
    83  			// If no error code is set, default to 1
    84  			if jerr.Code == 0 {
    85  				jerr.Code = 1
    86  			}
    87  			return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code)
    88  		}
    89  	}
    90  	return err
    91  }
    92  
    93  // CreateTar create a build context tar for the specified project and service name.
    94  func createTar(contextDirectory, dockerfile string) (io.ReadCloser, error) {
    95  	// This code was ripped off from docker/api/client/build.go
    96  	dockerfileName := filepath.Join(contextDirectory, dockerfile)
    97  
    98  	absContextDirectory, err := filepath.Abs(contextDirectory)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	filename := dockerfileName
   104  
   105  	if dockerfile == "" {
   106  		// No -f/--file was specified so use the default
   107  		dockerfileName = DefaultDockerfileName
   108  		filename = filepath.Join(absContextDirectory, dockerfileName)
   109  
   110  		// Just to be nice ;-) look for 'dockerfile' too but only
   111  		// use it if we found it, otherwise ignore this check
   112  		if _, err = os.Lstat(filename); os.IsNotExist(err) {
   113  			tmpFN := path.Join(absContextDirectory, strings.ToLower(dockerfileName))
   114  			if _, err = os.Lstat(tmpFN); err == nil {
   115  				dockerfileName = strings.ToLower(dockerfileName)
   116  				filename = tmpFN
   117  			}
   118  		}
   119  	}
   120  
   121  	origDockerfile := dockerfileName // used for error msg
   122  	if filename, err = filepath.Abs(filename); err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	// Now reset the dockerfileName to be relative to the build context
   127  	dockerfileName, err = filepath.Rel(absContextDirectory, filename)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	// And canonicalize dockerfile name to a platform-independent one
   133  	dockerfileName, err = archive.CanonicalTarNameForPath(dockerfileName)
   134  	if err != nil {
   135  		return nil, fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err)
   136  	}
   137  
   138  	if _, err = os.Lstat(filename); os.IsNotExist(err) {
   139  		return nil, fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile)
   140  	}
   141  	var includes = []string{"."}
   142  	var excludes []string
   143  
   144  	dockerIgnorePath := path.Join(contextDirectory, ".dockerignore")
   145  	dockerIgnore, err := os.Open(dockerIgnorePath)
   146  	if err != nil {
   147  		if !os.IsNotExist(err) {
   148  			return nil, err
   149  		}
   150  		logrus.Warnf("Error while reading .dockerignore (%s) : %s", dockerIgnorePath, err.Error())
   151  		excludes = make([]string, 0)
   152  	} else {
   153  		excludes, err = dockerignore.ReadAll(dockerIgnore)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  	}
   158  
   159  	// If .dockerignore mentions .dockerignore or the Dockerfile
   160  	// then make sure we send both files over to the daemon
   161  	// because Dockerfile is, obviously, needed no matter what, and
   162  	// .dockerignore is needed to know if either one needs to be
   163  	// removed.  The deamon will remove them for us, if needed, after it
   164  	// parses the Dockerfile.
   165  	keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
   166  	keepThem2, _ := fileutils.Matches(dockerfileName, excludes)
   167  	if keepThem1 || keepThem2 {
   168  		includes = append(includes, ".dockerignore", dockerfileName)
   169  	}
   170  
   171  	if err := builder.ValidateContextDirectory(contextDirectory, excludes); err != nil {
   172  		return nil, fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err)
   173  	}
   174  
   175  	options := &archive.TarOptions{
   176  		Compression:     archive.Uncompressed,
   177  		ExcludePatterns: excludes,
   178  		IncludeFiles:    includes,
   179  	}
   180  
   181  	return archive.TarWithOptions(contextDirectory, options)
   182  }