Check / check (pull_request) Skipped
Docker does not apply .dockerignore to a build context sent as a tar, so upaas sent every file in the clone. The build now reads the ignore file as docker build does, <Dockerfile>.dockerignore next to the Dockerfile if there is one, otherwise .dockerignore at the root, with the ignorefile reader of moby/patternmatcher, and passes its patterns as exclude patterns. As the docker command line does, the Dockerfile and .dockerignore are never left out. The Dockerfile path is read as Docker reads it, as a path inside the clone. The ignore file is read through os.Root, so it cannot be read from outside the clone. An ignore file that cannot be read, or a malformed pattern, fails the build. Model: opus-5-5
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
package docker
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/moby/patternmatcher"
|
|
"github.com/moby/patternmatcher/ignorefile"
|
|
)
|
|
|
|
// defaultDockerfileName is the Dockerfile Docker builds when none is named.
|
|
const defaultDockerfileName = "Dockerfile"
|
|
|
|
// defaultDockerignoreName is the ignore file at the root of a build context,
|
|
// read when the Dockerfile has no ignore file of its own.
|
|
const defaultDockerignoreName = ".dockerignore"
|
|
|
|
// tarBuildContext returns a tar of the build context in contextDir that leaves
|
|
// out the files the app's ignore file names, as docker build does; Docker does
|
|
// not apply the ignore file to a build context sent as a tar. dockerfile is
|
|
// the path of the Dockerfile inside contextDir.
|
|
func tarBuildContext(contextDir, dockerfile string) (io.ReadCloser, error) {
|
|
excludes, err := readDockerignore(contextDir, dockerfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return archive.TarWithOptions(contextDir, &archive.TarOptions{
|
|
ExcludePatterns: excludes,
|
|
})
|
|
}
|
|
|
|
// readDockerignore returns the patterns of the files to leave out of the
|
|
// build context in contextDir, read as docker build reads them for the
|
|
// Dockerfile at dockerfile: from <dockerfile>.dockerignore next to the
|
|
// Dockerfile if there is one, otherwise from .dockerignore at the root of the
|
|
// context. Without either file there are no patterns.
|
|
func readDockerignore(contextDir, dockerfile string) ([]string, error) {
|
|
// Docker reads the Dockerfile path as a path inside the build context:
|
|
// cleaned, with a leading / and any .. that would lead out of the context
|
|
// dropped, so ./Dockerfile and /Dockerfile both name the Dockerfile at the
|
|
// root.
|
|
dockerfile = strings.TrimPrefix(filepath.Join("/", dockerfile), "/")
|
|
if dockerfile == "" {
|
|
dockerfile = defaultDockerfileName
|
|
}
|
|
|
|
// Reading through os.Root keeps the read inside the build context, even
|
|
// when the ignore file is a symlink.
|
|
root, err := os.OpenRoot(contextDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() { _ = root.Close() }()
|
|
|
|
name := dockerfile + defaultDockerignoreName
|
|
|
|
file, err := root.Open(name)
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
name = defaultDockerignoreName
|
|
file, err = root.Open(name)
|
|
}
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil, nil
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read %s: %w", name, err)
|
|
}
|
|
|
|
defer func() { _ = file.Close() }()
|
|
|
|
excludes, err := ignorefile.ReadAll(file)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read %s: %w", name, err)
|
|
}
|
|
|
|
// Like the docker command line, never leave out .dockerignore or the
|
|
// Dockerfile: Docker reads the Dockerfile from the context.
|
|
for _, keep := range []string{defaultDockerignoreName, filepath.ToSlash(dockerfile)} {
|
|
excluded, err := patternmatcher.MatchesOrParentMatches(keep, excludes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid pattern in %s: %w", name, err)
|
|
}
|
|
|
|
if excluded {
|
|
excludes = append(excludes, "!"+keep)
|
|
}
|
|
}
|
|
|
|
return excludes, nil
|
|
}
|