podman
121 строка · 3.3 Кб
1// Copyright 2014 go-dockerclient authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package docker
6
7import (
8"fmt"
9"io"
10"os"
11"path"
12"path/filepath"
13"strings"
14
15"github.com/docker/docker/pkg/archive"
16"github.com/moby/patternmatcher"
17)
18
19func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) {
20srcPath, err := filepath.Abs(srcPath)
21if err != nil {
22return nil, err
23}
24
25excludes, err := parseDockerignore(srcPath)
26if err != nil {
27return nil, err
28}
29
30includes := []string{"."}
31
32// If .dockerignore mentions .dockerignore or the Dockerfile
33// then make sure we send both files over to the daemon
34// because Dockerfile is, obviously, needed no matter what, and
35// .dockerignore is needed to know if either one needs to be
36// removed. The deamon will remove them for us, if needed, after it
37// parses the Dockerfile.
38//
39// https://github.com/docker/docker/issues/8330
40//
41forceIncludeFiles := []string{".dockerignore", dockerfilePath}
42
43for _, includeFile := range forceIncludeFiles {
44if includeFile == "" {
45continue
46}
47keepThem, err := patternmatcher.Matches(includeFile, excludes)
48if err != nil {
49return nil, fmt.Errorf("cannot match .dockerfileignore: '%s', error: %w", includeFile, err)
50}
51if keepThem {
52includes = append(includes, includeFile)
53}
54}
55
56if err := validateContextDirectory(srcPath, excludes); err != nil {
57return nil, err
58}
59tarOpts := &archive.TarOptions{
60ExcludePatterns: excludes,
61IncludeFiles: includes,
62Compression: archive.Uncompressed,
63NoLchown: true,
64}
65return archive.TarWithOptions(srcPath, tarOpts)
66}
67
68// validateContextDirectory checks if all the contents of the directory
69// can be read and returns an error if some files can't be read.
70// Symlinks which point to non-existing files don't trigger an error
71func validateContextDirectory(srcPath string, excludes []string) error {
72return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
73// skip this directory/file if it's not in the path, it won't get added to the context
74if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil {
75return relErr
76} else if skip, matchErr := patternmatcher.Matches(relFilePath, excludes); matchErr != nil {
77return matchErr
78} else if skip {
79if f.IsDir() {
80return filepath.SkipDir
81}
82return nil
83}
84
85if err != nil {
86if os.IsPermission(err) {
87return fmt.Errorf("cannot stat %q: %w", filePath, err)
88}
89if os.IsNotExist(err) {
90return nil
91}
92return err
93}
94
95// skip checking if symlinks point to non-existing files, such symlinks can be useful
96// also skip named pipes, because they hanging on open
97if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
98return nil
99}
100
101if !f.IsDir() {
102currentFile, err := os.Open(filePath)
103if err != nil {
104return fmt.Errorf("cannot open %q for reading: %w", filePath, err)
105}
106currentFile.Close()
107}
108return nil
109})
110}
111
112func parseDockerignore(root string) ([]string, error) {
113var excludes []string
114ignore, err := os.ReadFile(path.Join(root, ".dockerignore"))
115if err != nil && !os.IsNotExist(err) {
116return excludes, fmt.Errorf("error reading .dockerignore: %w", err)
117}
118excludes = strings.Split(string(ignore), "\n")
119
120return excludes, nil
121}
122