github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/builder/dockerfile/internals.go (about) 1 package dockerfile // import "github.com/docker/docker/builder/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 "io" 11 "os" 12 "path" 13 "path/filepath" 14 "runtime" 15 "strings" 16 17 "github.com/docker/docker/api/types" 18 "github.com/docker/docker/api/types/backend" 19 "github.com/docker/docker/api/types/container" 20 "github.com/docker/docker/builder" 21 "github.com/docker/docker/image" 22 "github.com/docker/docker/pkg/archive" 23 "github.com/docker/docker/pkg/chrootarchive" 24 "github.com/docker/docker/pkg/containerfs" 25 "github.com/docker/docker/pkg/idtools" 26 "github.com/docker/docker/pkg/stringid" 27 "github.com/docker/docker/pkg/system" 28 "github.com/docker/go-connections/nat" 29 "github.com/pkg/errors" 30 ) 31 32 // Archiver defines an interface for copying files from one destination to 33 // another using Tar/Untar. 34 type Archiver interface { 35 TarUntar(src, dst string) error 36 UntarPath(src, dst string) error 37 CopyWithTar(src, dst string) error 38 CopyFileWithTar(src, dst string) error 39 IDMappings() *idtools.IDMappings 40 } 41 42 // The builder will use the following interfaces if the container fs implements 43 // these for optimized copies to and from the container. 44 type extractor interface { 45 ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error 46 } 47 48 type archiver interface { 49 ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error) 50 } 51 52 // helper functions to get tar/untar func 53 func untarFunc(i interface{}) containerfs.UntarFunc { 54 if ea, ok := i.(extractor); ok { 55 return ea.ExtractArchive 56 } 57 return chrootarchive.Untar 58 } 59 60 func tarFunc(i interface{}) containerfs.TarFunc { 61 if ap, ok := i.(archiver); ok { 62 return ap.ArchivePath 63 } 64 return archive.TarWithOptions 65 } 66 67 func (b *Builder) getArchiver(src, dst containerfs.Driver) Archiver { 68 t, u := tarFunc(src), untarFunc(dst) 69 return &containerfs.Archiver{ 70 SrcDriver: src, 71 DstDriver: dst, 72 Tar: t, 73 Untar: u, 74 IDMappingsVar: b.idMappings, 75 } 76 } 77 78 func (b *Builder) commit(dispatchState *dispatchState, comment string) error { 79 if b.disableCommit { 80 return nil 81 } 82 if !dispatchState.hasFromImage() { 83 return errors.New("Please provide a source image with `from` prior to commit") 84 } 85 86 optionsPlatform := system.ParsePlatform(b.options.Platform) 87 runConfigWithCommentCmd := copyRunConfig(dispatchState.runConfig, withCmdComment(comment, optionsPlatform.OS)) 88 hit, err := b.probeCache(dispatchState, runConfigWithCommentCmd) 89 if err != nil || hit { 90 return err 91 } 92 id, err := b.create(runConfigWithCommentCmd) 93 if err != nil { 94 return err 95 } 96 97 return b.commitContainer(dispatchState, id, runConfigWithCommentCmd) 98 } 99 100 func (b *Builder) commitContainer(dispatchState *dispatchState, id string, containerConfig *container.Config) error { 101 if b.disableCommit { 102 return nil 103 } 104 105 commitCfg := backend.CommitConfig{ 106 Author: dispatchState.maintainer, 107 // TODO: this copy should be done by Commit() 108 Config: copyRunConfig(dispatchState.runConfig), 109 ContainerConfig: containerConfig, 110 ContainerID: id, 111 } 112 113 imageID, err := b.docker.CommitBuildStep(commitCfg) 114 dispatchState.imageID = string(imageID) 115 return err 116 } 117 118 func (b *Builder) exportImage(state *dispatchState, layer builder.RWLayer, parent builder.Image, runConfig *container.Config) error { 119 newLayer, err := layer.Commit() 120 if err != nil { 121 return err 122 } 123 124 // add an image mount without an image so the layer is properly unmounted 125 // if there is an error before we can add the full mount with image 126 b.imageSources.Add(newImageMount(nil, newLayer)) 127 128 parentImage, ok := parent.(*image.Image) 129 if !ok { 130 return errors.Errorf("unexpected image type") 131 } 132 133 newImage := image.NewChildImage(parentImage, image.ChildConfig{ 134 Author: state.maintainer, 135 ContainerConfig: runConfig, 136 DiffID: newLayer.DiffID(), 137 Config: copyRunConfig(state.runConfig), 138 }, parentImage.OS) 139 140 // TODO: it seems strange to marshal this here instead of just passing in the 141 // image struct 142 config, err := newImage.MarshalJSON() 143 if err != nil { 144 return errors.Wrap(err, "failed to encode image config") 145 } 146 147 exportedImage, err := b.docker.CreateImage(config, state.imageID) 148 if err != nil { 149 return errors.Wrapf(err, "failed to export image") 150 } 151 152 state.imageID = exportedImage.ImageID() 153 b.imageSources.Add(newImageMount(exportedImage, newLayer)) 154 return nil 155 } 156 157 func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error { 158 srcHash := getSourceHashFromInfos(inst.infos) 159 160 var chownComment string 161 if inst.chownStr != "" { 162 chownComment = fmt.Sprintf("--chown=%s", inst.chownStr) 163 } 164 commentStr := fmt.Sprintf("%s %s%s in %s ", inst.cmdName, chownComment, srcHash, inst.dest) 165 166 // TODO: should this have been using origPaths instead of srcHash in the comment? 167 optionsPlatform := system.ParsePlatform(b.options.Platform) 168 runConfigWithCommentCmd := copyRunConfig( 169 state.runConfig, 170 withCmdCommentString(commentStr, optionsPlatform.OS)) 171 hit, err := b.probeCache(state, runConfigWithCommentCmd) 172 if err != nil || hit { 173 return err 174 } 175 176 imageMount, err := b.imageSources.Get(state.imageID, true) 177 if err != nil { 178 return errors.Wrapf(err, "failed to get destination image %q", state.imageID) 179 } 180 181 rwLayer, err := imageMount.NewRWLayer() 182 if err != nil { 183 return err 184 } 185 defer rwLayer.Release() 186 187 destInfo, err := createDestInfo(state.runConfig.WorkingDir, inst, rwLayer, b.options.Platform) 188 if err != nil { 189 return err 190 } 191 192 chownPair := b.idMappings.RootPair() 193 // if a chown was requested, perform the steps to get the uid, gid 194 // translated (if necessary because of user namespaces), and replace 195 // the root pair with the chown pair for copy operations 196 if inst.chownStr != "" { 197 chownPair, err = parseChownFlag(inst.chownStr, destInfo.root.Path(), b.idMappings) 198 if err != nil { 199 return errors.Wrapf(err, "unable to convert uid/gid chown string to host mapping") 200 } 201 } 202 203 for _, info := range inst.infos { 204 opts := copyFileOptions{ 205 decompress: inst.allowLocalDecompression, 206 archiver: b.getArchiver(info.root, destInfo.root), 207 chownPair: chownPair, 208 } 209 if err := performCopyForInfo(destInfo, info, opts); err != nil { 210 return errors.Wrapf(err, "failed to copy files") 211 } 212 } 213 return b.exportImage(state, rwLayer, imageMount.Image(), runConfigWithCommentCmd) 214 } 215 216 func createDestInfo(workingDir string, inst copyInstruction, rwLayer builder.RWLayer, platform string) (copyInfo, error) { 217 // Twiddle the destination when it's a relative path - meaning, make it 218 // relative to the WORKINGDIR 219 dest, err := normalizeDest(workingDir, inst.dest, platform) 220 if err != nil { 221 return copyInfo{}, errors.Wrapf(err, "invalid %s", inst.cmdName) 222 } 223 224 return copyInfo{root: rwLayer.Root(), path: dest}, nil 225 } 226 227 // normalizeDest normalises the destination of a COPY/ADD command in a 228 // platform semantically consistent way. 229 func normalizeDest(workingDir, requested string, platform string) (string, error) { 230 dest := fromSlash(requested, platform) 231 endsInSlash := strings.HasSuffix(dest, string(separator(platform))) 232 233 if platform != "windows" { 234 if !path.IsAbs(requested) { 235 dest = path.Join("/", filepath.ToSlash(workingDir), dest) 236 // Make sure we preserve any trailing slash 237 if endsInSlash { 238 dest += "/" 239 } 240 } 241 return dest, nil 242 } 243 244 // We are guaranteed that the working directory is already consistent, 245 // However, Windows also has, for now, the limitation that ADD/COPY can 246 // only be done to the system drive, not any drives that might be present 247 // as a result of a bind mount. 248 // 249 // So... if the path requested is Linux-style absolute (/foo or \\foo), 250 // we assume it is the system drive. If it is a Windows-style absolute 251 // (DRIVE:\\foo), error if DRIVE is not C. And finally, ensure we 252 // strip any configured working directories drive letter so that it 253 // can be subsequently legitimately converted to a Windows volume-style 254 // pathname. 255 256 // Not a typo - filepath.IsAbs, not system.IsAbs on this next check as 257 // we only want to validate where the DriveColon part has been supplied. 258 if filepath.IsAbs(dest) { 259 if strings.ToUpper(string(dest[0])) != "C" { 260 return "", fmt.Errorf("Windows does not support destinations not on the system drive (C:)") 261 } 262 dest = dest[2:] // Strip the drive letter 263 } 264 265 // Cannot handle relative where WorkingDir is not the system drive. 266 if len(workingDir) > 0 { 267 if ((len(workingDir) > 1) && !system.IsAbs(workingDir[2:])) || (len(workingDir) == 1) { 268 return "", fmt.Errorf("Current WorkingDir %s is not platform consistent", workingDir) 269 } 270 if !system.IsAbs(dest) { 271 if string(workingDir[0]) != "C" { 272 return "", fmt.Errorf("Windows does not support relative paths when WORKDIR is not the system drive") 273 } 274 dest = filepath.Join(string(os.PathSeparator), workingDir[2:], dest) 275 // Make sure we preserve any trailing slash 276 if endsInSlash { 277 dest += string(os.PathSeparator) 278 } 279 } 280 } 281 return dest, nil 282 } 283 284 // For backwards compat, if there's just one info then use it as the 285 // cache look-up string, otherwise hash 'em all into one 286 func getSourceHashFromInfos(infos []copyInfo) string { 287 if len(infos) == 1 { 288 return infos[0].hash 289 } 290 var hashs []string 291 for _, info := range infos { 292 hashs = append(hashs, info.hash) 293 } 294 return hashStringSlice("multi", hashs) 295 } 296 297 func hashStringSlice(prefix string, slice []string) string { 298 hasher := sha256.New() 299 hasher.Write([]byte(strings.Join(slice, ","))) 300 return prefix + ":" + hex.EncodeToString(hasher.Sum(nil)) 301 } 302 303 type runConfigModifier func(*container.Config) 304 305 func withCmd(cmd []string) runConfigModifier { 306 return func(runConfig *container.Config) { 307 runConfig.Cmd = cmd 308 } 309 } 310 311 // withCmdComment sets Cmd to a nop comment string. See withCmdCommentString for 312 // why there are two almost identical versions of this. 313 func withCmdComment(comment string, platform string) runConfigModifier { 314 return func(runConfig *container.Config) { 315 runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) ", comment) 316 } 317 } 318 319 // withCmdCommentString exists to maintain compatibility with older versions. 320 // A few instructions (workdir, copy, add) used a nop comment that is a single arg 321 // where as all the other instructions used a two arg comment string. This 322 // function implements the single arg version. 323 func withCmdCommentString(comment string, platform string) runConfigModifier { 324 return func(runConfig *container.Config) { 325 runConfig.Cmd = append(getShell(runConfig, platform), "#(nop) "+comment) 326 } 327 } 328 329 func withEnv(env []string) runConfigModifier { 330 return func(runConfig *container.Config) { 331 runConfig.Env = env 332 } 333 } 334 335 // withEntrypointOverride sets an entrypoint on runConfig if the command is 336 // not empty. The entrypoint is left unmodified if command is empty. 337 // 338 // The dockerfile RUN instruction expect to run without an entrypoint 339 // so the runConfig entrypoint needs to be modified accordingly. ContainerCreate 340 // will change a []string{""} entrypoint to nil, so we probe the cache with the 341 // nil entrypoint. 342 func withEntrypointOverride(cmd []string, entrypoint []string) runConfigModifier { 343 return func(runConfig *container.Config) { 344 if len(cmd) > 0 { 345 runConfig.Entrypoint = entrypoint 346 } 347 } 348 } 349 350 func copyRunConfig(runConfig *container.Config, modifiers ...runConfigModifier) *container.Config { 351 copy := *runConfig 352 copy.Cmd = copyStringSlice(runConfig.Cmd) 353 copy.Env = copyStringSlice(runConfig.Env) 354 copy.Entrypoint = copyStringSlice(runConfig.Entrypoint) 355 copy.OnBuild = copyStringSlice(runConfig.OnBuild) 356 copy.Shell = copyStringSlice(runConfig.Shell) 357 358 if copy.Volumes != nil { 359 copy.Volumes = make(map[string]struct{}, len(runConfig.Volumes)) 360 for k, v := range runConfig.Volumes { 361 copy.Volumes[k] = v 362 } 363 } 364 365 if copy.ExposedPorts != nil { 366 copy.ExposedPorts = make(nat.PortSet, len(runConfig.ExposedPorts)) 367 for k, v := range runConfig.ExposedPorts { 368 copy.ExposedPorts[k] = v 369 } 370 } 371 372 if copy.Labels != nil { 373 copy.Labels = make(map[string]string, len(runConfig.Labels)) 374 for k, v := range runConfig.Labels { 375 copy.Labels[k] = v 376 } 377 } 378 379 for _, modifier := range modifiers { 380 modifier(©) 381 } 382 return © 383 } 384 385 func copyStringSlice(orig []string) []string { 386 if orig == nil { 387 return nil 388 } 389 return append([]string{}, orig...) 390 } 391 392 // getShell is a helper function which gets the right shell for prefixing the 393 // shell-form of RUN, ENTRYPOINT and CMD instructions 394 func getShell(c *container.Config, os string) []string { 395 if 0 == len(c.Shell) { 396 return append([]string{}, defaultShellForOS(os)[:]...) 397 } 398 return append([]string{}, c.Shell[:]...) 399 } 400 401 func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.Config) (bool, error) { 402 cachedID, err := b.imageProber.Probe(dispatchState.imageID, runConfig) 403 if cachedID == "" || err != nil { 404 return false, err 405 } 406 fmt.Fprint(b.Stdout, " ---> Using cache\n") 407 408 dispatchState.imageID = cachedID 409 return true, nil 410 } 411 412 var defaultLogConfig = container.LogConfig{Type: "none"} 413 414 func (b *Builder) probeAndCreate(dispatchState *dispatchState, runConfig *container.Config) (string, error) { 415 if hit, err := b.probeCache(dispatchState, runConfig); err != nil || hit { 416 return "", err 417 } 418 // Set a log config to override any default value set on the daemon 419 hostConfig := &container.HostConfig{LogConfig: defaultLogConfig} 420 container, err := b.containerManager.Create(runConfig, hostConfig) 421 return container.ID, err 422 } 423 424 func (b *Builder) create(runConfig *container.Config) (string, error) { 425 hostConfig := hostConfigFromOptions(b.options) 426 container, err := b.containerManager.Create(runConfig, hostConfig) 427 if err != nil { 428 return "", err 429 } 430 // TODO: could this be moved into containerManager.Create() ? 431 for _, warning := range container.Warnings { 432 fmt.Fprintf(b.Stdout, " ---> [Warning] %s\n", warning) 433 } 434 fmt.Fprintf(b.Stdout, " ---> Running in %s\n", stringid.TruncateID(container.ID)) 435 return container.ID, nil 436 } 437 438 func hostConfigFromOptions(options *types.ImageBuildOptions) *container.HostConfig { 439 resources := container.Resources{ 440 CgroupParent: options.CgroupParent, 441 CPUShares: options.CPUShares, 442 CPUPeriod: options.CPUPeriod, 443 CPUQuota: options.CPUQuota, 444 CpusetCpus: options.CPUSetCPUs, 445 CpusetMems: options.CPUSetMems, 446 Memory: options.Memory, 447 MemorySwap: options.MemorySwap, 448 Ulimits: options.Ulimits, 449 } 450 451 hc := &container.HostConfig{ 452 SecurityOpt: options.SecurityOpt, 453 Isolation: options.Isolation, 454 ShmSize: options.ShmSize, 455 Resources: resources, 456 NetworkMode: container.NetworkMode(options.NetworkMode), 457 // Set a log config to override any default value set on the daemon 458 LogConfig: defaultLogConfig, 459 ExtraHosts: options.ExtraHosts, 460 } 461 462 // For WCOW, the default of 20GB hard-coded in the platform 463 // is too small for builder scenarios where many users are 464 // using RUN statements to install large amounts of data. 465 // Use 127GB as that's the default size of a VHD in Hyper-V. 466 if runtime.GOOS == "windows" && options.Platform == "windows" { 467 hc.StorageOpt = make(map[string]string) 468 hc.StorageOpt["size"] = "127GB" 469 } 470 471 return hc 472 } 473 474 // fromSlash works like filepath.FromSlash but with a given OS platform field 475 func fromSlash(path, platform string) string { 476 if platform == "windows" { 477 return strings.Replace(path, "/", "\\", -1) 478 } 479 return path 480 } 481 482 // separator returns a OS path separator for the given OS platform 483 func separator(platform string) byte { 484 if platform == "windows" { 485 return '\\' 486 } 487 return '/' 488 }