2020-06-19 06:03:43 +08:00
package conformance
import (
"archive/tar"
"bytes"
"context"
"encoding/json"
2022-07-06 17:14:06 +08:00
"errors"
2020-06-19 06:03:43 +08:00
"flag"
"fmt"
"io"
2023-05-31 02:30:49 +08:00
"io/fs"
2025-01-23 22:27:47 +08:00
"maps"
2020-06-19 06:03:43 +08:00
"os"
2024-06-11 03:18:01 +08:00
"path"
2020-06-19 06:03:43 +08:00
"path/filepath"
"reflect"
"regexp"
2025-01-23 22:27:47 +08:00
"slices"
2023-11-01 22:18:40 +08:00
"strconv"
2020-06-19 06:03:43 +08:00
"strings"
"sync"
"syscall"
"testing"
"text/tabwriter"
"time"
"github.com/containers/buildah"
"github.com/containers/buildah/copier"
2021-02-07 06:49:40 +08:00
"github.com/containers/buildah/define"
2020-06-19 06:03:43 +08:00
"github.com/containers/buildah/imagebuildah"
2023-11-01 22:18:40 +08:00
"github.com/containers/buildah/internal/config"
2020-06-19 06:03:43 +08:00
"github.com/containers/image/v5/docker/daemon"
"github.com/containers/image/v5/image"
2023-03-16 04:50:51 +08:00
"github.com/containers/image/v5/manifest"
2020-06-19 06:03:43 +08:00
"github.com/containers/image/v5/pkg/compression"
2023-11-01 22:18:40 +08:00
is "github.com/containers/image/v5/storage"
2020-06-19 06:03:43 +08:00
istorage "github.com/containers/image/v5/storage"
"github.com/containers/image/v5/transports"
2023-11-01 22:18:40 +08:00
"github.com/containers/image/v5/transports/alltransports"
2020-06-19 06:03:43 +08:00
"github.com/containers/image/v5/types"
"github.com/containers/storage"
2023-11-17 16:06:38 +08:00
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
2024-03-07 03:13:06 +08:00
"github.com/containers/storage/pkg/ioutils"
2020-06-19 06:03:43 +08:00
"github.com/containers/storage/pkg/reexec"
2021-09-22 03:06:58 +08:00
dockertypes "github.com/docker/docker/api/types"
dockerdockerclient "github.com/docker/docker/client"
2020-06-19 06:03:43 +08:00
docker "github.com/fsouza/go-dockerclient"
digest "github.com/opencontainers/go-digest"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/openshift/imagebuilder"
"github.com/openshift/imagebuilder/dockerclient"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
// See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_06, from archive/tar
2024-08-16 00:50:07 +08:00
cISUID = 0 o4000 // Set uid, from archive/tar
cISGID = 0 o2000 // Set gid, from archive/tar
cISVTX = 0 o1000 // Save text (sticky bit), from archive/tar
2024-11-20 23:43:00 +08:00
// xattrs in the PAXRecords map are namespaced with this prefix
xattrPAXRecordNamespace = "SCHILY.xattr."
2020-06-19 06:03:43 +08:00
)
var (
originalSkip = [ ] string {
"created" ,
"container" ,
"docker_version" ,
2021-08-27 23:10:17 +08:00
"container_config:hostname" ,
"config:hostname" ,
2020-06-19 06:03:43 +08:00
"config:image" ,
"container_config:cmd" ,
"container_config:image" ,
"history" ,
"rootfs:diff_ids" ,
2023-03-16 04:50:51 +08:00
"moby.buildkit.buildinfo.v1" ,
2020-06-19 06:03:43 +08:00
}
ociSkip = [ ] string {
"created" ,
"history" ,
"rootfs:diff_ids" ,
}
fsSkip = [ ] string {
// things that we volume mount or synthesize for RUN statements that currently bleed through
"(dir):etc:mtime" ,
"(dir):etc:(dir):hosts" ,
"(dir):etc:(dir):resolv.conf" ,
"(dir):run" ,
"(dir):run:mtime" ,
"(dir):run:(dir):.containerenv" ,
"(dir):run:(dir):secrets" ,
"(dir):proc" ,
"(dir):proc:mtime" ,
"(dir):sys" ,
"(dir):sys:mtime" ,
}
testDate = time . Unix ( 1485449953 , 0 )
compareLayers = false
compareImagebuilder = false
testDataDir = ""
dockerDir = ""
imagebuilderDir = ""
buildahDir = ""
contextCanDoXattrs * bool
storageCanDoXattrs * bool
)
func TestMain ( m * testing . M ) {
var logLevel string
if reexec . Init ( ) {
return
}
cwd , err := os . Getwd ( )
if err != nil {
logrus . Fatalf ( "error finding current directory: %v" , err )
}
testDataDir = filepath . Join ( cwd , "testdata" )
flag . StringVar ( & logLevel , "log-level" , "error" , "buildah logging log level" )
flag . BoolVar ( & compareLayers , "compare-layers" , compareLayers , "compare instruction-by-instruction" )
flag . BoolVar ( & compareImagebuilder , "compare-imagebuilder" , compareImagebuilder , "also compare using imagebuilder" )
flag . StringVar ( & testDataDir , "testdata" , testDataDir , "location of conformance testdata" )
flag . StringVar ( & dockerDir , "docker-dir" , dockerDir , "location to save docker build results" )
flag . StringVar ( & imagebuilderDir , "imagebuilder-dir" , imagebuilderDir , "location to save imagebuilder build results" )
flag . StringVar ( & buildahDir , "buildah-dir" , buildahDir , "location to save buildah build results" )
flag . Parse ( )
var tempdir string
2023-11-01 22:18:40 +08:00
if buildahDir == "" || dockerDir == "" || imagebuilderDir == "" {
2020-06-19 06:03:43 +08:00
if tempdir == "" {
2023-11-01 22:18:40 +08:00
if tempdir , err = os . MkdirTemp ( "" , "conformance" ) ; err != nil {
logrus . Fatalf ( "creating temporary directory: %v" , err )
os . Exit ( 1 )
}
2020-06-19 06:03:43 +08:00
}
2023-11-01 22:18:40 +08:00
}
if buildahDir == "" {
2020-06-19 06:03:43 +08:00
buildahDir = filepath . Join ( tempdir , "buildah" )
}
if dockerDir == "" {
dockerDir = filepath . Join ( tempdir , "docker" )
}
if imagebuilderDir == "" {
imagebuilderDir = filepath . Join ( tempdir , "imagebuilder" )
}
2023-11-01 22:18:40 +08:00
level , err := logrus . ParseLevel ( logLevel )
if err != nil {
logrus . Fatalf ( "error parsing log level %q: %v" , logLevel , err )
}
logrus . SetLevel ( level )
result := m . Run ( )
if err = os . RemoveAll ( tempdir ) ; err != nil {
logrus . Errorf ( "removing temporary directory %q: %v" , tempdir , err )
}
os . Exit ( result )
}
func TestConformance ( t * testing . T ) {
2025-02-20 00:36:36 +08:00
t . Parallel ( )
2020-06-19 06:03:43 +08:00
dateStamp := fmt . Sprintf ( "%d" , time . Now ( ) . UnixNano ( ) )
for i := range internalTestCases {
t . Run ( internalTestCases [ i ] . name , func ( t * testing . T ) {
2024-06-11 03:18:01 +08:00
if internalTestCases [ i ] . testUsingSetParent {
t . Run ( "new-set-parent" , func ( t * testing . T ) {
testConformanceInternal ( t , dateStamp , i , func ( test * testCase ) {
test . dockerBuilderVersion = docker . BuilderBuildKit
test . compatSetParent = types . OptionalBoolFalse
2024-08-16 01:41:05 +08:00
test . compatScratchConfig = types . OptionalBoolFalse
2024-06-11 03:18:01 +08:00
} )
} )
t . Run ( "old-set-parent" , func ( t * testing . T ) {
testConformanceInternal ( t , dateStamp , i , func ( test * testCase ) {
test . dockerBuilderVersion = docker . BuilderV1
test . compatSetParent = types . OptionalBoolTrue
2024-08-16 01:41:05 +08:00
test . compatScratchConfig = types . OptionalBoolTrue
2024-06-11 03:18:01 +08:00
} )
} )
2024-06-11 03:46:58 +08:00
} else if internalTestCases [ i ] . testUsingVolumes {
t . Run ( "new-volumes" , func ( t * testing . T ) {
testConformanceInternal ( t , dateStamp , i , func ( test * testCase ) {
test . dockerBuilderVersion = docker . BuilderBuildKit
test . compatVolumes = types . OptionalBoolFalse
2024-08-16 01:41:05 +08:00
test . compatScratchConfig = types . OptionalBoolFalse
2024-06-11 03:46:58 +08:00
} )
} )
t . Run ( "old-volumes" , func ( t * testing . T ) {
testConformanceInternal ( t , dateStamp , i , func ( test * testCase ) {
test . dockerBuilderVersion = docker . BuilderV1
test . compatVolumes = types . OptionalBoolTrue
2024-08-16 01:41:05 +08:00
test . compatScratchConfig = types . OptionalBoolTrue
2024-06-11 03:46:58 +08:00
} )
} )
2024-06-11 03:18:01 +08:00
} else {
testConformanceInternal ( t , dateStamp , i , nil )
}
2020-06-19 06:03:43 +08:00
} )
}
}
2024-06-11 03:18:01 +08:00
func testConformanceInternal ( t * testing . T , dateStamp string , testIndex int , mutate func ( * testCase ) ) {
2020-06-19 06:03:43 +08:00
test := internalTestCases [ testIndex ]
2024-06-11 03:18:01 +08:00
if mutate != nil {
mutate ( & test )
}
2021-09-22 03:06:58 +08:00
ctx := context . TODO ( )
2020-06-19 06:03:43 +08:00
cwd , err := os . Getwd ( )
2023-11-08 06:45:03 +08:00
require . NoError ( t , err , "error finding current directory" )
2020-06-19 06:03:43 +08:00
// create a temporary directory to hold our build context
2022-08-20 15:02:40 +08:00
tempdir := t . TempDir ( )
2020-06-19 06:03:43 +08:00
// create subdirectories to use as the build context and for buildah storage
contextDir := filepath . Join ( tempdir , "context" )
rootDir := filepath . Join ( tempdir , "root" )
runrootDir := filepath . Join ( tempdir , "runroot" )
// check if we can test xattrs where we're storing build contexts
if contextCanDoXattrs == nil {
testDir := filepath . Join ( tempdir , "test" )
2024-08-16 00:50:07 +08:00
if err := os . Mkdir ( testDir , 0 o700 ) ; err != nil {
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error creating test directory to check if xattrs are testable: %v" , err )
2020-06-19 06:03:43 +08:00
}
testFile := filepath . Join ( testDir , "testfile" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( testFile , [ ] byte ( "whatever" ) , 0 o600 ) ; err != nil {
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error creating test file to check if xattrs are testable: %v" , err )
2020-06-19 06:03:43 +08:00
}
can := false
if err := copier . Lsetxattrs ( testFile , map [ string ] string { "user.test" : "test" } ) ; err == nil {
can = true
}
contextCanDoXattrs = & can
}
// copy either a directory or just a Dockerfile into the temporary directory
pipeReader , pipeWriter := io . Pipe ( )
var getErr , putErr error
var wg sync . WaitGroup
wg . Add ( 1 )
go func ( ) {
if test . contextDir != "" {
getErr = copier . Get ( "" , testDataDir , copier . GetOptions { } , [ ] string { test . contextDir } , pipeWriter )
} else if test . dockerfile != "" {
getErr = copier . Get ( "" , testDataDir , copier . GetOptions { } , [ ] string { test . dockerfile } , pipeWriter )
}
pipeWriter . Close ( )
wg . Done ( )
} ( )
wg . Add ( 1 )
go func ( ) {
if test . contextDir != "" || test . dockerfile != "" {
putErr = copier . Put ( "" , contextDir , copier . PutOptions { } , pipeReader )
} else {
2024-08-16 00:50:07 +08:00
putErr = os . Mkdir ( contextDir , 0 o755 )
2020-06-19 06:03:43 +08:00
}
pipeReader . Close ( )
wg . Done ( )
} ( )
wg . Wait ( )
2023-11-08 06:45:03 +08:00
assert . NoErrorf ( t , getErr , "error reading build info from %q" , filepath . Join ( "testdata" , test . dockerfile ) )
assert . NoErrorf ( t , putErr , "error writing build info to %q" , contextDir )
2020-06-19 06:03:43 +08:00
if t . Failed ( ) {
t . FailNow ( )
}
// construct the names that we want to assign to the images. these should be reasonably unique
buildahImage := fmt . Sprintf ( "conformance-buildah:%s-%d" , dateStamp , testIndex )
dockerImage := fmt . Sprintf ( "conformance-docker:%s-%d" , dateStamp , testIndex )
imagebuilderImage := fmt . Sprintf ( "conformance-imagebuilder:%s-%d" , dateStamp , testIndex )
2024-06-11 03:18:01 +08:00
if mutate != nil {
buildahImage += path . Base ( t . Name ( ) )
dockerImage += path . Base ( t . Name ( ) )
imagebuilderImage += path . Base ( t . Name ( ) )
}
2020-06-19 06:03:43 +08:00
// compute the name of the Dockerfile in the build context directory
var dockerfileName string
if test . dockerfile != "" {
dockerfileName = filepath . Join ( contextDir , test . dockerfile )
} else {
dockerfileName = filepath . Join ( contextDir , "Dockerfile" )
}
// read the Dockerfile, for inclusion in failure messages
dockerfileContents := [ ] byte ( test . dockerfileContents )
if len ( dockerfileContents ) == 0 {
// no inlined contents -> read them from the specified location
2022-11-15 00:22:45 +08:00
contents , err := os . ReadFile ( dockerfileName )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading Dockerfile %q" , filepath . Join ( tempdir , dockerfileName ) )
2020-06-19 06:03:43 +08:00
dockerfileContents = contents
}
// initialize storage for buildah
options := storage . StoreOptions {
2020-11-07 04:31:29 +08:00
GraphDriverName : os . Getenv ( "STORAGE_DRIVER" ) ,
2020-06-19 06:03:43 +08:00
GraphRoot : rootDir ,
RunRoot : runrootDir ,
RootlessStoragePath : rootDir ,
}
store , err := storage . GetStore ( options )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error creating buildah storage at %q" , rootDir )
2020-06-19 06:03:43 +08:00
defer func ( ) {
if store != nil {
_ , err := store . Shutdown ( true )
2023-11-08 06:45:03 +08:00
require . NoError ( t , err , "error shutting down storage for buildah" )
2020-06-19 06:03:43 +08:00
}
} ( )
storageDriver := store . GraphDriverName ( )
storageRoot := store . GraphRoot ( )
// now that we have a Store, check if we can test xattrs in storage layers
if storageCanDoXattrs == nil {
layer , err := store . CreateLayer ( "" , "" , nil , "" , true , nil )
if err != nil {
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error creating test layer to check if xattrs are testable: %v" , err )
2020-06-19 06:03:43 +08:00
}
mountPoint , err := store . Mount ( layer . ID , "" )
if err != nil {
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error mounting test layer to check if xattrs are testable: %v" , err )
2020-06-19 06:03:43 +08:00
}
testFile := filepath . Join ( mountPoint , "testfile" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( testFile , [ ] byte ( "whatever" ) , 0 o600 ) ; err != nil {
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error creating file in test layer to check if xattrs are testable: %v" , err )
2020-06-19 06:03:43 +08:00
}
can := false
if err := copier . Lsetxattrs ( testFile , map [ string ] string { "user.test" : "test" } ) ; err == nil {
can = true
}
storageCanDoXattrs = & can
err = store . DeleteLayer ( layer . ID )
if err != nil {
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error removing test layer after checking if xattrs are testable: %v" , err )
2020-06-19 06:03:43 +08:00
}
}
2021-09-22 03:06:58 +08:00
// connect to dockerd using the docker client library
dockerClient , err := dockerdockerclient . NewClientWithOpts ( dockerdockerclient . FromEnv )
2023-11-08 06:45:03 +08:00
require . NoError ( t , err , "unable to initialize docker.client" )
2021-09-22 03:06:58 +08:00
dockerClient . NegotiateAPIVersion ( ctx )
2024-06-11 03:18:01 +08:00
if test . dockerUseBuildKit || test . dockerBuilderVersion != "" {
2024-02-01 02:41:12 +08:00
if err := dockerClient . NewVersionError ( ctx , "1.38" , "buildkit" ) ; err != nil {
2021-09-22 03:06:58 +08:00
t . Skipf ( "%v" , err )
}
}
// connect to dockerd using go-dockerclient
2020-06-19 06:03:43 +08:00
client , err := docker . NewClientFromEnv ( )
2023-11-08 06:45:03 +08:00
require . NoError ( t , err , "unable to initialize docker client" )
2020-06-19 06:03:43 +08:00
var dockerVersion [ ] string
if version , err := client . Version ( ) ; err == nil {
if version != nil {
for _ , s := range * version {
dockerVersion = append ( dockerVersion , s )
}
}
} else {
2023-11-08 06:45:03 +08:00
require . NoError ( t , err , "unable to connect to docker daemon" )
2020-06-19 06:03:43 +08:00
}
// make any last-minute tweaks to the build context directory that this test requires
if test . tweakContextDir != nil {
err = test . tweakContextDir ( t , contextDir , storageDriver , storageRoot )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error tweaking context directory using test-specific callback: %v" , err )
2020-06-19 06:03:43 +08:00
}
// decide whether we're building just one image for this Dockerfile, or
// one for each line in it after the first, which we'll assume is a FROM
if compareLayers {
// build and compare one line at a time
line := 1
for i := range dockerfileContents {
// scan the byte slice for newlines or the end of the slice, and build using the contents up to that point
if i == len ( dockerfileContents ) - 1 || ( dockerfileContents [ i ] == '\n' && ( i == 0 || dockerfileContents [ i - 1 ] != '\\' ) ) {
if line > 1 || ! bytes . HasPrefix ( dockerfileContents , [ ] byte ( "FROM " ) ) {
// hack: skip trying to build just the first FROM line
t . Run ( fmt . Sprintf ( "%d" , line ) , func ( t * testing . T ) {
2021-09-22 03:06:58 +08:00
testConformanceInternalBuild ( ctx , t , cwd , store , client , dockerClient , fmt . Sprintf ( "%s.%d" , buildahImage , line ) , fmt . Sprintf ( "%s.%d" , dockerImage , line ) , fmt . Sprintf ( "%s.%d" , imagebuilderImage , line ) , contextDir , dockerfileName , dockerfileContents [ : i + 1 ] , test , line , i == len ( dockerfileContents ) - 1 , dockerVersion )
2020-06-19 06:03:43 +08:00
} )
}
line ++
}
}
} else {
// build to completion
2021-09-22 03:06:58 +08:00
testConformanceInternalBuild ( ctx , t , cwd , store , client , dockerClient , buildahImage , dockerImage , imagebuilderImage , contextDir , dockerfileName , dockerfileContents , test , 0 , true , dockerVersion )
2020-06-19 06:03:43 +08:00
}
}
2021-09-22 03:06:58 +08:00
func testConformanceInternalBuild ( ctx context . Context , t * testing . T , cwd string , store storage . Store , client * docker . Client , dockerClient * dockerdockerclient . Client , buildahImage , dockerImage , imagebuilderImage , contextDir , dockerfileName string , dockerfileContents [ ] byte , test testCase , line int , finalOfSeveral bool , dockerVersion [ ] string ) {
2020-06-19 06:03:43 +08:00
var buildahLog , dockerLog , imagebuilderLog [ ] byte
var buildahRef , dockerRef , imagebuilderRef types . ImageReference
// overwrite the Dockerfile in the build context for this run using the
// contents we were passed, which may only be an initial subset of the
// original file, or inlined information, in which case the file didn't
// necessarily exist
2024-08-16 00:50:07 +08:00
err := os . WriteFile ( dockerfileName , dockerfileContents , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error writing Dockerfile at %q" , dockerfileName )
2020-06-19 06:03:43 +08:00
err = os . Chtimes ( dockerfileName , testDate , testDate )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error resetting timestamp on Dockerfile at %q" , dockerfileName )
2020-06-19 06:03:43 +08:00
err = os . Chtimes ( contextDir , testDate , testDate )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error resetting timestamp on context directory at %q" , contextDir )
2020-06-19 06:03:43 +08:00
defer func ( ) {
if t . Failed ( ) {
if test . contextDir != "" {
t . Logf ( "Context %q" , filepath . Join ( cwd , "testdata" , test . contextDir ) )
}
if test . dockerfile != "" {
if test . contextDir != "" {
t . Logf ( "Dockerfile: %q" , filepath . Join ( cwd , "testdata" , test . contextDir , test . dockerfile ) )
} else {
t . Logf ( "Dockerfile: %q" , filepath . Join ( cwd , "testdata" , test . dockerfile ) )
}
}
if ! bytes . HasSuffix ( dockerfileContents , [ ] byte { '\n' } ) && ! bytes . HasSuffix ( dockerfileContents , [ ] byte { '\r' } ) {
dockerfileContents = append ( dockerfileContents , [ ] byte ( "\n(no final end-of-line)" ) ... )
}
t . Logf ( "Dockerfile contents:\n%s" , dockerfileContents )
2022-11-15 00:22:45 +08:00
if dockerignoreContents , err := os . ReadFile ( filepath . Join ( contextDir , ".dockerignore" ) ) ; err == nil {
2020-06-19 06:03:43 +08:00
t . Logf ( ".dockerignore contents:\n%s" , string ( dockerignoreContents ) )
}
}
} ( )
// build using docker
if ! test . withoutDocker {
2021-09-22 03:06:58 +08:00
dockerRef , dockerLog = buildUsingDocker ( ctx , t , client , dockerClient , test , dockerImage , contextDir , dockerfileName , line , finalOfSeveral )
2020-06-19 06:03:43 +08:00
if dockerRef != nil {
defer func ( ) {
err := client . RemoveImageExtended ( dockerImage , docker . RemoveImageOptions {
Context : ctx ,
Force : true ,
} )
assert . Nil ( t , err , "error deleting newly-built-by-docker image %q" , dockerImage )
} ( )
}
saveReport ( ctx , t , dockerRef , filepath . Join ( dockerDir , t . Name ( ) ) , dockerfileContents , dockerLog , dockerVersion )
if finalOfSeveral && compareLayers {
saveReport ( ctx , t , dockerRef , filepath . Join ( dockerDir , t . Name ( ) , ".." ) , dockerfileContents , dockerLog , dockerVersion )
}
}
if t . Failed ( ) {
t . FailNow ( )
}
// build using imagebuilder if we're testing with it, too
if compareImagebuilder && ! test . withoutImagebuilder {
imagebuilderRef , imagebuilderLog = buildUsingImagebuilder ( t , client , test , imagebuilderImage , contextDir , dockerfileName , line , finalOfSeveral )
if imagebuilderRef != nil {
defer func ( ) {
err := client . RemoveImageExtended ( imagebuilderImage , docker . RemoveImageOptions {
Context : ctx ,
Force : true ,
} )
assert . Nil ( t , err , "error deleting newly-built-by-imagebuilder image %q" , imagebuilderImage )
} ( )
}
saveReport ( ctx , t , imagebuilderRef , filepath . Join ( imagebuilderDir , t . Name ( ) ) , dockerfileContents , imagebuilderLog , dockerVersion )
if finalOfSeveral && compareLayers {
saveReport ( ctx , t , imagebuilderRef , filepath . Join ( imagebuilderDir , t . Name ( ) , ".." ) , dockerfileContents , imagebuilderLog , dockerVersion )
}
}
if t . Failed ( ) {
t . FailNow ( )
}
// always build using buildah
buildahRef , buildahLog = buildUsingBuildah ( ctx , t , store , test , buildahImage , contextDir , dockerfileName , line , finalOfSeveral )
if buildahRef != nil {
defer func ( ) {
err := buildahRef . DeleteImage ( ctx , nil )
assert . Nil ( t , err , "error deleting newly-built-by-buildah image %q" , buildahImage )
} ( )
}
saveReport ( ctx , t , buildahRef , filepath . Join ( buildahDir , t . Name ( ) ) , dockerfileContents , buildahLog , nil )
if finalOfSeveral && compareLayers {
saveReport ( ctx , t , buildahRef , filepath . Join ( buildahDir , t . Name ( ) , ".." ) , dockerfileContents , buildahLog , nil )
}
if t . Failed ( ) {
t . FailNow ( )
}
if test . shouldFailAt != 0 {
// the build is expected to fail, so there's no point in comparing information about any images
return
}
// the report on the buildah image should always be there
2023-03-16 04:50:51 +08:00
_ , originalBuildahConfig , ociBuildahConfig , fsBuildah := readReport ( t , filepath . Join ( buildahDir , t . Name ( ) ) )
2020-06-19 06:03:43 +08:00
if t . Failed ( ) {
t . FailNow ( )
}
2024-06-28 23:05:49 +08:00
deleteIdentityLabel := func ( config map [ string ] interface { } ) {
2020-08-06 22:06:38 +08:00
for _ , configName := range [ ] string { "config" , "container_config" } {
if configStruct , ok := config [ configName ] ; ok {
if configMap , ok := configStruct . ( map [ string ] interface { } ) ; ok {
if labels , ok := configMap [ "Labels" ] ; ok {
if labelMap , ok := labels . ( map [ string ] interface { } ) ; ok {
2024-06-28 23:05:49 +08:00
delete ( labelMap , buildah . BuilderIdentityAnnotation )
2020-08-06 22:06:38 +08:00
}
}
}
}
}
}
2024-06-28 23:05:49 +08:00
deleteIdentityLabel ( originalBuildahConfig )
deleteIdentityLabel ( ociBuildahConfig )
2020-06-19 06:03:43 +08:00
var originalDockerConfig , ociDockerConfig , fsDocker map [ string ] interface { }
// the report on the docker image should be there if we expected the build to succeed
if ! test . withoutDocker {
2023-03-16 04:50:51 +08:00
var mediaType string
mediaType , originalDockerConfig , ociDockerConfig , fsDocker = readReport ( t , filepath . Join ( dockerDir , t . Name ( ) ) )
assert . Equal ( t , manifest . DockerV2Schema2MediaType , mediaType , "Image built by docker build didn't use Docker MIME type - tests require update" )
2020-06-19 06:03:43 +08:00
if t . Failed ( ) {
t . FailNow ( )
}
2024-06-28 23:05:49 +08:00
// Some of the base images for our tests were built with buildah, too
deleteIdentityLabel ( originalDockerConfig )
deleteIdentityLabel ( ociDockerConfig )
2020-06-19 06:03:43 +08:00
miss , left , diff , same := compareJSON ( originalDockerConfig , originalBuildahConfig , originalSkip )
if ! same {
assert . Failf ( t , "Image configurations differ as committed in Docker format" , configCompareResult ( miss , left , diff , "buildah" ) )
}
miss , left , diff , same = compareJSON ( ociDockerConfig , ociBuildahConfig , ociSkip )
if ! same {
assert . Failf ( t , "Image configurations differ when converted to OCI format" , configCompareResult ( miss , left , diff , "buildah" ) )
}
miss , left , diff , same = compareJSON ( fsDocker , fsBuildah , append ( fsSkip , test . fsSkip ... ) )
if ! same {
assert . Failf ( t , "Filesystem contents differ" , fsCompareResult ( miss , left , diff , "buildah" ) )
}
}
// the report on the imagebuilder image should be there if we expected the build to succeed
if compareImagebuilder && ! test . withoutImagebuilder {
2023-03-16 04:50:51 +08:00
_ , originalDockerConfig , ociDockerConfig , fsDocker = readReport ( t , filepath . Join ( dockerDir , t . Name ( ) ) )
2021-08-27 23:10:17 +08:00
if t . Failed ( ) {
t . FailNow ( )
}
2023-03-16 04:50:51 +08:00
_ , originalImagebuilderConfig , ociImagebuilderConfig , fsImagebuilder := readReport ( t , filepath . Join ( imagebuilderDir , t . Name ( ) ) )
2020-06-19 06:03:43 +08:00
if t . Failed ( ) {
t . FailNow ( )
}
// compare the reports between docker and imagebuilder
miss , left , diff , same := compareJSON ( originalDockerConfig , originalImagebuilderConfig , originalSkip )
if ! same {
assert . Failf ( t , "Image configurations differ as committed in Docker format" , configCompareResult ( miss , left , diff , "imagebuilder" ) )
}
miss , left , diff , same = compareJSON ( ociDockerConfig , ociImagebuilderConfig , ociSkip )
if ! same {
assert . Failf ( t , "Image configurations differ when converted to OCI format" , configCompareResult ( miss , left , diff , "imagebuilder" ) )
}
miss , left , diff , same = compareJSON ( fsDocker , fsImagebuilder , append ( fsSkip , test . fsSkip ... ) )
if ! same {
assert . Failf ( t , "Filesystem contents differ" , fsCompareResult ( miss , left , diff , "imagebuilder" ) )
}
}
}
func buildUsingBuildah ( ctx context . Context , t * testing . T , store storage . Store , test testCase , buildahImage , contextDir , dockerfileName string , line int , finalOfSeveral bool ) ( buildahRef types . ImageReference , buildahLog [ ] byte ) {
// buildah tests might be using transient mounts. replace "@@TEMPDIR@@"
// in such specifications with the path of the context directory
var transientMounts [ ] string
for _ , mount := range test . transientMounts {
transientMounts = append ( transientMounts , strings . Replace ( mount , "@@TEMPDIR@@" , contextDir , 1 ) )
}
// set up build options
output := & bytes . Buffer { }
2024-06-11 03:18:01 +08:00
if test . compatSetParent != types . OptionalBoolUndefined {
compat := "default"
switch test . compatSetParent {
case types . OptionalBoolFalse :
compat = "false"
case types . OptionalBoolTrue :
compat = "true"
}
t . Logf ( "using buildah flag CompatSetParent = %s" , compat )
}
2024-06-11 03:46:58 +08:00
if test . compatVolumes != types . OptionalBoolUndefined {
compat := "default"
switch test . compatVolumes {
case types . OptionalBoolFalse :
compat = "false"
case types . OptionalBoolTrue :
compat = "true"
}
t . Logf ( "using buildah flag CompatVolumes = %s" , compat )
}
2024-08-16 01:41:05 +08:00
if test . compatScratchConfig != types . OptionalBoolUndefined {
compat := "default"
switch test . compatScratchConfig {
case types . OptionalBoolFalse :
compat = "false"
case types . OptionalBoolTrue :
compat = "true"
}
t . Logf ( "using buildah flag CompatScratchConfig = %s" , compat )
}
2021-03-02 02:07:58 +08:00
options := define . BuildOptions {
2020-06-19 06:03:43 +08:00
ContextDirectory : contextDir ,
2021-02-07 06:49:40 +08:00
CommonBuildOpts : & define . CommonBuildOptions { } ,
NamespaceOptions : [ ] define . NamespaceOption { {
2020-06-19 06:03:43 +08:00
Name : string ( rspec . NetworkNamespace ) ,
Host : true ,
} } ,
TransientMounts : transientMounts ,
Output : buildahImage ,
OutputFormat : buildah . Dockerv2ImageManifest ,
Out : output ,
Err : output ,
Layers : true ,
NoCache : true ,
RemoveIntermediateCtrs : true ,
ForceRmIntermediateCtrs : true ,
2024-06-11 03:18:01 +08:00
CompatSetParent : test . compatSetParent ,
2024-06-11 03:46:58 +08:00
CompatVolumes : test . compatVolumes ,
2024-08-16 01:41:05 +08:00
CompatScratchConfig : test . compatScratchConfig ,
2024-06-28 23:05:49 +08:00
Args : maps . Clone ( test . buildArgs ) ,
2020-06-19 06:03:43 +08:00
}
// build the image and gather output. log the output if the build part of the test failed
imageID , _ , err := imagebuildah . BuildDockerfiles ( ctx , store , options , dockerfileName )
if err != nil {
output . WriteString ( "\n" + err . Error ( ) )
}
outputString := output . String ( )
defer func ( ) {
if t . Failed ( ) {
t . Logf ( "buildah output:\n%s" , outputString )
}
} ( )
buildPost ( t , test , err , "buildah" , outputString , test . buildahRegex , test . buildahErrRegex , line , finalOfSeveral )
// return a reference to the new image, if we succeeded
if err == nil {
buildahRef , err = istorage . Transport . ParseStoreReference ( store , imageID )
assert . Nil ( t , err , "error parsing reference to newly-built image with ID %q" , imageID )
}
return buildahRef , [ ] byte ( outputString )
}
2023-11-16 00:01:29 +08:00
func pullImageIfMissing ( t * testing . T , client * docker . Client , image string ) {
if _ , err := client . InspectImage ( image ) ; err != nil {
repository , tag := docker . ParseRepositoryTag ( image )
if tag == "" {
tag = "latest"
}
pullOptions := docker . PullImageOptions {
Repository : repository ,
Tag : tag ,
}
pullAuths := docker . AuthConfiguration { }
if err := client . PullImage ( pullOptions , pullAuths ) ; err != nil {
t . Fatalf ( "while pulling %q: %v" , image , err )
}
}
}
2021-09-22 03:06:58 +08:00
func buildUsingDocker ( ctx context . Context , t * testing . T , client * docker . Client , dockerClient * dockerdockerclient . Client , test testCase , dockerImage , contextDir , dockerfileName string , line int , finalOfSeveral bool ) ( dockerRef types . ImageReference , dockerLog [ ] byte ) {
2020-06-19 06:03:43 +08:00
// compute the path of the dockerfile relative to the build context
dockerfileRelativePath , err := filepath . Rel ( contextDir , dockerfileName )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "unable to compute path of dockerfile %q relative to context directory %q" , dockerfileName , contextDir )
2020-06-19 06:03:43 +08:00
2023-11-16 00:01:29 +08:00
// read the Dockerfile so that we can pull base images
dockerfileContent , err := os . ReadFile ( dockerfileName )
require . NoErrorf ( t , err , "reading dockerfile %q" , dockerfileName )
for _ , line := range strings . Split ( string ( dockerfileContent ) , "\n" ) {
line = strings . TrimSpace ( line )
if strings . HasPrefix ( line , "# syntax=" ) {
pullImageIfMissing ( t , client , strings . TrimPrefix ( line , "# syntax=" ) )
}
}
parsed , err := imagebuilder . ParseDockerfile ( bytes . NewReader ( dockerfileContent ) )
require . NoErrorf ( t , err , "parsing dockerfile %q" , dockerfileName )
dummyBuilder := imagebuilder . NewBuilder ( nil )
stages , err := imagebuilder . NewStages ( parsed , dummyBuilder )
require . NoErrorf ( t , err , "breaking dockerfile %q up into stages" , dockerfileName )
for i := range stages {
stageBase , err := dummyBuilder . From ( stages [ i ] . Node )
require . NoErrorf ( t , err , "parsing base image for stage %d in %q" , i , dockerfileName )
if stageBase == "" || stageBase == imagebuilder . NoBaseImageSpecifier {
continue
2021-09-22 03:06:58 +08:00
}
2023-11-16 00:01:29 +08:00
needToEnsureBase := true
for j := 0 ; j < i ; j ++ {
if stageBase == stages [ j ] . Name {
needToEnsureBase = false
2021-09-22 03:06:58 +08:00
}
}
2023-11-16 00:01:29 +08:00
if ! needToEnsureBase {
continue
2021-09-22 03:06:58 +08:00
}
2023-11-16 00:01:29 +08:00
pullImageIfMissing ( t , client , stageBase )
}
2023-11-17 16:06:38 +08:00
excludes , err := imagebuilder . ParseDockerignore ( contextDir )
require . NoErrorf ( t , err , "parsing ignores file in %q" , contextDir )
excludes = append ( excludes , "!" + dockerfileRelativePath , "!.dockerignore" )
tarOptions := & archive . TarOptions {
ExcludePatterns : excludes ,
ChownOpts : & idtools . IDPair { UID : 0 , GID : 0 } ,
}
input , err := archive . TarWithOptions ( contextDir , tarOptions )
require . NoErrorf ( t , err , "archiving context directory %q" , contextDir )
defer input . Close ( )
2024-06-28 23:05:49 +08:00
var buildArgs [ ] docker . BuildArg
for k , v := range test . buildArgs {
buildArgs = append ( buildArgs , docker . BuildArg { Name : k , Value : v } )
}
2023-11-16 00:01:29 +08:00
// set up build options
output := & bytes . Buffer { }
options := docker . BuildImageOptions {
Context : ctx ,
Dockerfile : dockerfileRelativePath ,
2023-11-17 16:06:38 +08:00
InputStream : input ,
2023-11-16 00:01:29 +08:00
OutputStream : output ,
Name : dockerImage ,
NoCache : true ,
RmTmpContainer : true ,
ForceRmTmpContainer : true ,
2024-06-28 23:05:49 +08:00
BuildArgs : buildArgs ,
2023-11-16 00:01:29 +08:00
}
2024-06-11 03:18:01 +08:00
if test . dockerUseBuildKit || test . dockerBuilderVersion != "" {
if test . dockerBuilderVersion != "" {
var version string
switch test . dockerBuilderVersion {
case docker . BuilderBuildKit :
version = "BuildKit"
case docker . BuilderV1 :
version = "V1 (classic)"
default :
version = "(unknown)"
}
t . Logf ( "requesting docker builder %s" , version )
options . Version = test . dockerBuilderVersion
} else {
t . Log ( "requesting docker builder BuildKit" )
options . Version = docker . BuilderBuildKit
}
2023-11-16 00:01:29 +08:00
}
// build the image and gather output. log the output if the build part of the test failed
err = client . BuildImage ( options )
if err != nil {
output . WriteString ( "\n" + err . Error ( ) )
2020-06-19 06:03:43 +08:00
}
2021-09-22 03:06:58 +08:00
if _ , err := dockerClient . BuildCachePrune ( ctx , dockertypes . BuildCachePruneOptions { All : true } ) ; err != nil {
t . Logf ( "docker build cache prune: %v" , err )
2020-06-19 06:03:43 +08:00
}
outputString := output . String ( )
defer func ( ) {
if t . Failed ( ) {
t . Logf ( "docker build output:\n%s" , outputString )
}
} ( )
buildPost ( t , test , err , "docker build" , outputString , test . dockerRegex , test . dockerErrRegex , line , finalOfSeveral )
// return a reference to the new image, if we succeeded
if err == nil {
dockerRef , err = daemon . ParseReference ( dockerImage )
assert . Nil ( t , err , "error parsing reference to newly-built image with name %q" , dockerImage )
}
return dockerRef , [ ] byte ( outputString )
}
func buildUsingImagebuilder ( t * testing . T , client * docker . Client , test testCase , imagebuilderImage , contextDir , dockerfileName string , line int , finalOfSeveral bool ) ( imagebuilderRef types . ImageReference , imagebuilderLog [ ] byte ) {
// compute the path of the dockerfile relative to the build context
dockerfileRelativePath , err := filepath . Rel ( contextDir , dockerfileName )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "unable to compute path of dockerfile %q relative to context directory %q" , dockerfileName , contextDir )
2020-06-19 06:03:43 +08:00
// set up build options
output := & bytes . Buffer { }
executor := dockerclient . NewClientExecutor ( client )
executor . Directory = contextDir
executor . Tag = imagebuilderImage
executor . AllowPull = true
executor . Out = output
executor . ErrOut = output
executor . LogFn = func ( format string , args ... interface { } ) {
fmt . Fprintf ( output , "--> %s\n" , fmt . Sprintf ( format , args ... ) )
}
// buildah tests might be using transient mounts. replace "@@TEMPDIR@@"
// in such specifications with the path of the context directory
for _ , mount := range test . transientMounts {
var src , dest string
mountSpec := strings . SplitN ( strings . Replace ( mount , "@@TEMPDIR@@" , contextDir , 1 ) , ":" , 2 )
if len ( mountSpec ) > 1 {
src = mountSpec [ 0 ]
}
dest = mountSpec [ len ( mountSpec ) - 1 ]
executor . TransientMounts = append ( executor . TransientMounts , dockerclient . Mount {
SourcePath : src ,
DestinationPath : dest ,
} )
}
// build the image and gather output. log the output if the build part of the test failed
2024-06-28 23:05:49 +08:00
builder := imagebuilder . NewBuilder ( maps . Clone ( test . buildArgs ) )
2020-06-19 06:03:43 +08:00
node , err := imagebuilder . ParseFile ( filepath . Join ( contextDir , dockerfileRelativePath ) )
if err != nil {
assert . Nil ( t , err , "error parsing Dockerfile: %v" , err )
}
if _ , err = os . Stat ( filepath . Join ( contextDir , ".dockerignore" ) ) ; err == nil {
if builder . Excludes , err = imagebuilder . ParseDockerignore ( contextDir ) ; err != nil {
assert . Nil ( t , err , "error parsing .dockerignore file: %v" , err )
}
}
stages , err := imagebuilder . NewStages ( node , builder )
if err != nil {
assert . Nil ( t , err , "error breaking Dockerfile into stages" )
} else {
if finalExecutor , err := executor . Stages ( builder , stages , "" ) ; err != nil {
output . WriteString ( "\n" + err . Error ( ) )
} else {
if err = finalExecutor . Commit ( stages [ len ( stages ) - 1 ] . Builder ) ; err != nil {
assert . Nil ( t , err , "error committing final stage: %v" , err )
}
}
}
outputString := output . String ( )
defer func ( ) {
if t . Failed ( ) {
t . Logf ( "imagebuilder build output:\n%s" , outputString )
}
for err := range executor . Release ( ) {
t . Logf ( "imagebuilder build post-error: %v" , err )
}
} ( )
buildPost ( t , test , err , "imagebuilder" , outputString , test . imagebuilderRegex , test . imagebuilderErrRegex , line , finalOfSeveral )
// return a reference to the new image, if we succeeded
if err == nil {
imagebuilderRef , err = daemon . ParseReference ( imagebuilderImage )
assert . Nil ( t , err , "error parsing reference to newly-built image with name %q" , imagebuilderImage )
}
return imagebuilderRef , [ ] byte ( outputString )
}
func buildPost ( t * testing . T , test testCase , err error , buildTool , outputString , stdoutRegex , stderrRegex string , line int , finalOfSeveral bool ) {
// check if the build succeeded or failed, whichever was expected
if test . shouldFailAt != 0 && ( line == 0 || line >= test . shouldFailAt ) {
// this is expected to fail, and we're either at/past
// the line where it should fail, or we're not going
// line-by-line
assert . NotNil ( t , err , fmt . Sprintf ( "%s build was expected to fail, but succeeded" , buildTool ) )
} else {
assert . Nil ( t , err , fmt . Sprintf ( "%s build was expected to succeed, but failed" , buildTool ) )
}
// if the build failed, and we have an error message we expected, check for it
if err != nil && test . failureRegex != "" {
outputTokens := strings . Join ( strings . Fields ( err . Error ( ) ) , " " )
assert . Regexpf ( t , regexp . MustCompile ( test . failureRegex ) , outputTokens , "build failure did not match %q" , test . failureRegex )
}
// if this is the last image we're building for this case, we can scan
// the build log for expected messages
if finalOfSeveral {
outputTokens := strings . Join ( strings . Fields ( outputString ) , " " )
// check for expected output
if stdoutRegex != "" {
assert . Regexpf ( t , regexp . MustCompile ( stdoutRegex ) , outputTokens , "build output did not match %q" , stdoutRegex )
}
if stderrRegex != "" {
assert . Regexpf ( t , regexp . MustCompile ( stderrRegex ) , outputTokens , "build error did not match %q" , stderrRegex )
}
}
}
// FSTree holds the information we have about an image's filesystem
type FSTree struct {
Layers [ ] Layer ` json:"layers,omitempty" `
Tree FSEntry ` json:"tree,omitempty" `
}
// Layer keeps track of the digests and contents of a layer blob
type Layer struct {
UncompressedDigest digest . Digest ` json:"uncompressed-digest,omitempty" `
CompressedDigest digest . Digest ` json:"compressed-digest,omitempty" `
Headers [ ] FSHeader ` json:"-,omitempty" `
}
// FSHeader is the parts of the tar.Header for an entry in a layer blob that
// are relevant
type FSHeader struct {
Typeflag byte ` json:"typeflag,omitempty" `
Name string ` json:"name,omitempty" `
Linkname string ` json:"linkname,omitempty" `
2019-07-25 22:10:03 +08:00
Size int64 ` json:"size" `
2020-06-19 06:03:43 +08:00
Mode int64 ` json:"mode,omitempty" `
2019-07-25 22:10:03 +08:00
UID int ` json:"uid" `
GID int ` json:"gid" `
2020-06-19 06:03:43 +08:00
ModTime time . Time ` json:"mtime,omitempty" `
2020-12-22 00:19:56 +08:00
Devmajor int64 ` json:"devmajor,omitempty" `
2020-06-19 06:03:43 +08:00
Devminor int64 ` json:"devminor,omitempty" `
Xattrs map [ string ] string ` json:"xattrs,omitempty" `
Digest digest . Digest ` json:"digest,omitempty" `
}
2020-09-11 22:06:31 +08:00
// FSEntry stores one item in a filesystem tree. If it represents a directory,
2020-06-19 06:03:43 +08:00
// its contents are stored as its children
type FSEntry struct {
FSHeader
Children map [ string ] * FSEntry ` json:"(dir),omitempty" `
}
// fsHeaderForEntry converts a tar header to an FSHeader, in the process
// discarding some fields which we don't care to compare
func fsHeaderForEntry ( hdr * tar . Header ) FSHeader {
return FSHeader {
Typeflag : hdr . Typeflag ,
Name : hdr . Name ,
Linkname : hdr . Linkname ,
Size : hdr . Size ,
2023-05-31 02:30:49 +08:00
Mode : ( hdr . Mode & int64 ( fs . ModePerm ) ) ,
2020-06-19 06:03:43 +08:00
UID : hdr . Uid ,
GID : hdr . Gid ,
ModTime : hdr . ModTime ,
Devmajor : hdr . Devmajor ,
Devminor : hdr . Devminor ,
Xattrs : hdr . Xattrs , // nolint:staticcheck
}
}
// save information about the specified image to the specified directory
func saveReport ( ctx context . Context , t * testing . T , ref types . ImageReference , directory string , dockerfileContents [ ] byte , buildLog [ ] byte , version [ ] string ) {
imageName := ""
// make sure the directory exists
2024-08-16 00:50:07 +08:00
err := os . MkdirAll ( directory , 0 o755 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error ensuring directory %q exists for storing a report" , directory )
2020-06-19 06:03:43 +08:00
// save the Dockerfile that was used to generate the image
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "Dockerfile" ) , dockerfileContents , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving Dockerfile for image %q" , imageName )
2020-06-19 06:03:43 +08:00
// save the log generated while building the image
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "build.log" ) , buildLog , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving build log for image %q" , imageName )
2020-06-19 06:03:43 +08:00
// save the version information
if len ( version ) > 0 {
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "version" ) , [ ] byte ( strings . Join ( version , "\n" ) + "\n" ) , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving builder version information for image %q" , imageName )
2020-06-19 06:03:43 +08:00
}
// open the image for reading
if ref == nil {
return
}
imageName = transports . ImageName ( ref )
src , err := ref . NewImageSource ( ctx , nil )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error opening image %q as source to read its configuration" , imageName )
2020-06-19 06:03:43 +08:00
closer := io . Closer ( src )
defer func ( ) {
closer . Close ( )
} ( )
img , err := image . FromSource ( ctx , nil , src )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error opening image %q to read its configuration" , imageName )
2020-06-19 06:03:43 +08:00
closer = img
2023-03-16 04:50:51 +08:00
// read the manifest in its original form
rawManifest , _ , err := src . GetManifest ( ctx , nil )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading raw manifest from image %q" , imageName )
2020-06-19 06:03:43 +08:00
// read the config blob in its original form
rawConfig , err := img . ConfigBlob ( ctx )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading configuration from image %q" , imageName )
2020-06-19 06:03:43 +08:00
// read the config blob, converted to OCI format by the image library, and re-encode it
ociConfig , err := img . OCIConfig ( ctx )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading OCI-format configuration from image %q" , imageName )
2020-06-19 06:03:43 +08:00
encodedConfig , err := json . Marshal ( ociConfig )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error encoding OCI-format configuration from image %q for saving" , imageName )
2023-03-16 04:50:51 +08:00
// save the manifest in its original form
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "manifest.json" ) , rawManifest , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving original manifest from image %q" , imageName )
2020-06-19 06:03:43 +08:00
// save the config blob in the OCI format
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "oci-config.json" ) , encodedConfig , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving OCI-format configuration from image %q" , imageName )
2020-06-19 06:03:43 +08:00
// save the config blob in its original format
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "config.json" ) , rawConfig , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving original configuration from image %q" , imageName )
2020-06-19 06:03:43 +08:00
// start pulling layer information
layerBlobInfos , err := img . LayerInfosForCopy ( ctx )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading blob infos for image %q" , imageName )
2020-06-19 06:03:43 +08:00
if len ( layerBlobInfos ) == 0 {
layerBlobInfos = img . LayerInfos ( )
}
fstree := FSTree { Tree : FSEntry { Children : make ( map [ string ] * FSEntry ) } }
// grab digest and header information from the layer blob
for _ , layerBlobInfo := range layerBlobInfos {
rc , _ , err := src . GetBlob ( ctx , layerBlobInfo , nil )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading blob %+v for image %q" , layerBlobInfo , imageName )
2020-06-19 06:03:43 +08:00
defer rc . Close ( )
layer := summarizeLayer ( t , imageName , layerBlobInfo , rc )
fstree . Layers = append ( fstree . Layers , layer )
}
// apply the header information from blobs, in the order they're listed
// in the config blob, to produce what we think the filesystem tree
// would look like
for _ , diffID := range ociConfig . RootFS . DiffIDs {
var layer * Layer
for i := range fstree . Layers {
if fstree . Layers [ i ] . CompressedDigest == diffID {
layer = & fstree . Layers [ i ]
break
}
if fstree . Layers [ i ] . UncompressedDigest == diffID {
layer = & fstree . Layers [ i ]
break
}
}
if layer == nil {
require . Failf ( t , "missing layer diff" , "config for image %q specifies a layer with diffID %q, but we found no layer blob matching that digest" , imageName , diffID )
}
applyLayerToFSTree ( t , layer , & fstree . Tree )
}
2020-09-11 22:06:31 +08:00
// encode the filesystem tree information and save it to a file,
2020-06-19 06:03:43 +08:00
// discarding the layer summaries because different tools may choose
// between marking a directory as opaque and removing each of its
// contents individually, which would produce the same result, so
// there's no point in saving them for comparison later
encodedFSTree , err := json . Marshal ( fstree . Tree )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error encoding filesystem tree from image %q for saving" , imageName )
2024-08-16 00:50:07 +08:00
err = os . WriteFile ( filepath . Join ( directory , "fs.json" ) , encodedFSTree , 0 o644 )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error saving filesystem tree from image %q" , imageName )
2020-06-19 06:03:43 +08:00
}
// summarizeLayer reads a blob and returns a summary of the parts of its contents that we care about
func summarizeLayer ( t * testing . T , imageName string , blobInfo types . BlobInfo , reader io . Reader ) ( layer Layer ) {
compressedDigest := digest . Canonical . Digester ( )
2024-03-07 03:13:06 +08:00
counter := ioutils . NewWriteCounter ( compressedDigest . Hash ( ) )
compressionAlgorithm , _ , reader , err := compression . DetectCompressionFormat ( reader )
require . NoErrorf ( t , err , "error checking if blob %+v for image %q is compressed" , blobInfo , imageName )
uncompressedBlob , wasCompressed , err := compression . AutoDecompress ( io . TeeReader ( reader , counter ) )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error decompressing blob %+v for image %q" , blobInfo , imageName )
2020-06-19 06:03:43 +08:00
defer uncompressedBlob . Close ( )
uncompressedDigest := digest . Canonical . Digester ( )
2024-03-07 03:13:06 +08:00
tarToRead := io . TeeReader ( uncompressedBlob , uncompressedDigest . Hash ( ) )
tr := tar . NewReader ( tarToRead )
2020-06-19 06:03:43 +08:00
hdr , err := tr . Next ( )
for err == nil {
header := fsHeaderForEntry ( hdr )
if hdr . Size != 0 {
contentDigest := digest . Canonical . Digester ( )
n , err := io . Copy ( contentDigest . Hash ( ) , tr )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error digesting contents of %q from layer %+v for image %q" , hdr . Name , blobInfo , imageName )
2020-06-19 06:03:43 +08:00
require . Equal ( t , hdr . Size , n , "error reading contents of %q from layer %+v for image %q: wrong size" , hdr . Name , blobInfo , imageName )
header . Digest = contentDigest . Digest ( )
}
layer . Headers = append ( layer . Headers , header )
hdr , err = tr . Next ( )
}
require . Equal ( t , io . EOF , err , "unexpected error reading layer contents %+v for image %q" , blobInfo , imageName )
2024-03-07 03:13:06 +08:00
_ , err = io . Copy ( io . Discard , tarToRead )
require . NoError ( t , err , "reading out any not-usually-present zero padding at the end" )
2020-06-19 06:03:43 +08:00
layer . CompressedDigest = compressedDigest . Digest ( )
2024-03-07 03:13:06 +08:00
blobFormatDescription := "uncompressed"
if wasCompressed {
if compressionAlgorithm . Name ( ) != "" {
blobFormatDescription = "compressed with " + compressionAlgorithm . Name ( )
} else {
blobFormatDescription = "compressed (?)"
}
}
require . Equalf ( t , blobInfo . Digest , layer . CompressedDigest , "calculated digest of %s blob didn't match expected digest (expected length %d, actual length %d)" , blobFormatDescription , blobInfo . Size , counter . Count )
2020-06-19 06:03:43 +08:00
layer . UncompressedDigest = uncompressedDigest . Digest ( )
return layer
}
// applyLayerToFSTree updates the in-memory summary of a tree to incorporate
// changes described in the layer. This is a little naive, in that we don't
// expect the pathname to include symlinks, which we don't resolve, as
// components, but tools that currently generate layer diffs don't create
// those.
func applyLayerToFSTree ( t * testing . T , layer * Layer , root * FSEntry ) {
for i , entry := range layer . Headers {
if entry . Typeflag == tar . TypeLink {
// if the entry is a hard link, replace it with the
// contents of the hard-linked file
replaced := false
name := entry . Name
for j , otherEntry := range layer . Headers {
if j >= i {
break
}
if otherEntry . Name == entry . Linkname {
entry = otherEntry
entry . Name = name
replaced = true
break
}
}
if ! replaced {
require . Fail ( t , "layer diff error" , "hardlink entry referenced a file that isn't in the layer" )
}
}
// parse the name from the entry, and don't get tripped up by a final '/'
dirEntry := root
components := strings . Split ( strings . Trim ( entry . Name , string ( os . PathSeparator ) ) , string ( os . PathSeparator ) )
require . NotEmpty ( t , entry . Name , "layer diff error" , "entry has no name" )
require . NotZerof ( t , len ( components ) , "entry name %q has no components" , entry . Name )
require . NotZerof ( t , components [ 0 ] , "entry name %q has no components" , entry . Name )
// "split" the final part of the path from the rest
base := components [ len ( components ) - 1 ]
components = components [ : len ( components ) - 1 ]
// find the directory that contains this entry
for i , component := range components {
// this should be a parent directory, so check if it looks like a parent directory
if dirEntry . Children == nil {
require . Failf ( t , "layer diff error" , "layer diff %q includes entry for %q, but %q is not a directory" , layer . UncompressedDigest , entry . Name , strings . Join ( components [ : i ] , string ( os . PathSeparator ) ) )
}
// if the directory is already there, move into it
if child , ok := dirEntry . Children [ component ] ; ok {
dirEntry = child
continue
}
// if the directory should be there, but we haven't
// created it yet, blame the tool that generated this
// layer diff
require . Failf ( t , "layer diff error" , "layer diff %q includes entry for %q, but %q doesn't exist" , layer . UncompressedDigest , entry . Name , strings . Join ( components [ : i ] , string ( os . PathSeparator ) ) )
}
// if the current directory is marked as "opaque", remove all
// of its contents
if base == ".wh..opq" {
dirEntry . Children = make ( map [ string ] * FSEntry )
continue
}
// if the item is a whiteout, strip the "this is a whiteout
// entry" prefix and remove the item it names
if strings . HasPrefix ( base , ".wh." ) {
delete ( dirEntry . Children , strings . TrimPrefix ( base , ".wh." ) )
continue
}
2020-12-12 02:22:39 +08:00
// if the item already exists, make sure we don't get confused
// by replacing a directory with a non-directory or vice-versa
2020-06-19 06:03:43 +08:00
if child , ok := dirEntry . Children [ base ] ; ok {
if child . Children != nil {
// it's a directory
if entry . Typeflag == tar . TypeDir {
// new entry is a directory, too. no
// sweat, just update the metadata
child . FSHeader = entry
continue
}
2020-12-12 02:22:39 +08:00
// nope, a directory no longer
2020-06-19 06:03:43 +08:00
} else {
// it's not a directory
if entry . Typeflag != tar . TypeDir {
// new entry is not a directory, too.
// no sweat, just update the metadata
dirEntry . Children [ base ] . FSHeader = entry
continue
}
2020-12-12 02:22:39 +08:00
// well, it's a directory now
2020-06-19 06:03:43 +08:00
}
}
2020-12-12 02:22:39 +08:00
// the item doesn't already exist, or it needs to be replaced, so we need to add it
2020-06-19 06:03:43 +08:00
var children map [ string ] * FSEntry
if entry . Typeflag == tar . TypeDir {
// only directory entries can hold items
children = make ( map [ string ] * FSEntry )
}
dirEntry . Children [ base ] = & FSEntry { FSHeader : entry , Children : children }
}
}
// read information about the specified image from the specified directory
2023-03-16 04:50:51 +08:00
func readReport ( t * testing . T , directory string ) ( manifestType string , original , oci , fs map [ string ] interface { } ) {
// read the manifest in the as-committed format, whatever that is
originalManifest , err := os . ReadFile ( filepath . Join ( directory , "manifest.json" ) )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading manifest %q" , filepath . Join ( directory , "manifest.json" ) )
2023-03-16 04:50:51 +08:00
// dump it into a map
manifest := make ( map [ string ] interface { } )
err = json . Unmarshal ( originalManifest , & manifest )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error decoding manifest %q" , filepath . Join ( directory , "manifest.json" ) )
2023-03-16 04:50:51 +08:00
if str , ok := manifest [ "mediaType" ] . ( string ) ; ok {
manifestType = str
}
2020-06-19 06:03:43 +08:00
// read the config in the as-committed (docker) format
2022-11-15 00:22:45 +08:00
originalConfig , err := os . ReadFile ( filepath . Join ( directory , "config.json" ) )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading configuration file %q" , filepath . Join ( directory , "config.json" ) )
2020-06-19 06:03:43 +08:00
// dump it into a map
original = make ( map [ string ] interface { } )
err = json . Unmarshal ( originalConfig , & original )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error decoding configuration from file %q" , filepath . Join ( directory , "config.json" ) )
2020-06-19 06:03:43 +08:00
// read the config in converted-to-OCI format
2023-03-16 04:50:51 +08:00
ociConfig , err := os . ReadFile ( filepath . Join ( directory , "oci-config.json" ) )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading OCI configuration file %q" , filepath . Join ( directory , "oci-config.json" ) )
2020-06-19 06:03:43 +08:00
// dump it into a map
oci = make ( map [ string ] interface { } )
err = json . Unmarshal ( ociConfig , & oci )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error decoding OCI configuration from file %q" , filepath . Join ( directory , "oci.json" ) )
2020-06-19 06:03:43 +08:00
// read the filesystem
2022-11-15 00:22:45 +08:00
fsInfo , err := os . ReadFile ( filepath . Join ( directory , "fs.json" ) )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error reading filesystem summary file %q" , filepath . Join ( directory , "fs.json" ) )
2020-06-19 06:03:43 +08:00
// dump it into a map for comparison
fs = make ( map [ string ] interface { } )
err = json . Unmarshal ( fsInfo , & fs )
2023-11-08 06:45:03 +08:00
require . NoErrorf ( t , err , "error decoding filesystem summary from file %q" , filepath . Join ( directory , "fs.json" ) )
2020-06-19 06:03:43 +08:00
// return both
2023-03-16 04:50:51 +08:00
return manifestType , original , oci , fs
2020-06-19 06:03:43 +08:00
}
2023-11-08 06:45:03 +08:00
// contains is used to check if item exists in []string or not, ignoring case
2020-06-19 06:03:43 +08:00
func contains ( slice [ ] string , item string ) bool {
for _ , s := range slice {
if strings . EqualFold ( s , item ) {
return true
}
}
return false
}
// addPrefix prepends the given prefix to each string in []string.
// The prefix and the string are joined with ":"
func addPrefix ( a [ ] string , prefix string ) [ ] string {
b := make ( [ ] string , 0 , len ( a ) )
for _ , s := range a {
b = append ( b , prefix + ":" + s )
}
return b
}
// diffDebug returns a row for a tabwriter that summarizes a field name and the
// values for that field in two documents
func diffDebug ( k string , a , b interface { } ) string {
if k == "mode" {
// force modes to be displayed in octal instead of decimal
a , aok := a . ( float64 )
b , bok := b . ( float64 )
if aok && bok {
return fmt . Sprintf ( "%v\t0%o\t0%o\n" , k , int64 ( a ) , int64 ( b ) )
}
}
return fmt . Sprintf ( "%v\t%v\t%v\n" , k , a , b )
}
// compareJSON compares two parsed JSON structures. missKeys and leftKeys are
// lists of field names present only in the first map or the second,
// respectively, while diffKeys is a list of items which are present in both
// maps, but which have different values, formatted with diffDebug.
func compareJSON ( a , b map [ string ] interface { } , skip [ ] string ) ( missKeys , leftKeys , diffKeys [ ] string , isSame bool ) {
isSame = true
for k , v := range a {
vb , ok := b [ k ]
if ok {
// remove this item from b. when we're done, all that's
// left in b will be the items that weren't also in a.
delete ( b , k )
}
if contains ( skip , k ) {
continue
}
if ! ok {
// key is in a, but not in b
missKeys = append ( missKeys , k )
isSame = false
continue
}
if reflect . TypeOf ( v ) != reflect . TypeOf ( vb ) {
if reflect . TypeOf ( v ) == nil && reflect . ValueOf ( vb ) . Len ( ) == 0 {
continue
}
if reflect . TypeOf ( vb ) == nil && reflect . ValueOf ( v ) . Len ( ) == 0 {
continue
}
diffKeys = append ( diffKeys , diffDebug ( k , v , vb ) )
isSame = false
continue
}
switch v . ( type ) {
case map [ string ] interface { } :
// this field in the object is itself an object (e.g.
// "config" or "container_config"), so recursively
// compare them
2021-08-27 23:10:17 +08:00
var nextSkip [ ] string
prefix := k + ":"
2020-06-19 06:03:43 +08:00
for _ , s := range skip {
2021-08-27 23:10:17 +08:00
if strings . HasPrefix ( s , prefix ) {
nextSkip = append ( nextSkip , strings . TrimPrefix ( s , prefix ) )
2020-06-19 06:03:43 +08:00
}
}
submiss , subleft , subdiff , ok := compareJSON ( v . ( map [ string ] interface { } ) , vb . ( map [ string ] interface { } ) , nextSkip )
missKeys = append ( missKeys , addPrefix ( submiss , k ) ... )
leftKeys = append ( leftKeys , addPrefix ( subleft , k ) ... )
diffKeys = append ( diffKeys , addPrefix ( subdiff , k ) ... )
if ! ok {
isSame = false
}
case [ ] interface { } :
// this field in the object is an array; make sure both
// arrays have the same set of elements, which is more
// or less correct for labels and environment
// variables.
// this will break if it tries to compare an array of
// objects like "history", since maps, slices, and
// functions can't be used as keys in maps
tmpa := v . ( [ ] interface { } )
tmpb := vb . ( [ ] interface { } )
if len ( tmpa ) != len ( tmpb ) {
diffKeys = append ( diffKeys , diffDebug ( k , v , vb ) )
isSame = false
break
}
m := make ( map [ interface { } ] struct { } )
for i := 0 ; i < len ( tmpb ) ; i ++ {
m [ tmpb [ i ] ] = struct { } { }
}
for i := 0 ; i < len ( tmpa ) ; i ++ {
if _ , ok := m [ tmpa [ i ] ] ; ! ok {
diffKeys = append ( diffKeys , diffDebug ( k , v , vb ) )
isSame = false
break
}
}
default :
// this field in the object is neither an object nor an
// array, so assume it's a scalar item
if ! reflect . DeepEqual ( v , vb ) {
diffKeys = append ( diffKeys , diffDebug ( k , v , vb ) )
isSame = false
}
}
}
if len ( b ) > 0 {
for k := range b {
if ! contains ( skip , k ) {
leftKeys = append ( leftKeys , k )
}
}
}
2024-06-04 02:44:47 +08:00
return slices . Clone ( missKeys ) , slices . Clone ( leftKeys ) , slices . Clone ( diffKeys ) , isSame
2020-06-19 06:03:43 +08:00
}
// configCompareResult summarizes the output of compareJSON for display
func configCompareResult ( miss , left , diff [ ] string , notDocker string ) string {
var buffer bytes . Buffer
if len ( miss ) > 0 {
buffer . WriteString ( fmt . Sprintf ( "Fields missing from %s version: %s\n" , notDocker , strings . Join ( miss , " " ) ) )
}
if len ( left ) > 0 {
buffer . WriteString ( fmt . Sprintf ( "Fields which only exist in %s version: %s\n" , notDocker , strings . Join ( left , " " ) ) )
}
if len ( diff ) > 0 {
buffer . WriteString ( "Fields present in both versions have different values:\n" )
tw := tabwriter . NewWriter ( & buffer , 1 , 1 , 8 , ' ' , 0 )
if _ , err := tw . Write ( [ ] byte ( fmt . Sprintf ( "Field\tDocker\t%s\n" , notDocker ) ) ) ; err != nil {
panic ( err )
}
for _ , d := range diff {
if _ , err := tw . Write ( [ ] byte ( d ) ) ; err != nil {
panic ( err )
}
}
tw . Flush ( )
}
return buffer . String ( )
}
// fsCompareResult summarizes the output of compareJSON for display
func fsCompareResult ( miss , left , diff [ ] string , notDocker string ) string {
var buffer bytes . Buffer
fixup := func ( names [ ] string ) [ ] string {
n := make ( [ ] string , 0 , len ( names ) )
for _ , name := range names {
n = append ( n , strings . ReplaceAll ( strings . ReplaceAll ( name , ":(dir):" , "/" ) , "(dir):" , "/" ) )
}
return n
}
if len ( miss ) > 0 {
buffer . WriteString ( fmt . Sprintf ( "Content missing from %s version: %s\n" , notDocker , strings . Join ( fixup ( miss ) , " " ) ) )
}
if len ( left ) > 0 {
buffer . WriteString ( fmt . Sprintf ( "Content which only exists in %s version: %s\n" , notDocker , strings . Join ( fixup ( left ) , " " ) ) )
}
if len ( diff ) > 0 {
buffer . WriteString ( "File attributes in both versions have different values:\n" )
tw := tabwriter . NewWriter ( & buffer , 1 , 1 , 8 , ' ' , 0 )
if _ , err := tw . Write ( [ ] byte ( fmt . Sprintf ( "File:attr\tDocker\t%s\n" , notDocker ) ) ) ; err != nil {
panic ( err )
}
for _ , d := range fixup ( diff ) {
if _ , err := tw . Write ( [ ] byte ( d ) ) ; err != nil {
panic ( err )
}
}
tw . Flush ( )
}
return buffer . String ( )
}
2024-08-16 00:50:07 +08:00
type (
testCaseTweakContextDirFn func ( * testing . T , string , string , string ) error
testCase struct {
name string // name of the test
dockerfileContents string // inlined Dockerfile content to use instead of possible file in the build context
dockerfile string // name of the Dockerfile, relative to contextDir, if not Dockerfile
contextDir string // name of context subdirectory, if there is one to be copied
tweakContextDir testCaseTweakContextDirFn // callback to make updates to the temporary build context before we build it
shouldFailAt int // line where a build is expected to fail (starts with 1, 0 if it should succeed
buildahRegex string // if set, expect this to be present in output
dockerRegex string // if set, expect this to be present in output
imagebuilderRegex string // if set, expect this to be present in output
buildahErrRegex string // if set, expect this to be present in output
dockerErrRegex string // if set, expect this to be present in output
imagebuilderErrRegex string // if set, expect this to be present in output
failureRegex string // if set, expect this to be present in output when the build fails
withoutImagebuilder bool // don't build this with imagebuilder, because it depends on a buildah-specific feature
withoutDocker bool // don't build this with docker, because it depends on a buildah-specific feature
dockerUseBuildKit bool // if building with docker, request that dockerd use buildkit
dockerBuilderVersion docker . BuilderVersion // if building with docker, request the specific builder
testUsingSetParent bool // test both with old (gets set) and new (left blank) config.Parent behavior
compatSetParent types . OptionalBool // placeholder for a value to set for the buildah compatSetParent flag
testUsingVolumes bool // test both with old (preserved) and new (just a config note) volume behavior
compatVolumes types . OptionalBool // placeholder for a value to set for the buildah compatVolumes flag
2024-08-16 01:41:05 +08:00
compatScratchConfig types . OptionalBool // placeholder for a value to set for the buildah compatScratchConfig flag
2024-08-16 00:50:07 +08:00
transientMounts [ ] string // one possible buildah-specific feature
fsSkip [ ] string // expected filesystem differences, typically timestamps on files or directories we create or modify during the build and don't reset
buildArgs map [ string ] string // build args to supply, as if --build-arg was used
}
)
2020-06-19 06:03:43 +08:00
var internalTestCases = [ ] testCase {
{
name : "shell test" ,
dockerfile : "Dockerfile.shell" ,
buildahRegex : "(?s)[0-9a-z]+(.*)--" ,
dockerRegex : "(?s)RUN env.*?Running in [0-9a-z]+(.*?)---" ,
} ,
2024-08-09 02:20:31 +08:00
{
2024-08-14 03:04:04 +08:00
name : "copy-escape-glob" ,
contextDir : "copy-escape-glob" ,
fsSkip : [ ] string { "(dir):app:mtime" , "(dir):app2:mtime" , "(dir):app3:mtime" , "(dir):app4:mtime" , "(dir):app5:mtime" } ,
tweakContextDir : func ( t * testing . T , contextDir , _ , _ string ) error {
appDir := filepath . Join ( contextDir , "app" )
jklDir := filepath . Join ( appDir , "jkl?" )
require . NoError ( t , os . Mkdir ( jklDir , 0 o700 ) )
jklFile := filepath . Join ( jklDir , "file.txt" )
require . NoError ( t , os . WriteFile ( jklFile , [ ] byte ( "another" ) , 0 o600 ) )
nopeDir := filepath . Join ( appDir , "n?pe" )
require . NoError ( t , os . Mkdir ( nopeDir , 0 o700 ) )
nopeFile := filepath . Join ( nopeDir , "file.txt" )
require . NoError ( t , os . WriteFile ( nopeFile , [ ] byte ( "and also" ) , 0 o600 ) )
stuvDir := filepath . Join ( appDir , "st*uv" )
require . NoError ( t , os . Mkdir ( stuvDir , 0 o700 ) )
stuvFile := filepath . Join ( stuvDir , "file.txt" )
require . NoError ( t , os . WriteFile ( stuvFile , [ ] byte ( "and yet" ) , 0 o600 ) )
return nil
} ,
2024-08-09 02:20:31 +08:00
dockerUseBuildKit : true ,
} ,
2020-06-19 06:03:43 +08:00
{
name : "copy file to root" ,
dockerfile : "Dockerfile.copyfrom_1" ,
buildahRegex : "[-rw]+.*?/a" ,
fsSkip : [ ] string { "(dir):a:mtime" } ,
} ,
{
name : "copy file to same file" ,
dockerfile : "Dockerfile.copyfrom_2" ,
buildahRegex : "[-rw]+.*?/a" ,
fsSkip : [ ] string { "(dir):a:mtime" } ,
} ,
{
name : "copy file to workdir" ,
dockerfile : "Dockerfile.copyfrom_3" ,
buildahRegex : "[-rw]+.*?/b/a" ,
fsSkip : [ ] string { "(dir):b:mtime" , "(dir):b:(dir):a:mtime" } ,
} ,
{
name : "copy file to workdir rename" ,
dockerfile : "Dockerfile.copyfrom_3_1" ,
buildahRegex : "[-rw]+.*?/b/b" ,
fsSkip : [ ] string { "(dir):b:mtime" , "(dir):b:(dir):a:mtime" } ,
} ,
{
name : "copy folder contents to higher level" ,
dockerfile : "Dockerfile.copyfrom_4" ,
buildahRegex : "(?s)[-rw]+.*?/b/1.*?[-rw]+.*?/b/2.*?/b.*?[-rw]+.*?1.*?[-rw]+.*?2" ,
buildahErrRegex : "/a: No such file or directory" ,
fsSkip : [ ] string { "(dir):b:mtime" } ,
} ,
{
name : "copy wildcard folder contents to higher level" ,
dockerfile : "Dockerfile.copyfrom_5" ,
buildahRegex : "(?s)[-rw]+.*?/b/1.*?[-rw]+.*?/b/2.*?/b.*?[-rw]+.*?1.*?[-rw]+.*?2" ,
buildahErrRegex : "(?s)/a: No such file or directory.*?/b/a: No such file or directory.*?/b/b: No such file or director" ,
fsSkip : [ ] string { "(dir):b:mtime" , "(dir):b:(dir):1:mtime" , "(dir):b:(dir):2:mtime" } ,
} ,
{
name : "copy folder with dot contents to higher level" ,
dockerfile : "Dockerfile.copyfrom_6" ,
buildahRegex : "(?s)[-rw]+.*?/b/1.*?[-rw]+.*?/b/2.*?/b.*?[-rw]+.*?1.*?[-rw]+.*?2" ,
buildahErrRegex : "(?s)/a: No such file or directory.*?/b/a: No such file or directory.*?/b/b: No such file or director" ,
fsSkip : [ ] string { "(dir):b:mtime" , "(dir):b:(dir):1:mtime" , "(dir):b:(dir):2:mtime" } ,
} ,
{
name : "copy root file to different root name" ,
dockerfile : "Dockerfile.copyfrom_7" ,
buildahRegex : "[-rw]+.*?/a" ,
buildahErrRegex : "/b: No such file or directory" ,
fsSkip : [ ] string { "(dir):a:mtime" } ,
} ,
{
name : "copy nested file to different root name" ,
dockerfile : "Dockerfile.copyfrom_8" ,
buildahRegex : "[-rw]+.*?/a" ,
buildahErrRegex : "/b: No such file or directory" ,
fsSkip : [ ] string { "(dir):a:mtime" } ,
} ,
{
name : "copy file to deeper directory with explicit slash" ,
dockerfile : "Dockerfile.copyfrom_9" ,
buildahRegex : "[-rw]+.*?/a/b/c/1" ,
buildahErrRegex : "/a/b/1: No such file or directory" ,
fsSkip : [ ] string { "(dir):a:mtime" , "(dir):a:(dir):b:mtime" , "(dir):a:(dir):b:(dir):c:mtime" , "(dir):a:(dir):b:(dir):c:(dir):1:mtime" } ,
} ,
{
name : "copy file to deeper directory without explicit slash" ,
dockerfile : "Dockerfile.copyfrom_10" ,
buildahRegex : "[-rw]+.*?/a/b/c" ,
buildahErrRegex : "/a/b/1: No such file or directory" ,
fsSkip : [ ] string { "(dir):a:mtime" , "(dir):a:(dir):b:mtime" , "(dir):a:(dir):b:(dir):c:mtime" } ,
} ,
{
name : "copy directory to deeper directory without explicit slash" ,
dockerfile : "Dockerfile.copyfrom_11" ,
buildahRegex : "[-rw]+.*?/a/b/c/1" ,
buildahErrRegex : "/a/b/1: No such file or directory" ,
fsSkip : [ ] string {
"(dir):a:mtime" , "(dir):a:(dir):b:mtime" , "(dir):a:(dir):b:(dir):c:mtime" ,
"(dir):a:(dir):b:(dir):c:(dir):1:mtime" ,
} ,
} ,
{
name : "copy directory to root without explicit slash" ,
dockerfile : "Dockerfile.copyfrom_12" ,
buildahRegex : "[-rw]+.*?/a/1" ,
buildahErrRegex : "/a/a: No such file or directory" ,
fsSkip : [ ] string { "(dir):a:mtime" , "(dir):a:(dir):1:mtime" } ,
} ,
{
name : "copy directory trailing to root without explicit slash" ,
dockerfile : "Dockerfile.copyfrom_13" ,
buildahRegex : "[-rw]+.*?/a/1" ,
buildahErrRegex : "/a/a: No such file or directory" ,
fsSkip : [ ] string { "(dir):a:mtime" , "(dir):a:(dir):1:mtime" } ,
} ,
{
name : "multi stage base" ,
dockerfile : "Dockerfile.reusebase" ,
buildahRegex : "[0-9a-z]+ /1" ,
fsSkip : [ ] string { "(dir):1:mtime" } ,
} ,
{
name : "directory" ,
contextDir : "dir" ,
fsSkip : [ ] string { "(dir):dir:mtime" , "(dir):test:mtime" } ,
} ,
{
name : "copy to dir" ,
contextDir : "copy" ,
fsSkip : [ ] string { "(dir):usr:(dir):bin:mtime" } ,
} ,
{
name : "copy dir" ,
contextDir : "copydir" ,
fsSkip : [ ] string { "(dir):dir" } ,
} ,
2020-08-16 17:37:22 +08:00
{
name : "copy from symlink source" ,
contextDir : "copysymlink" ,
2021-04-02 01:25:27 +08:00
} ,
{
name : "copy-symlink-2" ,
contextDir : "copysymlink" ,
dockerfile : "Dockerfile2" ,
2020-08-16 17:37:22 +08:00
} ,
2020-08-07 01:16:02 +08:00
{
name : "copy from subdir to new directory" ,
contextDir : "copydir" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY dir/file /subdir/" ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-08-07 01:16:02 +08:00
} ,
2020-06-19 06:03:43 +08:00
{
name : "copy to renamed file" ,
contextDir : "copyrename" ,
fsSkip : [ ] string { "(dir):usr:(dir):bin:mtime" } ,
} ,
2024-06-04 04:45:22 +08:00
{
name : "copy with --chown" ,
contextDir : "copychown" ,
fsSkip : [ ] string { "(dir):usr:(dir):bin:mtime" , "(dir):usr:(dir):local:(dir):bin:mtime" } ,
} ,
2021-03-23 00:00:02 +08:00
2020-06-19 06:03:43 +08:00
{
name : "directory with slash" ,
contextDir : "overlapdirwithslash" ,
} ,
{
name : "directory without slash" ,
contextDir : "overlapdirwithoutslash" ,
} ,
{
name : "environment" ,
dockerfile : "Dockerfile.env" ,
shouldFailAt : 12 ,
} ,
{
name : "edgecases" ,
dockerfile : "Dockerfile.edgecases" ,
fsSkip : [ ] string {
2021-08-27 23:10:17 +08:00
"(dir):test:mtime" , "(dir):test:(dir):copy:mtime" , "(dir):test2:mtime" , "(dir):test3:mtime" ,
"(dir):test3:(dir):copy:mtime" ,
"(dir):test3:(dir):test:mtime" , "(dir):tmp:mtime" , "(dir):tmp:(dir):passwd:mtime" ,
2020-06-19 06:03:43 +08:00
} ,
} ,
{
2024-06-11 03:18:01 +08:00
name : "exposed default" ,
dockerfile : "Dockerfile.exposedefault" ,
testUsingSetParent : true ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "add" ,
dockerfile : "Dockerfile.add" ,
fsSkip : [ ] string { "(dir):b:mtime" , "(dir):tmp:mtime" } ,
} ,
{
name : "run with JSON" ,
dockerfile : "Dockerfile.run.args" ,
buildahRegex : "(first|third|fifth|inner) (second|fourth|sixth|outer)" ,
dockerRegex : "Running in [0-9a-z]+.*?(first|third|fifth|inner) (second|fourth|sixth|outer)" ,
} ,
{
name : "wildcard" ,
contextDir : "wildcard" ,
fsSkip : [ ] string { "(dir):usr:mtime" , "(dir):usr:(dir):test:mtime" } ,
} ,
{
2024-06-11 03:46:58 +08:00
name : "volume" ,
contextDir : "volume" ,
fsSkip : [ ] string { "(dir):var:mtime" , "(dir):var:(dir):www:mtime" } ,
testUsingVolumes : true ,
2020-06-19 06:03:43 +08:00
} ,
{
2024-06-11 03:46:58 +08:00
name : "volumerun" ,
contextDir : "volumerun" ,
fsSkip : [ ] string { "(dir):var:mtime" , "(dir):var:(dir):www:mtime" } ,
testUsingVolumes : true ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "mount" ,
contextDir : "mount" ,
buildahRegex : "/tmp/test/file.*?regular file.*?/tmp/test/file2.*?regular file" ,
withoutDocker : true ,
2021-04-28 02:52:39 +08:00
transientMounts : [ ] string { "@@TEMPDIR@@:/tmp/test" + selinuxMountFlag ( ) } ,
2020-06-19 06:03:43 +08:00
} ,
{
2021-04-28 02:52:39 +08:00
name : "transient-mount" ,
contextDir : "transientmount" ,
2024-08-08 04:46:39 +08:00
buildahRegex : "file2.*?FROM mirror.gcr.io/busybox ENV name value" ,
2021-04-28 02:52:39 +08:00
withoutDocker : true ,
transientMounts : [ ] string {
"@@TEMPDIR@@:/mountdir" + selinuxMountFlag ( ) ,
"@@TEMPDIR@@/Dockerfile.env:/mountfile" + selinuxMountFlag ( ) ,
} ,
2020-06-19 06:03:43 +08:00
} ,
2024-06-04 02:49:53 +08:00
{
// from internal team chat
name : "ci-pipeline-modified" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox" ,
2024-06-04 02:49:53 +08:00
"WORKDIR /go/src/github.com/openshift/ocp-release-operator-sdk/" ,
"ENV GOPATH=/go" ,
"RUN env | grep -E -v '^(HOSTNAME|OLDPWD)=' | LANG=C sort | tee /env-contents.txt\n" ,
} , "\n" ) ,
fsSkip : [ ] string {
"(dir):go:mtime" ,
"(dir):go:(dir):src:mtime" ,
"(dir):go:(dir):src:(dir):github.com:mtime" ,
"(dir):go:(dir):src:(dir):github.com:(dir):openshift:mtime" ,
"(dir):go:(dir):src:(dir):github.com:(dir):openshift:(dir):ocp-release-operator-sdk:mtime" ,
"(dir):env-contents.txt:mtime" ,
} ,
} ,
2020-06-19 06:03:43 +08:00
2020-09-30 03:01:27 +08:00
{
2021-02-05 06:27:52 +08:00
name : "add-permissions" ,
withoutDocker : true ,
2020-09-30 03:01:27 +08:00
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
2022-01-18 10:37:08 +08:00
"# Does ADD preserve permissions differently for archives and files?" ,
2020-09-30 03:01:27 +08:00
"ADD archive.tar subdir1/" ,
"ADD archive/ subdir2/" ,
} , "\n" ) ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-09-30 03:01:27 +08:00
content := [ ] byte ( "test content" )
2024-08-16 00:50:07 +08:00
if err := os . Mkdir ( filepath . Join ( contextDir , "archive" ) , 0 o755 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating subdirectory of temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename := filepath . Join ( contextDir , "archive" , "should-be-owned-by-root" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , content , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating file owned by root in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chown ( filename , 0 , 0 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting ownership on file owned by root in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on file owned by root file in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename = filepath . Join ( contextDir , "archive" , "should-be-owned-by-99" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , content , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chown ( filename , 99 , 99 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting ownership on file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename = filepath . Join ( contextDir , "archive.tar" )
2024-08-16 00:50:07 +08:00
f , err := os . OpenFile ( filename , os . O_CREATE | os . O_WRONLY , 0 o644 )
2020-09-30 03:01:27 +08:00
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating archive file: %w" , err )
2020-09-30 03:01:27 +08:00
}
defer f . Close ( )
tw := tar . NewWriter ( f )
defer tw . Close ( )
err = tw . WriteHeader ( & tar . Header {
Name : "archive/should-be-owned-by-root" ,
Typeflag : tar . TypeReg ,
Size : int64 ( len ( content ) ) ,
ModTime : testDate ,
2024-08-16 00:50:07 +08:00
Mode : 0 o640 ,
2020-09-30 03:01:27 +08:00
Uid : 0 ,
Gid : 0 ,
} )
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing archive file header: %w" , err )
2020-09-30 03:01:27 +08:00
}
n , err := tw . Write ( content )
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing archive file contents: %w" , err )
2020-09-30 03:01:27 +08:00
}
if n != len ( content ) {
2022-07-06 17:14:06 +08:00
return errors . New ( "short write writing archive file contents" )
2020-09-30 03:01:27 +08:00
}
err = tw . WriteHeader ( & tar . Header {
Name : "archive/should-be-owned-by-99" ,
Typeflag : tar . TypeReg ,
Size : int64 ( len ( content ) ) ,
ModTime : testDate ,
2024-08-16 00:50:07 +08:00
Mode : 0 o640 ,
2020-09-30 03:01:27 +08:00
Uid : 99 ,
Gid : 99 ,
} )
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing archive file header: %w" , err )
2020-09-30 03:01:27 +08:00
}
n , err = tw . Write ( content )
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing archive file contents: %w" , err )
2020-09-30 03:01:27 +08:00
}
if n != len ( content ) {
2022-07-06 17:14:06 +08:00
return errors . New ( "short write writing archive file contents" )
2020-09-30 03:01:27 +08:00
}
return nil
} ,
fsSkip : [ ] string { "(dir):subdir1:mtime" , "(dir):subdir2:mtime" } ,
} ,
{
name : "copy-permissions" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
2022-01-18 10:37:08 +08:00
"# Does COPY --chown change permissions on already-present directories?" ,
2020-09-30 03:01:27 +08:00
"COPY subdir/ subdir/" ,
"COPY --chown=99:99 subdir/ subdir/" ,
} , "\n" ) ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-09-30 03:01:27 +08:00
content := [ ] byte ( "test content" )
2024-08-16 00:50:07 +08:00
if err := os . Mkdir ( filepath . Join ( contextDir , "subdir" ) , 0 o755 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating subdirectory of temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename := filepath . Join ( contextDir , "subdir" , "would-be-owned-by-root" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , content , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating file owned by root in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chown ( filename , 0 , 0 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting ownership on file owned by root in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on file owned by root file in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename = filepath . Join ( contextDir , "subdir" , "would-be-owned-by-99" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , content , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chown ( filename , 99 , 99 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting ownership on file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-09-30 03:01:27 +08:00
} ,
{
name : "copy-permissions-implicit" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
2022-01-18 10:37:08 +08:00
"# Does COPY --chown change permissions on already-present directories?" ,
2020-09-30 03:01:27 +08:00
"COPY --chown=99:99 subdir/ subdir/" ,
"COPY subdir/ subdir/" ,
} , "\n" ) ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-09-30 03:01:27 +08:00
content := [ ] byte ( "test content" )
2024-08-16 00:50:07 +08:00
if err := os . Mkdir ( filepath . Join ( contextDir , "subdir" ) , 0 o755 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating subdirectory of temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename := filepath . Join ( contextDir , "subdir" , "would-be-owned-by-root" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , content , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating file owned by root in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chown ( filename , 0 , 0 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting ownership on file owned by root in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on file owned by root file in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
filename = filepath . Join ( contextDir , "subdir" , "would-be-owned-by-99" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , content , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chown ( filename , 99 , 99 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting ownership on file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on file owned by 99 in temporary context directory: %w" , err )
2020-09-30 03:01:27 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-09-30 03:01:27 +08:00
} ,
2020-06-19 06:03:43 +08:00
{
// the digest just ensures that we can handle a digest
// reference to a manifest list; the digest of any manifest
// list in the image repository would do
name : "stage-container-as-source-plus-hardlinks" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox@sha256:9ae97d36d26566ff84e8893c64a6dc4fe8ca6d1144bf5b87b2b85a32def253c7 AS build" ,
2020-06-19 06:03:43 +08:00
"RUN mkdir -p /target/subdir" ,
2021-08-27 23:10:17 +08:00
"RUN cp -p /etc/passwd /target/" ,
2020-06-19 06:03:43 +08:00
"RUN ln /target/passwd /target/subdir/passwd" ,
"RUN ln /target/subdir/passwd /target/subdir/passwd2" ,
"FROM scratch" ,
"COPY --from=build /target/subdir /subdir" ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerfile-in-subdirectory" ,
dockerfile : "subdir/Dockerfile" ,
contextDir : "subdir" ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "setuid-file-in-context" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
fmt . Sprintf ( "# Does the setuid file (0%o) in the context dir end up setuid in the image?" , syscall . S_ISUID ) ,
"COPY . subdir1" ,
"ADD . subdir2" ,
} , "\n" ) ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
filename := filepath . Join ( contextDir , "should-be-setuid-file" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , [ ] byte ( "test content" ) , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating setuid test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
2024-08-16 00:50:07 +08:00
if err = syscall . Chmod ( filename , syscall . S_ISUID | 0 o755 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting setuid bit on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on setuid test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
2020-07-29 04:36:39 +08:00
filename = filepath . Join ( contextDir , "should-be-setgid-file" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , [ ] byte ( "test content" ) , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating setgid test file in temporary context directory: %w" , err )
2020-07-29 04:36:39 +08:00
}
2024-08-16 00:50:07 +08:00
if err = syscall . Chmod ( filename , syscall . S_ISGID | 0 o755 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting setgid bit on test file in temporary context directory: %w" , err )
2020-07-29 04:36:39 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on setgid test file in temporary context directory: %w" , err )
2020-07-29 04:36:39 +08:00
}
filename = filepath . Join ( contextDir , "should-be-sticky-file" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , [ ] byte ( "test content" ) , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating sticky test file in temporary context directory: %w" , err )
2020-07-29 04:36:39 +08:00
}
2024-08-16 00:50:07 +08:00
if err = syscall . Chmod ( filename , syscall . S_ISVTX | 0 o755 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting permissions on sticky test file in temporary context directory: %w" , err )
2020-07-29 04:36:39 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on sticky test file in temporary context directory: %w" , err )
2020-07-29 04:36:39 +08:00
}
filename = filepath . Join ( contextDir , "should-not-be-setuid-setgid-sticky-file" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , [ ] byte ( "test content" ) , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating non-suid non-sgid non-sticky test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
2024-08-16 00:50:07 +08:00
if err = syscall . Chmod ( filename , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting permissions on plain test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on plain test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir1:mtime" , "(dir):subdir2:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "xattrs-file-in-context" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"# Do the xattrs on a file in the context dir end up in the image?" ,
"COPY . subdir1" ,
"ADD . subdir2" ,
} , "\n" ) ,
tweakContextDir : func ( t * testing . T , contextDir , storageDriver , storageRoot string ) ( err error ) {
if ! * contextCanDoXattrs {
t . Skipf ( "test context directory %q doesn't support xattrs" , contextDir )
}
if ! * storageCanDoXattrs {
t . Skipf ( "test storage driver %q and directory %q don't support xattrs together" , storageDriver , storageRoot )
}
filename := filepath . Join ( contextDir , "xattrs-file" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , [ ] byte ( "test content" ) , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating test file with xattrs in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = copier . Lsetxattrs ( filename , map [ string ] string { "user.a" : "test" } ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting xattrs on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
2024-08-16 00:50:07 +08:00
if err = syscall . Chmod ( filename , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting permissions on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir1:mtime" , "(dir):subdir2:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
2025-02-14 17:43:22 +08:00
{
name : "from-scratch-simple-file" , // Fix On ARM64: fields missing from buildah version: variant
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY file-a.txt /" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
compatScratchConfig : types . OptionalBoolTrue ,
} ,
2020-06-19 06:03:43 +08:00
{
name : "setuid-file-in-archive" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
2020-07-29 04:36:39 +08:00
fmt . Sprintf ( "# Do the setuid/setgid/sticky files in this archive end up setuid(0%o)/setgid(0%o)/sticky(0%o)?" , syscall . S_ISUID , syscall . S_ISGID , syscall . S_ISVTX ) ,
2020-06-19 06:03:43 +08:00
"ADD archive.tar ." ,
} , "\n" ) ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
filename := filepath . Join ( contextDir , "archive.tar" )
2024-08-16 00:50:07 +08:00
f , err := os . OpenFile ( filename , os . O_CREATE | os . O_WRONLY , 0 o644 )
2020-06-19 06:03:43 +08:00
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating new archive file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
defer f . Close ( )
tw := tar . NewWriter ( f )
defer tw . Close ( )
hdr := tar . Header {
Name : "setuid-file" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeReg ,
Size : 8 ,
2024-08-16 00:50:07 +08:00
Mode : cISUID | 0 o755 ,
2020-07-29 04:36:39 +08:00
ModTime : testDate ,
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive header: %w" , err )
2020-07-29 04:36:39 +08:00
}
if _ , err = io . Copy ( tw , bytes . NewReader ( [ ] byte ( "whatever" ) ) ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive content: %w" , err )
2020-07-29 04:36:39 +08:00
}
hdr = tar . Header {
Name : "setgid-file" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeReg ,
Size : 8 ,
2024-08-16 00:50:07 +08:00
Mode : cISGID | 0 o755 ,
2020-07-29 04:36:39 +08:00
ModTime : testDate ,
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive header: %w" , err )
2020-07-29 04:36:39 +08:00
}
if _ , err = io . Copy ( tw , bytes . NewReader ( [ ] byte ( "whatever" ) ) ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive content: %w" , err )
2020-07-29 04:36:39 +08:00
}
hdr = tar . Header {
Name : "sticky-file" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeReg ,
Size : 8 ,
2024-08-16 00:50:07 +08:00
Mode : cISVTX | 0 o755 ,
2020-06-19 06:03:43 +08:00
ModTime : testDate ,
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive header: %w" , err )
2020-06-19 06:03:43 +08:00
}
if _ , err = io . Copy ( tw , bytes . NewReader ( [ ] byte ( "whatever" ) ) ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive content: %w" , err )
2020-06-19 06:03:43 +08:00
}
2022-11-10 04:18:02 +08:00
hdr = tar . Header {
Name : "setuid-dir" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeDir ,
Size : 0 ,
2024-08-16 00:50:07 +08:00
Mode : cISUID | 0 o755 ,
2022-11-10 04:18:02 +08:00
ModTime : testDate ,
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
return fmt . Errorf ( "error writing tar archive header: %w" , err )
}
hdr = tar . Header {
Name : "setgid-dir" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeDir ,
Size : 0 ,
2024-08-16 00:50:07 +08:00
Mode : cISGID | 0 o755 ,
2022-11-10 04:18:02 +08:00
ModTime : testDate ,
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
return fmt . Errorf ( "error writing tar archive header: %w" , err )
}
hdr = tar . Header {
Name : "sticky-dir" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeDir ,
Size : 0 ,
2024-08-16 00:50:07 +08:00
Mode : cISVTX | 0 o755 ,
2022-11-10 04:18:02 +08:00
ModTime : testDate ,
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
return fmt . Errorf ( "error writing tar archive header: %w" , err )
}
2020-06-19 06:03:43 +08:00
return nil
} ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "xattrs-file-in-archive" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"# Do the xattrs on a file in an archive end up in the image?" ,
"ADD archive.tar ." ,
} , "\n" ) ,
tweakContextDir : func ( t * testing . T , contextDir , storageDriver , storageRoot string ) ( err error ) {
if ! * contextCanDoXattrs {
t . Skipf ( "test context directory %q doesn't support xattrs" , contextDir )
}
if ! * storageCanDoXattrs {
t . Skipf ( "test storage driver %q and directory %q don't support xattrs together" , storageDriver , storageRoot )
}
filename := filepath . Join ( contextDir , "archive.tar" )
2024-08-16 00:50:07 +08:00
f , err := os . OpenFile ( filename , os . O_CREATE | os . O_WRONLY , 0 o644 )
2020-06-19 06:03:43 +08:00
if err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating new archive file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
defer f . Close ( )
tw := tar . NewWriter ( f )
defer tw . Close ( )
hdr := tar . Header {
Name : "xattr-file" ,
Uid : 0 ,
Gid : 0 ,
Typeflag : tar . TypeReg ,
Size : 8 ,
2024-08-16 00:50:07 +08:00
Mode : 0 o640 ,
2020-06-19 06:03:43 +08:00
ModTime : testDate ,
2024-11-20 23:43:00 +08:00
PAXRecords : map [ string ] string {
xattrPAXRecordNamespace + "user.a" : "test" ,
} ,
2020-06-19 06:03:43 +08:00
}
if err = tw . WriteHeader ( & hdr ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive header: %w" , err )
2020-06-19 06:03:43 +08:00
}
if _ , err = io . Copy ( tw , bytes . NewReader ( [ ] byte ( "whatever" ) ) ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing tar archive content: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir1:mtime" , "(dir):subdir2:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
// docker build apparently stopped preserving this bit somewhere between 18.09.7 and 19.03,
// possibly around https://github.com/moby/moby/pull/38599
name : "setuid-file-in-other-stage" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox" ,
2020-06-19 06:03:43 +08:00
"RUN mkdir /a && echo whatever > /a/setuid && chmod u+xs /a/setuid && touch -t @1485449953 /a/setuid" ,
"RUN mkdir /b && echo whatever > /b/setgid && chmod g+xs /b/setgid && touch -t @1485449953 /b/setgid" ,
2020-07-29 04:36:39 +08:00
"RUN mkdir /c && echo whatever > /c/sticky && chmod o+x /c/sticky && chmod +t /c/sticky && touch -t @1485449953 /c/sticky" ,
2020-06-19 06:03:43 +08:00
"FROM scratch" ,
2020-07-29 04:36:39 +08:00
fmt . Sprintf ( "# Does this setuid/setgid/sticky file copied from another stage end up setuid/setgid/sticky (0%o/0%o/0%o)?" , syscall . S_ISUID , syscall . S_ISGID , syscall . S_ISVTX ) ,
2020-06-19 06:03:43 +08:00
"COPY --from=0 /a/setuid /b/setgid /c/sticky /" ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "xattrs-file-in-other-stage" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . ." ,
"FROM scratch" ,
"# Do the xattrs on a file in another stage end up in the image?" ,
"COPY --from=0 /xattrs-file /" ,
} , "\n" ) ,
tweakContextDir : func ( t * testing . T , contextDir , storageDriver , storageRoot string ) ( err error ) {
if ! * contextCanDoXattrs {
t . Skipf ( "test context directory %q doesn't support xattrs" , contextDir )
}
if ! * storageCanDoXattrs {
t . Skipf ( "test storage driver %q and directory %q don't support xattrs together" , storageDriver , storageRoot )
}
filename := filepath . Join ( contextDir , "xattrs-file" )
2024-08-16 00:50:07 +08:00
if err = os . WriteFile ( filename , [ ] byte ( "test content" ) , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "creating test file with xattrs in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = copier . Lsetxattrs ( filename , map [ string ] string { "user.a" : "test" } ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting xattrs on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
2024-08-16 00:50:07 +08:00
if err = syscall . Chmod ( filename , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting permissions on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filename , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on test file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "copy-multiple-some-missing" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY file-a.txt subdir-a file-z.txt subdir-z subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
shouldFailAt : 2 ,
} ,
2021-04-19 18:22:41 +08:00
{
name : "copy-multiple-missing-file-with-glob" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY file-z.txt subdir-* subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
shouldFailAt : 2 ,
} ,
{
name : "copy-multiple-missing-file-with-nomatch-on-glob" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY missing* subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
shouldFailAt : 2 ,
} ,
{
name : "copy-multiple-some-missing-glob" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY file-a.txt subdir-* file-?.txt missing* subdir/" ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2021-04-19 18:22:41 +08:00
} ,
2020-06-19 06:03:43 +08:00
{
name : "file-in-workdir-in-other-stage" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch AS base" ,
"COPY . /subdir/" ,
"WORKDIR /subdir" ,
"FROM base" ,
"COPY --from=base . ." , // --from=otherstage ignores that stage's WORKDIR
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2021-08-27 23:10:17 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" , "(dir):subdir:(dir):subdir:mtime" } ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "copy-integration1" ,
contextDir : "dockerignore/integration1" ,
shouldFailAt : 3 ,
2021-07-01 21:56:17 +08:00
failureRegex : "(no such file or directory)|(file not found)|(file does not exist)" ,
2020-06-19 06:03:43 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "copy-integration2" ,
contextDir : "dockerignore/integration2" ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "copy-integration3" ,
contextDir : "dockerignore/integration3" ,
shouldFailAt : 4 ,
2021-07-01 21:56:17 +08:00
failureRegex : "(no such file or directory)|(file not found)|(file does not exist)" ,
2020-06-19 06:03:43 +08:00
} ,
2021-04-01 21:59:33 +08:00
{
name : "copy-empty-1" ,
contextDir : "copyempty" ,
dockerfile : "Dockerfile" ,
fsSkip : [ ] string { "(dir):usr:(dir):local:mtime" , "(dir):usr:(dir):local:(dir):tmp:mtime" } ,
} ,
{
name : "copy-empty-2" ,
contextDir : "copyempty" ,
dockerfile : "Dockerfile2" ,
fsSkip : [ ] string { "(dir):usr:(dir):local:mtime" , "(dir):usr:(dir):local:(dir):tmp:mtime" } ,
} ,
2021-04-02 05:26:29 +08:00
{
name : "copy-absolute-directory-1" ,
contextDir : "copyblahblub" ,
dockerfile : "Dockerfile" ,
fsSkip : [ ] string { "(dir):var:mtime" } ,
} ,
{
name : "copy-absolute-directory-2" ,
contextDir : "copyblahblub" ,
dockerfile : "Dockerfile2" ,
fsSkip : [ ] string { "(dir):var:mtime" } ,
} ,
{
name : "copy-absolute-directory-3" ,
contextDir : "copyblahblub" ,
dockerfile : "Dockerfile3" ,
fsSkip : [ ] string { "(dir):var:mtime" } ,
} ,
2020-06-19 06:03:43 +08:00
{
name : "multi-stage-through-base" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/alpine AS base" ,
2021-08-27 23:10:17 +08:00
"RUN touch -t @1485449953 /1" ,
2020-06-19 06:03:43 +08:00
"ENV LOCAL=/1" ,
"RUN find $LOCAL" ,
"FROM base" ,
"RUN find $LOCAL" ,
} , "\n" ) ,
fsSkip : [ ] string { "(dir):root:mtime" , "(dir):1:mtime" } ,
} ,
{
name : "multi-stage-derived" , // from #2415
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox as layer" ,
2020-06-19 06:03:43 +08:00
"RUN touch /root/layer" ,
"FROM layer as derived" ,
2021-08-27 23:10:17 +08:00
"RUN touch -t @1485449953 /root/derived ; rm /root/layer" ,
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox AS output" ,
2020-06-19 06:03:43 +08:00
"COPY --from=layer /root /root" ,
} , "\n" ) ,
fsSkip : [ ] string { "(dir):root:mtime" , "(dir):root:(dir):layer:mtime" } ,
} ,
{
2021-02-05 06:27:52 +08:00
name : "dockerignore-minimal-test" , // from #2237
contextDir : "dockerignore/minimal_test" ,
withoutDocker : true ,
fsSkip : [ ] string { "(dir):tmp:mtime" , "(dir):tmp:(dir):stuff:mtime" } ,
2020-06-19 06:03:43 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-is-even-there" ,
contextDir : "dockerignore/empty" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-irrelevant" ,
contextDir : "dockerignore/empty" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/*-a" , "!**/*-c" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o600 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exceptions-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/*-a" , "!**/*-c" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o644 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-extensions-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/*-a" , "!**/*-c" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o600 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-includes-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "!**/*-c" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
2024-09-12 21:08:12 +08:00
{
name : "add--exclude-includes-star" ,
dockerfileContents : strings . Join ( [ ] string {
"# syntax=docker/dockerfile:1.9-labs" ,
"FROM scratch" ,
"ADD --exclude=**/*-c ./ subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolFalse ,
dockerUseBuildKit : true ,
} ,
{
name : "add--exclude-includes-slash" ,
dockerfileContents : strings . Join ( [ ] string {
"# syntax=docker/dockerfile:1.9-labs" ,
"FROM scratch" ,
"ADD --exclude=*.txt / subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolFalse ,
dockerUseBuildKit : true ,
} ,
{
name : "add--exclude-includes-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"# syntax=docker/dockerfile:1.9-labs" ,
"FROM scratch" ,
"ADD --exclude=*-c . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolFalse ,
dockerUseBuildKit : true ,
} ,
{
name : "copy--exclude-includes-subdir-slash" ,
dockerfileContents : strings . Join ( [ ] string {
"# syntax=docker/dockerfile:1.9-labs" ,
"FROM scratch" ,
"COPY --exclude=**/*-c / subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolFalse ,
dockerUseBuildKit : true ,
} ,
{
name : "copy--exclude-includes-dot-slash" ,
dockerfileContents : strings . Join ( [ ] string {
"# syntax=docker/dockerfile:1.9-labs" ,
"FROM scratch" ,
"COPY --exclude='!**/*-c' ./ subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolFalse ,
dockerUseBuildKit : true ,
} ,
{
name : "copy--exclude-includes-slash" ,
dockerfileContents : strings . Join ( [ ] string {
"# syntax=docker/dockerfile:1.9-labs" ,
"FROM scratch" ,
"COPY --exclude='!**/*-c' . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolFalse ,
dockerUseBuildKit : true ,
} ,
2020-06-19 06:03:43 +08:00
{
name : "dockerignore-includes-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "!**/*-c\n" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o100 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-plain-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "subdir-c" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o200 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-plain-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "subdir-c" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o400 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-plain-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-c" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o200 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-plain-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-c" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o400 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-wildcard-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "subdir-*" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o000 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-wildcard-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "subdir-*" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o660 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-deep-wildcard-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-*" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o000 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-deep-wildcard-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-*" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o660 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-deep-subdir-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-f" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o666 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-deep-subdir-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-f" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o640 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-not-so-deep-subdir-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-b" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o705 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-not-so-deep-subdir-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( "**/subdir-b" )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o750 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-kind-of-deep-subdir-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/subdir-e" , "!**/subdir-f" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o750 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-kind-of-deep-subdir-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/subdir-e" , "!**/subdir-f" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o750 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-deep-subdir-dot" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY . subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/subdir-f" , "!**/subdir-g" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o750 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "dockerignore-exclude-deep-subdir-star" ,
dockerfileContents : strings . Join ( [ ] string {
"FROM scratch" ,
"COPY * subdir/" ,
} , "\n" ) ,
contextDir : "dockerignore/populated" ,
2024-08-07 03:07:02 +08:00
tweakContextDir : func ( _ * testing . T , contextDir , _ , _ string ) ( err error ) {
2020-06-19 06:03:43 +08:00
dockerignore := [ ] byte ( strings . Join ( [ ] string { "**/subdir-f" , "!**/subdir-g" } , "\n" ) )
2024-08-16 00:50:07 +08:00
if err := os . WriteFile ( filepath . Join ( contextDir , ".dockerignore" ) , dockerignore , 0 o750 ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "writing .dockerignore file: %w" , err )
2020-06-19 06:03:43 +08:00
}
if err = os . Chtimes ( filepath . Join ( contextDir , ".dockerignore" ) , testDate , testDate ) ; err != nil {
2022-09-18 18:36:08 +08:00
return fmt . Errorf ( "setting date on .dockerignore file in temporary context directory: %w" , err )
2020-06-19 06:03:43 +08:00
}
return nil
} ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "(dir):subdir:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-whitespace" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name value ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-simple" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name=value ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-unquoted-list" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name=value name2=value2 ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-dquoted-list" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name="value value1" ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-escaped-value" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name=value\ value2 ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-squote-in-dquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name="value'quote space'value2" ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-dquote-in-squote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name='value"double quote"value2' ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-escaped-list" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name=value\ value2 name2=value2\ value3 ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-eddquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name="a\"b" ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-invalid-ssquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name='a\'b' ` ,
} , "\n" ) ,
shouldFailAt : 3 ,
} ,
{
name : "env-esdquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name="a\'b" ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-essquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name='a\'b'' ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-edsquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name='a\"b' ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-empty-squote-in-empty-dquote" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` ENV name="''" ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
{
name : "env-multiline" ,
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
` FROM scratch ` ,
` COPY script . ` ,
` # don't put anything after the next line - it must be the last line of the ` ,
` # Dockerfile and it must end with \ ` ,
` ENV name=value \ ` ,
` name1=value1 \ ` ,
` name2="value2a \ ` ,
` value2b" \ ` ,
` name3="value3a\n\"value3b\"" \ ` ,
` name4="value4a\\nvalue4b" \ ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
compatScratchConfig : types . OptionalBoolTrue ,
2020-06-19 06:03:43 +08:00
} ,
2020-08-21 23:42:49 +08:00
{
name : "copy-from-owner" , // from issue #2518
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
` FROM mirror.gcr.io/alpine ` ,
2021-08-27 23:10:17 +08:00
` RUN set -ex; touch -t @1485449953 /test; chown 65:65 /test ` ,
2020-08-21 23:42:49 +08:00
` FROM scratch ` ,
` USER 66:66 ` ,
` COPY --from=0 /test /test ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "test:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-08-21 23:42:49 +08:00
} ,
{
name : "copy-from-owner-with-chown" , // issue #2518, but with chown to override
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
` FROM mirror.gcr.io/alpine ` ,
2021-08-27 23:10:17 +08:00
` RUN set -ex; touch -t @1485449953 /test; chown 65:65 /test ` ,
2020-08-21 23:42:49 +08:00
` FROM scratch ` ,
` USER 66:66 ` ,
` COPY --from=0 --chown=1:1 /test /test ` ,
} , "\n" ) ,
2024-08-16 01:41:05 +08:00
fsSkip : [ ] string { "test:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-08-21 23:42:49 +08:00
} ,
{
name : "copy-for-user" , // flip side of issue #2518
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
` FROM mirror.gcr.io/alpine ` ,
2020-08-21 23:42:49 +08:00
` USER 66:66 ` ,
` COPY /script /script ` ,
} , "\n" ) ,
} ,
{
name : "copy-for-user-with-chown" , // flip side of issue #2518, but with chown to override
contextDir : "copy" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
` FROM mirror.gcr.io/alpine ` ,
2020-08-21 23:42:49 +08:00
` USER 66:66 ` ,
` COPY --chown=1:1 /script /script ` ,
} , "\n" ) ,
} ,
2020-10-02 01:39:33 +08:00
2021-02-17 02:40:23 +08:00
{
2024-08-16 01:41:05 +08:00
name : "add-parent-symlink" ,
contextDir : "add/parent-symlink" ,
fsSkip : [ ] string { "(dir):testsubdir:mtime" , "(dir):testsubdir:(dir):etc:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2021-02-17 02:40:23 +08:00
} ,
{
name : "add-parent-dangling" ,
contextDir : "add/parent-dangling" ,
2021-08-27 23:10:17 +08:00
fsSkip : [ ] string { "(dir):symlink:mtime" , "(dir):symlink-target:mtime" , "(dir):symlink-target:(dir):subdirectory:mtime" } ,
2021-02-17 02:40:23 +08:00
} ,
{
name : "add-parent-clean" ,
contextDir : "add/parent-clean" ,
fsSkip : [ ] string { "(dir):symlink:mtime" , "(dir):symlink-target:mtime" , "(dir):symlink-target:(dir):subdirectory:mtime" } ,
} ,
2020-10-02 01:39:33 +08:00
{
2024-08-16 01:41:05 +08:00
name : "add-archive-1" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.1" ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "add-archive-2" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.2" ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "add-archive-3" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.3" ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "add-archive-4" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.4" ,
fsSkip : [ ] string { "(dir):sub:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "add-archive-5" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.5" ,
fsSkip : [ ] string { "(dir):sub:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "add-archive-6" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.6" ,
fsSkip : [ ] string { "(dir):sub:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "add-archive-7" ,
contextDir : "add/archive" ,
dockerfile : "Dockerfile.7" ,
fsSkip : [ ] string { "(dir):sub:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-02 01:39:33 +08:00
} ,
2020-10-06 05:43:49 +08:00
2020-12-12 02:22:39 +08:00
{
name : "add-dir-not-dir" ,
contextDir : "add/dir-not-dir" ,
} ,
{
name : "add-not-dir-dir" ,
contextDir : "add/not-dir-dir" ,
} ,
{
name : "add-populated-dir-not-dir" ,
contextDir : "add/populated-dir-not-dir" ,
} ,
2020-10-06 05:43:49 +08:00
{
name : "dockerignore-allowlist-subdir-nofile-dir" ,
contextDir : "dockerignore/allowlist/subdir-nofile" ,
shouldFailAt : 2 ,
2021-07-01 21:56:17 +08:00
failureRegex : "(no such file or directory)|(file not found)|(file does not exist)" ,
2020-10-06 05:43:49 +08:00
} ,
{
name : "dockerignore-allowlist-subdir-nofile-file" ,
contextDir : "dockerignore/allowlist/subdir-nofile" ,
shouldFailAt : 2 ,
2021-07-01 21:56:17 +08:00
failureRegex : "(no such file or directory)|(file not found)|(file does not exist)" ,
2020-10-06 05:43:49 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-allowlist-subdir-file-dir" ,
contextDir : "dockerignore/allowlist/subdir-file" ,
fsSkip : [ ] string { "(dir):f1:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-06 05:43:49 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-allowlist-subdir-file-file" ,
contextDir : "dockerignore/allowlist/subdir-file" ,
fsSkip : [ ] string { "(dir):f1:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-06 05:43:49 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-allowlist-nothing-dot" ,
contextDir : "dockerignore/allowlist/nothing-dot" ,
fsSkip : [ ] string { "file:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-06 05:43:49 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-allowlist-nothing-slash" ,
contextDir : "dockerignore/allowlist/nothing-slash" ,
fsSkip : [ ] string { "file:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-06 05:43:49 +08:00
} ,
{
// the directories are excluded, so entries for them don't get
// included in the build context archive, so they only get
// created implicitly when extracted, so there's no point in us
// trying to preserve any of that, either
2021-02-05 06:27:52 +08:00
name : "dockerignore-allowlist-subsubdir-file" ,
contextDir : "dockerignore/allowlist/subsubdir-file" ,
withoutDocker : true ,
fsSkip : [ ] string { "(dir):folder:mtime" , "(dir):folder:(dir):subfolder:mtime" , "file:mtime" } ,
2020-10-06 05:43:49 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-allowlist-subsubdir-nofile" ,
contextDir : "dockerignore/allowlist/subsubdir-nofile" ,
fsSkip : [ ] string { "file:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-06 05:43:49 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "dockerignore-allowlist-subsubdir-nosubdir" ,
contextDir : "dockerignore/allowlist/subsubdir-nosubdir" ,
fsSkip : [ ] string { "file:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2020-10-06 05:43:49 +08:00
} ,
{
2021-02-05 06:27:52 +08:00
name : "dockerignore-allowlist-alternating" ,
contextDir : "dockerignore/allowlist/alternating" ,
withoutDocker : true ,
2020-10-06 05:43:49 +08:00
fsSkip : [ ] string {
"(dir):subdir1:mtime" ,
"(dir):subdir1:(dir):subdir2:(dir):subdir3:mtime" ,
"(dir):subdir1:(dir):subdir2:(dir):subdir3:(dir):subdir4:(dir):subdir5:mtime" ,
"(dir):subdir2:(dir):subdir3:mtime" ,
"(dir):subdir2:(dir):subdir3:(dir):subdir4:(dir):subdir5:mtime" ,
"(dir):subdir3:mtime" ,
"(dir):subdir3:(dir):subdir4:(dir):subdir5:mtime" ,
"(dir):subdir4:(dir):subdir5:mtime" ,
"(dir):subdir5:mtime" ,
} ,
} ,
{
name : "dockerignore-allowlist-alternating-nothing" ,
contextDir : "dockerignore/allowlist/alternating-nothing" ,
shouldFailAt : 7 ,
2021-07-01 21:56:17 +08:00
failureRegex : "(no such file or directory)|(file not found)|(file does not exist)" ,
2020-10-06 05:43:49 +08:00
} ,
{
name : "dockerignore-allowlist-alternating-other" ,
contextDir : "dockerignore/allowlist/alternating-other" ,
shouldFailAt : 7 ,
2021-07-01 21:56:17 +08:00
failureRegex : "(no such file or directory)|(file not found)|(file does not exist)" ,
2020-10-06 05:43:49 +08:00
} ,
2020-10-23 23:51:55 +08:00
{
2021-02-05 06:27:52 +08:00
name : "tar-g" ,
contextDir : "tar-g" ,
withoutDocker : true ,
fsSkip : [ ] string { "(dir):tmp:mtime" } ,
2020-10-23 23:51:55 +08:00
} ,
2021-08-24 22:41:16 +08:00
{
name : "dockerignore-exceptions-skip" ,
contextDir : "dockerignore/exceptions-skip" ,
fsSkip : [ ] string { "(dir):volume:mtime" } ,
} ,
2021-09-17 00:57:03 +08:00
{
name : "dockerignore-exceptions-weirdness-1" ,
contextDir : "dockerignore/exceptions-weirdness-1" ,
fsSkip : [ ] string { "(dir):newdir:mtime" , "(dir):newdir:(dir):subdir:mtime" } ,
} ,
{
name : "dockerignore-exceptions-weirdness-2" ,
contextDir : "dockerignore/exceptions-weirdness-2" ,
fsSkip : [ ] string { "(dir):newdir:mtime" , "(dir):newdir:(dir):subdir:mtime" } ,
} ,
2021-09-22 03:06:58 +08:00
2023-03-16 04:50:51 +08:00
{
2025-02-14 17:52:10 +08:00
name : "multistage-builtin-args" , // By default, BUILDVARIANT/TARGETVARIANT should be empty.
2023-03-16 04:50:51 +08:00
dockerfile : "Dockerfile.margs" ,
dockerUseBuildKit : true ,
} ,
2021-11-30 23:10:42 +08:00
2023-11-16 16:36:08 +08:00
{
name : "heredoc-copy" ,
dockerfile : "Dockerfile.heredoc_copy" ,
dockerUseBuildKit : true ,
contextDir : "heredoc" ,
2024-08-16 00:50:07 +08:00
fsSkip : [ ] string {
"(dir):test:mtime" ,
2023-11-16 16:36:08 +08:00
"(dir):test2:mtime" ,
"(dir):test:(dir):humans.txt:mtime" ,
"(dir):test:(dir):robots.txt:mtime" ,
"(dir):test2:(dir):humans.txt:mtime" ,
"(dir):test2:(dir):robots.txt:mtime" ,
"(dir):test2:(dir):image_file:mtime" ,
2024-08-16 00:50:07 +08:00
"(dir):etc:(dir):hostname" , /* buildkit does not contains /etc/hostname like buildah */
} ,
2023-11-16 16:36:08 +08:00
} ,
2021-11-30 23:10:42 +08:00
{
2024-08-16 01:41:05 +08:00
name : "replace-symlink-with-directory" ,
contextDir : "replace/symlink-with-directory" ,
compatScratchConfig : types . OptionalBoolTrue ,
2021-11-30 23:10:42 +08:00
} ,
2022-02-05 04:41:07 +08:00
{
2024-08-16 01:41:05 +08:00
name : "replace-directory-with-symlink" ,
contextDir : "replace/symlink-with-directory" ,
dockerfile : "Dockerfile.2" ,
compatScratchConfig : types . OptionalBoolTrue ,
2022-02-05 04:41:07 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "replace-symlink-with-directory-subdir" ,
contextDir : "replace/symlink-with-directory" ,
dockerfile : "Dockerfile.3" ,
fsSkip : [ ] string { "(dir):tree:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2022-02-05 04:41:07 +08:00
} ,
{
2024-08-16 01:41:05 +08:00
name : "replace-directory-with-symlink-subdir" ,
contextDir : "replace/symlink-with-directory" ,
dockerfile : "Dockerfile.4" ,
fsSkip : [ ] string { "(dir):tree:mtime" } ,
compatScratchConfig : types . OptionalBoolTrue ,
2022-02-05 04:41:07 +08:00
} ,
2022-03-23 18:37:57 +08:00
2023-03-16 04:50:51 +08:00
{
name : "workdir-owner" , // from issue #3620
dockerfileContents : strings . Join ( [ ] string {
2023-11-16 00:01:29 +08:00
` # syntax=docker/dockerfile:1.4 ` ,
2024-08-08 04:46:39 +08:00
` FROM mirror.gcr.io/alpine ` ,
2023-03-16 04:50:51 +08:00
` USER daemon ` ,
` WORKDIR /created/directory ` ,
` RUN ls /created ` ,
} , "\n" ) ,
fsSkip : [ ] string { "(dir):created:mtime" , "(dir):created:(dir):directory:mtime" } ,
dockerUseBuildKit : true ,
} ,
{
name : "env-precedence" ,
contextDir : "env/precedence" ,
dockerUseBuildKit : true ,
} ,
2024-03-04 22:42:38 +08:00
{
name : "multistage-copyback" ,
contextDir : "multistage/copyback" ,
dockerUseBuildKit : true ,
} ,
2024-04-12 03:48:57 +08:00
{
name : "heredoc-quoting" ,
dockerfile : "Dockerfile.heredoc-quoting" ,
dockerUseBuildKit : true ,
fsSkip : [ ] string { "(dir):etc:(dir):hostname" } , // buildkit does not create a phantom /etc/hostname
} ,
2024-06-04 02:50:23 +08:00
{
name : "workdir with trailing separator" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox" ,
2024-06-04 02:50:23 +08:00
"USER daemon" ,
"WORKDIR /tmp/" ,
} , "\n" ) ,
} ,
{
name : "workdir without trailing separator" ,
dockerfileContents : strings . Join ( [ ] string {
2024-08-08 04:46:39 +08:00
"FROM mirror.gcr.io/busybox" ,
2024-06-04 02:50:23 +08:00
"USER daemon" ,
"WORKDIR /tmp" ,
} , "\n" ) ,
} ,
2024-05-01 01:29:42 +08:00
{
2024-06-11 03:46:58 +08:00
name : "chown-volume" , // from podman #22530
contextDir : "chown-volume" ,
testUsingVolumes : true ,
2024-05-01 01:29:42 +08:00
} ,
2024-06-28 23:05:49 +08:00
{
name : "builtins" ,
contextDir : "builtins" ,
dockerUseBuildKit : true ,
2024-08-08 04:46:39 +08:00
buildArgs : map [ string ] string { "SOURCE" : "source" , "BUSYBOX" : "mirror.gcr.io/busybox" , "ALPINE" : "mirror.gcr.io/alpine" , "OWNERID" : "0" , "SECONDBASE" : "localhost/no-such-image" } ,
2024-06-28 23:05:49 +08:00
} ,
{
name : "header-builtin" ,
contextDir : "header-builtin" ,
dockerUseBuildKit : true ,
} ,
2024-08-15 05:54:31 +08:00
{
name : "copyglob-1" ,
contextDir : "copyglob" ,
dockerUseBuildKit : true ,
buildArgs : map [ string ] string { "SOURCE" : "**/*.txt" } ,
} ,
{
name : "copyglob-2" ,
contextDir : "copyglob" ,
dockerUseBuildKit : true ,
buildArgs : map [ string ] string { "SOURCE" : "**/sub/*.txt" } ,
} ,
{
name : "copyglob-3" ,
contextDir : "copyglob" ,
dockerUseBuildKit : true ,
buildArgs : map [ string ] string { "SOURCE" : "e/**/*sub/*.txt" } ,
} ,
{
name : "copyglob-4" ,
contextDir : "copyglob" ,
dockerUseBuildKit : true ,
buildArgs : map [ string ] string { "SOURCE" : "e/**/**/*sub/*.txt" } ,
} ,
2025-02-08 01:00:41 +08:00
{
name : "mount-cache-by-ownership" ,
dockerUseBuildKit : true ,
dockerfileContents : strings . Join ( [ ] string {
"FROM mirror.gcr.io/busybox" ,
"USER 10" ,
"RUN --mount=type=cache,uid=10,target=/cache touch /cache/10.txt" ,
"USER 0" ,
"RUN --mount=type=cache,target=/cache touch /cache/0.txt" ,
"RUN mkdir -m 770 /results /results/0 /results/10 /results/0+10" ,
"RUN chown -R 10 /results" ,
"RUN --mount=type=cache,target=/cache cp -a /cache/* /results/0" ,
"USER 10" ,
"RUN --mount=type=cache,uid=10,target=/cache cp -a /cache/* /results/10" ,
"USER 0" ,
"RUN --mount=type=cache,uid=10,target=/cache cp -a /cache/* /results/0+10" ,
"RUN touch -r /bin `find /results -print`" ,
} , "\n" ) ,
} ,
2020-06-19 06:03:43 +08:00
}
2023-11-01 22:18:40 +08:00
func TestCommit ( t * testing . T ) {
2025-02-20 00:36:36 +08:00
t . Parallel ( )
2023-11-01 22:18:40 +08:00
testCases := [ ] struct {
description string
baseImage string
changes , derivedChanges [ ] string
config , derivedConfig * docker . Config
} {
{
description : "defaults" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
} ,
{
description : "empty change" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "" } ,
} ,
{
description : "empty config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config { } ,
} ,
{
description : "cmd just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "CMD /bin/imaginarySh" } ,
} ,
{
description : "cmd just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
Cmd : [ ] string { "/usr/bin/imaginarySh" } ,
} ,
} ,
{
description : "cmd conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "CMD /bin/imaginarySh" } ,
config : & docker . Config {
Cmd : [ ] string { "/usr/bin/imaginarySh" } ,
} ,
} ,
{
description : "entrypoint just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "ENTRYPOINT /bin/imaginarySh" } ,
} ,
{
description : "entrypoint just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
Entrypoint : [ ] string { "/usr/bin/imaginarySh" } ,
} ,
} ,
{
description : "entrypoint conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "ENTRYPOINT /bin/imaginarySh" } ,
config : & docker . Config {
Entrypoint : [ ] string { "/usr/bin/imaginarySh" } ,
} ,
} ,
{
description : "environment just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "ENV A=1" , "ENV C=2" } ,
} ,
{
description : "environment just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
Env : [ ] string { "A=B" } ,
} ,
} ,
{
description : "environment with conflict union" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "ENV A=1" , "ENV C=2" } ,
config : & docker . Config {
Env : [ ] string { "A=B" } ,
} ,
} ,
{
description : "expose just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "EXPOSE 12345" } ,
} ,
{
description : "expose just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
2024-08-10 07:55:34 +08:00
ExposedPorts : map [ docker . Port ] struct { } { "23456" : { } } ,
2023-11-01 22:18:40 +08:00
} ,
} ,
{
description : "expose union" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "EXPOSE 12345" } ,
config : & docker . Config {
2024-08-10 07:55:34 +08:00
ExposedPorts : map [ docker . Port ] struct { } { "23456" : { } } ,
2023-11-01 22:18:40 +08:00
} ,
} ,
{
description : "healthcheck just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { ` HEALTHCHECK --interval=1s --timeout=1s --start-period=1s --retries=1 CMD ["/bin/false"] ` } ,
} ,
{
description : "healthcheck just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
Healthcheck : & docker . HealthConfig {
Test : [ ] string { "/bin/true" } ,
Interval : 2 * time . Second ,
Timeout : 2 * time . Second ,
StartPeriod : 2 * time . Second ,
Retries : 2 ,
} ,
} ,
} ,
{
description : "healthcheck conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { ` HEALTHCHECK --interval=1s --timeout=1s --start-period=1s --retries=1 CMD ["/bin/false"] ` } ,
config : & docker . Config {
Healthcheck : & docker . HealthConfig {
Test : [ ] string { "/bin/true" } ,
Interval : 2 * time . Second ,
Timeout : 2 * time . Second ,
StartPeriod : 2 * time . Second ,
Retries : 2 ,
} ,
} ,
} ,
{
description : "label just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "LABEL A=1 C=2" } ,
} ,
{
description : "label just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
Labels : map [ string ] string { "A" : "B" } ,
} ,
} ,
{
description : "label with conflict union" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "LABEL A=1 C=2" } ,
config : & docker . Config {
Labels : map [ string ] string { "A" : "B" } ,
} ,
} ,
// n.b. dockerd didn't like a MAINTAINER change, so no test for it, and it's not in a config blob
{
description : "onbuild just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "ONBUILD USER alice" , "ONBUILD LABEL A=1" } ,
derivedChanges : [ ] string { "LABEL C=3" } ,
} ,
{
description : "onbuild just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
OnBuild : [ ] string { "USER bob" , ` CMD ["/bin/smash"] ` , "LABEL B=2" } ,
} ,
derivedChanges : [ ] string { "LABEL C=3" } ,
} ,
{
description : "onbuild conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "ONBUILD USER alice" , "ONBUILD LABEL A=1" } ,
config : & docker . Config {
OnBuild : [ ] string { "USER bob" , ` CMD ["/bin/smash"] ` , "LABEL B=2" } ,
} ,
derivedChanges : [ ] string { "LABEL C=3" } ,
} ,
// n.b. dockerd didn't like a SHELL change, so no test for it or a conflict with a config blob
{
description : "shell just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
Shell : [ ] string { "/usr/bin/imaginarySh" } ,
} ,
} ,
{
description : "stop signal conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "STOPSIGNAL SIGTERM" } ,
config : & docker . Config {
StopSignal : "SIGKILL" ,
} ,
} ,
{
description : "stop timeout=0" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
StopTimeout : 0 ,
} ,
} ,
{
description : "stop timeout=15" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
StopTimeout : 15 ,
} ,
} ,
{
description : "stop timeout=15, then 0" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
StopTimeout : 15 ,
} ,
derivedConfig : & docker . Config {
StopTimeout : 0 ,
} ,
} ,
{
description : "stop timeout=0, then 15" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
StopTimeout : 0 ,
} ,
derivedConfig : & docker . Config {
StopTimeout : 15 ,
} ,
} ,
{
description : "user just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "USER 1001:1001" } ,
} ,
{
description : "user just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
User : "1000:1000" ,
} ,
} ,
{
description : "user with conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "USER 1001:1001" } ,
config : & docker . Config {
User : "1000:1000" ,
} ,
} ,
{
description : "volume just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "VOLUME /a-volume" } ,
} ,
{
description : "volume just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
2024-08-10 07:55:34 +08:00
Volumes : map [ string ] struct { } { "/b-volume" : { } } ,
2023-11-01 22:18:40 +08:00
} ,
} ,
{
description : "volume union" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "VOLUME /a-volume" } ,
config : & docker . Config {
2024-08-10 07:55:34 +08:00
Volumes : map [ string ] struct { } { "/b-volume" : { } } ,
2023-11-01 22:18:40 +08:00
} ,
} ,
{
description : "workdir just changes" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "WORKDIR /yeah" } ,
} ,
{
description : "workdir just config" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
config : & docker . Config {
WorkingDir : "/naw" ,
} ,
} ,
{
description : "workdir with conflict" ,
2024-08-08 04:46:39 +08:00
baseImage : "mirror.gcr.io/busybox" ,
2023-11-01 22:18:40 +08:00
changes : [ ] string { "WORKDIR /yeah" } ,
config : & docker . Config {
WorkingDir : "/naw" ,
} ,
} ,
}
var tempdir string
buildahDir := buildahDir
if buildahDir == "" {
if tempdir == "" {
tempdir = t . TempDir ( )
}
buildahDir = filepath . Join ( tempdir , "buildah" )
}
dockerDir := dockerDir
if dockerDir == "" {
if tempdir == "" {
tempdir = t . TempDir ( )
}
dockerDir = filepath . Join ( tempdir , "docker" )
}
ctx := context . TODO ( )
// connect to dockerd using go-dockerclient
client , err := docker . NewClientFromEnv ( )
require . NoErrorf ( t , err , "unable to initialize docker client" )
var dockerVersion [ ] string
if version , err := client . Version ( ) ; err == nil {
if version != nil {
for _ , s := range * version {
dockerVersion = append ( dockerVersion , s )
}
}
} else {
require . NoErrorf ( t , err , "unable to connect to docker daemon" )
}
// find a new place to store buildah builds
tempdir = t . TempDir ( )
// create subdirectories to use for buildah storage
rootDir := filepath . Join ( tempdir , "root" )
runrootDir := filepath . Join ( tempdir , "runroot" )
// initialize storage for buildah
options := storage . StoreOptions {
GraphDriverName : os . Getenv ( "STORAGE_DRIVER" ) ,
GraphRoot : rootDir ,
RunRoot : runrootDir ,
RootlessStoragePath : rootDir ,
}
store , err := storage . GetStore ( options )
require . NoErrorf ( t , err , "error creating buildah storage at %q" , rootDir )
defer func ( ) {
if store != nil {
_ , err := store . Shutdown ( true )
require . NoErrorf ( t , err , "error shutting down storage for buildah" )
}
} ( )
// walk through test cases
for testIndex , testCase := range testCases {
t . Run ( testCase . description , func ( t * testing . T ) {
test := testCases [ testIndex ]
// create the test container, then commit it, using the docker client
baseImage := test . baseImage
repository , tag := docker . ParseRepositoryTag ( baseImage )
if tag == "" {
tag = "latest"
}
baseImage = repository + ":" + tag
if _ , err := client . InspectImage ( test . baseImage ) ; err != nil && errors . Is ( err , docker . ErrNoSuchImage ) {
// oh, we need to pull the base image
err = client . PullImage ( docker . PullImageOptions {
Repository : repository ,
Tag : tag ,
} , docker . AuthConfiguration { } )
require . NoErrorf ( t , err , "pulling base image" )
}
container , err := client . CreateContainer ( docker . CreateContainerOptions {
Context : ctx ,
Config : & docker . Config {
Image : baseImage ,
} ,
} )
require . NoErrorf ( t , err , "creating the working container with docker" )
if err == nil {
defer func ( containerName string ) {
err := client . RemoveContainer ( docker . RemoveContainerOptions {
ID : containerName ,
Force : true ,
} )
assert . Nil ( t , err , "error deleting working docker container %q" , containerName )
} ( container . ID )
}
dockerImageName := "committed:" + strconv . Itoa ( testIndex )
dockerImage , err := client . CommitContainer ( docker . CommitContainerOptions {
Container : container . ID ,
Changes : test . changes ,
Run : test . config ,
Repository : dockerImageName ,
} )
assert . NoErrorf ( t , err , "committing the working container with docker" )
if err == nil {
defer func ( dockerImageName string ) {
err := client . RemoveImageExtended ( dockerImageName , docker . RemoveImageOptions {
Context : ctx ,
Force : true ,
} )
assert . Nil ( t , err , "error deleting newly-built docker image %q" , dockerImage . ID )
} ( dockerImageName )
}
dockerRef , err := alltransports . ParseImageName ( "docker-daemon:" + dockerImageName )
assert . NoErrorf ( t , err , "parsing name of newly-committed docker image" )
if len ( test . derivedChanges ) > 0 || test . derivedConfig != nil {
container , err := client . CreateContainer ( docker . CreateContainerOptions {
Context : ctx ,
Config : & docker . Config {
Image : dockerImage . ID ,
} ,
} )
require . NoErrorf ( t , err , "creating the derived container with docker" )
if err == nil {
defer func ( containerName string ) {
err := client . RemoveContainer ( docker . RemoveContainerOptions {
ID : containerName ,
Force : true ,
} )
assert . Nil ( t , err , "error deleting derived docker container %q" , containerName )
} ( container . ID )
}
derivedImageName := "derived:" + strconv . Itoa ( testIndex )
derivedImage , err := client . CommitContainer ( docker . CommitContainerOptions {
Container : container . ID ,
Changes : test . derivedChanges ,
Run : test . derivedConfig ,
Repository : derivedImageName ,
} )
assert . NoErrorf ( t , err , "committing the derived container with docker" )
defer func ( derivedImageName string ) {
err := client . RemoveImageExtended ( derivedImageName , docker . RemoveImageOptions {
Context : ctx ,
Force : true ,
} )
assert . Nil ( t , err , "error deleting newly-derived docker image %q" , derivedImage . ID )
} ( derivedImageName )
dockerRef , err = alltransports . ParseImageName ( "docker-daemon:" + derivedImageName )
assert . NoErrorf ( t , err , "parsing name of newly-derived docker image" )
}
// create the test container, then commit it, using the buildah API
builder , err := buildah . NewBuilder ( ctx , store , buildah . BuilderOptions {
FromImage : baseImage ,
} )
require . NoErrorf ( t , err , "creating the working container with buildah" )
defer func ( builder * buildah . Builder ) {
err := builder . Delete ( )
assert . NoErrorf ( t , err , "removing the working container" )
} ( builder )
var overrideConfig * manifest . Schema2Config
if test . config != nil {
overrideConfig = config . Schema2ConfigFromGoDockerclientConfig ( test . config )
}
buildahID , _ , _ , err := builder . Commit ( ctx , nil , buildah . CommitOptions {
PreferredManifestType : manifest . DockerV2Schema2MediaType ,
OverrideChanges : test . changes ,
OverrideConfig : overrideConfig ,
} )
assert . NoErrorf ( t , err , "committing buildah image" )
buildahRef , err := is . Transport . NewStoreReference ( store , nil , buildahID )
assert . NoErrorf ( t , err , "parsing name of newly-built buildah image" )
if len ( test . derivedChanges ) > 0 || test . derivedConfig != nil {
derivedBuilder , err := buildah . NewBuilder ( ctx , store , buildah . BuilderOptions {
FromImage : buildahID ,
} )
2024-04-22 17:23:10 +08:00
require . NoErrorf ( t , err , "creating the derived container with buildah" )
2023-11-01 22:18:40 +08:00
defer func ( builder * buildah . Builder ) {
err := builder . Delete ( )
assert . NoErrorf ( t , err , "removing the derived container" )
} ( derivedBuilder )
var overrideConfig * manifest . Schema2Config
if test . derivedConfig != nil {
overrideConfig = config . Schema2ConfigFromGoDockerclientConfig ( test . derivedConfig )
}
derivedID , _ , _ , err := builder . Commit ( ctx , nil , buildah . CommitOptions {
PreferredManifestType : manifest . DockerV2Schema2MediaType ,
OverrideChanges : test . derivedChanges ,
OverrideConfig : overrideConfig ,
} )
assert . NoErrorf ( t , err , "committing derived buildah image" )
buildahRef , err = is . Transport . NewStoreReference ( store , nil , derivedID )
assert . NoErrorf ( t , err , "parsing name of newly-derived buildah image" )
}
// scan the images
saveReport ( ctx , t , dockerRef , filepath . Join ( dockerDir , t . Name ( ) ) , [ ] byte { } , [ ] byte { } , dockerVersion )
saveReport ( ctx , t , buildahRef , filepath . Join ( buildahDir , t . Name ( ) ) , [ ] byte { } , [ ] byte { } , dockerVersion )
// compare the scans
_ , originalDockerConfig , ociDockerConfig , fsDocker := readReport ( t , filepath . Join ( dockerDir , t . Name ( ) ) )
_ , originalBuildahConfig , ociBuildahConfig , fsBuildah := readReport ( t , filepath . Join ( buildahDir , t . Name ( ) ) )
miss , left , diff , same := compareJSON ( originalDockerConfig , originalBuildahConfig , originalSkip )
if ! same {
assert . Failf ( t , "Image configurations differ as committed in Docker format" , configCompareResult ( miss , left , diff , "buildah" ) )
}
miss , left , diff , same = compareJSON ( ociDockerConfig , ociBuildahConfig , ociSkip )
if ! same {
assert . Failf ( t , "Image configurations differ when converted to OCI format" , configCompareResult ( miss , left , diff , "buildah" ) )
}
miss , left , diff , same = compareJSON ( fsDocker , fsBuildah , fsSkip )
if ! same {
assert . Failf ( t , "Filesystem contents differ" , fsCompareResult ( miss , left , diff , "buildah" ) )
}
} )
}
}