github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/builder/dockerfile/internals.go (about)

     1  package dockerfile
     2  
     3  // internals for handling commands. Covers many areas and a lot of
     4  // non-contiguous functionality. Please read the comments.
     5  
     6  import (
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/backend"
    14  	"github.com/docker/docker/api/types/container"
    15  	"github.com/docker/docker/pkg/stringid"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  func (b *Builder) commit(dispatchState *dispatchState, comment string) error {
    20  	if b.disableCommit {
    21  		return nil
    22  	}
    23  	if !dispatchState.hasFromImage() {
    24  		return errors.New("Please provide a source image with `from` prior to commit")
    25  	}
    26  
    27  	runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment))
    28  	hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd)
    29  	if err != nil || hit {
    30  		return err
    31  	}
    32  	id, err := b.create(runConfigWithCommentCmd)
    33  	if err != nil {
    34  		return err
    35  	}
    36  
    37  	return b.commitContainer(dispatchState, id, runConfigWithCommentCmd)
    38  }
    39  
    40  // TODO: see if any args can be dropped
    41  func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error {
    42  	if b.disableCommit {
    43  		return nil
    44  	}
    45  
    46  	commitCfg := &backend.ContainerCommitConfig{
    47  		ContainerCommitConfig: types.ContainerCommitConfig{
    48  			Author: dispatchState.maintainer,
    49  			Pause:  true,
    50  			// TODO: this should be done by Commit()
    51  			Config: copyRunConfig(dispatchState.runConfig),
    52  		},
    53  		ContainerConfig: containerConfig,
    54  	}
    55  
    56  	// Commit the container
    57  	imageID, err := b.docker.Commit(id, commitCfg)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	dispatchState.imageID = imageID
    63  	b.buildStages.update(imageID, dispatchState.runConfig)
    64  	return nil
    65  }
    66  
    67  func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error {
    68  	srcHash := getSourceHashFromInfos(inst.infos)
    69  
    70  	// TODO: should this have been using origPaths instead of srcHash in the comment?
    71  	runConfigWithCommentCmd := copyRunConfig(
    72  		state.runConfig,
    73  		withCmdCommentString(fmt.Sprintf("%s %s in %s ", inst.cmdName, srcHash, inst.dest)))
    74  	containerID, err := b.probeAndCreate(state, runConfigWithCommentCmd)
    75  	if err != nil || containerID == "" {
    76  		return err
    77  	}
    78  
    79  	// Twiddle the destination when it's a relative path - meaning, make it
    80  	// relative to the WORKINGDIR
    81  	dest, err := normaliseDest(inst.cmdName, state.runConfig.WorkingDir, inst.dest)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	for _, info := range inst.infos {
    87  		if err := b.docker.CopyOnBuild(containerID, dest, info.root, info.path, inst.allowLocalDecompression); err != nil {
    88  			return err
    89  		}
    90  	}
    91  	return b.commitContainer(state, containerID, runConfigWithCommentCmd)
    92  }
    93  
    94  // For backwards compat, if there's just one info then use it as the
    95  // cache look-up string, otherwise hash 'em all into one
    96  func getSourceHashFromInfos(infos []copyInfo) string {
    97  	if len(infos) == 1 {
    98  		return infos[0].hash
    99  	}
   100  	var hashs []string
   101  	for _, info := range infos {
   102  		hashs = append(hashs, info.hash)
   103  	}
   104  	return hashStringSlice("multi", hashs)
   105  }
   106  
   107  func hashStringSlice(prefix string, slice []string) string {
   108  	hasher := sha256.New()
   109  	hasher.Write([]byte(strings.Join(slice, ",")))
   110  	return prefix + ":" + hex.EncodeToString(hasher.Sum(nil))
   111  }
   112  
   113  type runConfigModifier func(*container.Config)
   114  
   115  func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config {
   116  	copy := *runConfig
   117  	for _, modifier := range modifiers {
   118  		modifier(&copy)
   119  	}
   120  	return &copy
   121  }
   122  
   123  func withCmd(cmd []string) runConfigModifier {
   124  	return func(runConfig *container.Config) {
   125  		runConfig.Cmd = cmd
   126  	}
   127  }
   128  
   129  // withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for
   130  // why there are two almost identical versions of this.
   131  func withCmdComment(comment string) runConfigModifier {
   132  	return func(runConfig *container.Config) {
   133  		runConfig.Cmd = append(getShell(runConfig), "#(nop) ", comment)
   134  	}
   135  }
   136  
   137  // withCmdCommentString exists to maintain compatibility with older versions.
   138  // A few instructions (workdir, copy, add) used a nop comment that is a single arg
   139  // where as all the other instructions used a two arg comment string. This
   140  // function implements the single arg version.
   141  func withCmdCommentString(comment string) runConfigModifier {
   142  	return func(runConfig *container.Config) {
   143  		runConfig.Cmd = append(getShell(runConfig), "#(nop) "+comment)
   144  	}
   145  }
   146  
   147  func withEnv(env []string) runConfigModifier {
   148  	return func(runConfig *container.Config) {
   149  		runConfig.Env = env
   150  	}
   151  }
   152  
   153  // withEntrypointOverride sets an entrypoint on runConfig if the command is
   154  // not empty. The entrypoint is left unmodified if command is empty.
   155  //
   156  // The dockerfile RUN instruction expect to run without an entrypoint
   157  // so the runConfig entrypoint needs to be modified accordingly. ContainerCreate
   158  // will change a []string{""} entrypoint to nil, so we probe the cache with the
   159  // nil entrypoint.
   160  func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier {
   161  	return func(runConfig *container.Config) {
   162  		if len(cmd) > 0 {
   163  			runConfig.Entrypoint = entrypoint
   164  		}
   165  	}
   166  }
   167  
   168  // getShell is a helper function which gets the right shell for prefixing the
   169  // shell-form of RUN, ENTRYPOINT and CMD instructions
   170  func getShell(c *container.Config) []string {
   171  	if 0 == len(c.Shell) {
   172  		return append([]string{}, defaultShell[:]...)
   173  	}
   174  	return append([]string{}, c.Shell[:]...)
   175  }
   176  
   177  func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) {
   178  	cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig)
   179  	if cachedID == "" || err != nil {
   180  		return false, err
   181  	}
   182  	fmt.Fprint(b.Stdout, " ---> Using cache\n")
   183  
   184  	dispatchState.imageID = string(cachedID)
   185  	b.buildStages.update(dispatchState.imageID, runConfig)
   186  	return true, nil
   187  }
   188  
   189  var defaultLogConfig = container.LogConfig{Type: "none"}
   190  
   191  func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *container.Config) (string, error) {
   192  	if hit, err := b.probeCache(dispatchState, runConfig); err != nil || hit {
   193  		return "", err
   194  	}
   195  	// Set a log config to override any default value set on the daemon
   196  	hostConfig := &container.HostConfig{LogConfig: defaultLogConfig}
   197  	container, err := b.containerManager.Create(runConfig, hostConfig)
   198  	return container.ID, err
   199  }
   200  
   201  func (b *Builder) create(runConfig *container.Config) (string, error) {
   202  	hostConfig := hostConfigFromOptions(b.options)
   203  	container, err := b.containerManager.Create(runConfig, hostConfig)
   204  	if err != nil {
   205  		return "", err
   206  	}
   207  	// TODO: could this be moved into containerManager.Create() ?
   208  	for _, warning := range container.Warnings {
   209  		fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning)
   210  	}
   211  	fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(container.ID))
   212  	return container.ID, nil
   213  }
   214  
   215  func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConfig {
   216  	resources := container.Resources{
   217  		CgroupParent: options.CgroupParent,
   218  		CPUShares:    options.CPUShares,
   219  		CPUPeriod:    options.CPUPeriod,
   220  		CPUQuota:     options.CPUQuota,
   221  		CpusetCpus:   options.CPUSetCPUs,
   222  		CpusetMems:   options.CPUSetMems,
   223  		Memory:       options.Memory,
   224  		MemorySwap:   options.MemorySwap,
   225  		Ulimits:      options.Ulimits,
   226  	}
   227  
   228  	return &container.HostConfig{
   229  		SecurityOpt: options.SecurityOpt,
   230  		Isolation:   options.Isolation,
   231  		ShmSize:     options.ShmSize,
   232  		Resources:   resources,
   233  		NetworkMode: container.NetworkMode(options.NetworkMode),
   234  		// Set a log config to override any default value set on the daemon
   235  		LogConfig:  defaultLogConfig,
   236  		ExtraHosts: options.ExtraHosts,
   237  	}
   238  }