521 lines
19 KiB
Go
521 lines
19 KiB
Go
package phase_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/apex/log"
|
|
"github.com/apex/log/handlers/discard"
|
|
"github.com/golang/mock/gomock"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/fake"
|
|
"github.com/google/uuid"
|
|
"github.com/sclevine/spec"
|
|
"github.com/sclevine/spec/report"
|
|
|
|
"github.com/buildpacks/lifecycle/api"
|
|
"github.com/buildpacks/lifecycle/buildpack"
|
|
"github.com/buildpacks/lifecycle/internal/extend"
|
|
llog "github.com/buildpacks/lifecycle/log"
|
|
"github.com/buildpacks/lifecycle/phase"
|
|
"github.com/buildpacks/lifecycle/phase/testmock"
|
|
"github.com/buildpacks/lifecycle/platform"
|
|
"github.com/buildpacks/lifecycle/platform/files"
|
|
h "github.com/buildpacks/lifecycle/testhelpers"
|
|
)
|
|
|
|
func TestExtender(t *testing.T) {
|
|
spec.Run(t, "unit-new-extender", testExtenderFactory, spec.Report(report.Terminal{}))
|
|
spec.Run(t, "unit-extender", testExtender, spec.Sequential(), spec.Report(report.Terminal{}))
|
|
}
|
|
|
|
func testExtenderFactory(t *testing.T, when spec.G, it spec.S) {
|
|
when("#NewExtender", func() {
|
|
var (
|
|
mockController *gomock.Controller
|
|
extenderFactory *phase.HermeticFactory
|
|
fakeAPIVerifier *testmock.MockBuildpackAPIVerifier
|
|
fakeConfigHandler *testmock.MockConfigHandler
|
|
fakeDirStore *testmock.MockDirStore
|
|
logger *log.Logger
|
|
analyzedMD = files.Analyzed{
|
|
BuildImage: &files.ImageIdentifier{Reference: "some-build-image-ref"},
|
|
RunImage: &files.RunImage{Reference: "some-run-image-ref"},
|
|
}
|
|
extender *phase.Extender
|
|
kind = "build"
|
|
)
|
|
|
|
createExtender := func() {
|
|
fakeConfigHandler.EXPECT().ReadAnalyzed("some-analyzed-path", logger).Return(
|
|
analyzedMD, nil,
|
|
).AnyTimes()
|
|
fakeConfigHandler.EXPECT().ReadGroup("some-group-path").Return(
|
|
buildpack.Group{GroupExtensions: []buildpack.GroupElement{{ID: "A", Version: "v1", API: "0.9", Extension: true}}}, nil,
|
|
).AnyTimes()
|
|
fakeDirStore.EXPECT().LookupExt("A", "v1").Return(&buildpack.ExtDescriptor{
|
|
WithAPI: "0.9",
|
|
Extension: buildpack.ExtInfo{
|
|
BaseInfo: buildpack.BaseInfo{
|
|
ID: "A",
|
|
Version: "v1",
|
|
},
|
|
},
|
|
}, nil).AnyTimes()
|
|
fakeAPIVerifier.EXPECT().VerifyBuildpackAPI(buildpack.KindExtension, "A@v1", "0.9", logger).AnyTimes()
|
|
fakeDockerfileApplier := testmock.NewMockDockerfileApplier(mockController)
|
|
|
|
var err error
|
|
extender, err = extenderFactory.NewExtender(platform.LifecycleInputs{
|
|
AnalyzedPath: "some-analyzed-path",
|
|
AppDir: "some-app-dir",
|
|
ExtendedDir: "some-extended-dir",
|
|
GeneratedDir: "some-generated-dir",
|
|
GroupPath: "some-group-path",
|
|
LayersDir: "some-layers-dir",
|
|
PlatformDir: "some-platform-dir",
|
|
KanikoCacheTTL: 7 * (24 * time.Hour),
|
|
ExtendKind: kind,
|
|
}, fakeDockerfileApplier, logger)
|
|
h.AssertNil(t, err)
|
|
}
|
|
|
|
it.Before(func() {
|
|
mockController = gomock.NewController(t)
|
|
fakeAPIVerifier = testmock.NewMockBuildpackAPIVerifier(mockController)
|
|
fakeConfigHandler = testmock.NewMockConfigHandler(mockController)
|
|
fakeDirStore = testmock.NewMockDirStore(mockController)
|
|
extenderFactory = phase.NewHermeticFactory(api.Platform.Latest(), fakeAPIVerifier, fakeConfigHandler, nil)
|
|
|
|
logger = &log.Logger{Handler: &discard.Handler{}}
|
|
})
|
|
|
|
it.After(func() {
|
|
mockController.Finish()
|
|
})
|
|
|
|
it("configures the extender", func() {
|
|
createExtender()
|
|
|
|
h.AssertEq(t, extender.AppDir, "some-app-dir")
|
|
h.AssertEq(t, extender.ExtendedDir, "some-extended-dir")
|
|
h.AssertEq(t, extender.GeneratedDir, "some-generated-dir")
|
|
h.AssertEq(t, extender.ImageRef, "some-build-image-ref")
|
|
h.AssertEq(t, extender.LayersDir, "some-layers-dir")
|
|
h.AssertEq(t, extender.PlatformDir, "some-platform-dir")
|
|
h.AssertEq(t, extender.CacheTTL, 7*(24*time.Hour))
|
|
h.AssertEq(t, extender.Extensions, []buildpack.GroupElement{
|
|
{ID: "A", Version: "v1", API: "0.9", Extension: true},
|
|
})
|
|
})
|
|
|
|
when("build image is nil", func() {
|
|
it("does not panic", func() {
|
|
analyzedMD = files.Analyzed{}
|
|
createExtender()
|
|
})
|
|
})
|
|
|
|
when("kind", func() {
|
|
when("build", func() {
|
|
kind = "build"
|
|
|
|
it("configures the extender with the build image", func() {
|
|
createExtender()
|
|
h.AssertEq(t, extender.ImageRef, "some-build-image-ref")
|
|
})
|
|
})
|
|
|
|
when("run", func() {
|
|
kind = "run"
|
|
|
|
it("configures the extender with the run image", func() {
|
|
createExtender()
|
|
h.AssertEq(t, extender.ImageRef, "some-run-image-ref")
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func testExtender(t *testing.T, when spec.G, it spec.S) {
|
|
when("Extend", func() {
|
|
var (
|
|
extender *phase.Extender
|
|
fakeDockerfileApplier *testmock.MockDockerfileApplier
|
|
mockCtrl *gomock.Controller
|
|
extendedDir string
|
|
generatedDir string
|
|
someFakeImage *fake.FakeImage
|
|
|
|
logger *log.Logger
|
|
)
|
|
|
|
it.Before(func() {
|
|
mockCtrl = gomock.NewController(t)
|
|
|
|
var err error
|
|
extendedDir, err = os.MkdirTemp("", "lifecycle")
|
|
h.AssertNil(t, err)
|
|
generatedDir, err = os.MkdirTemp("", "lifecycle")
|
|
h.AssertNil(t, err)
|
|
|
|
someFakeImage = &fake.FakeImage{}
|
|
|
|
providedExtensions := []buildpack.GroupElement{
|
|
{ID: "A", Version: "v1", API: api.Buildpack.Latest().String(), Homepage: "A Homepage"},
|
|
{ID: "B", Version: "v2", API: api.Buildpack.Latest().String()},
|
|
}
|
|
fakeDockerfileApplier = testmock.NewMockDockerfileApplier(mockCtrl)
|
|
extender = &phase.Extender{
|
|
AppDir: "some-app-dir",
|
|
ImageRef: "some-image-tag@sha256:9412cff392ca11c0d7b9df015808c4e40aff218fbe324df6490b9552ba82be38",
|
|
ExtendedDir: extendedDir,
|
|
GeneratedDir: generatedDir,
|
|
LayersDir: "some-layers-dir",
|
|
PlatformDir: "some-platform-dir",
|
|
CacheTTL: 7 * (24 * time.Hour),
|
|
DockerfileApplier: fakeDockerfileApplier,
|
|
Extensions: providedExtensions,
|
|
}
|
|
|
|
logger = &log.Logger{Handler: &discard.Handler{}}
|
|
})
|
|
|
|
it.After(func() {
|
|
_ = os.RemoveAll(extendedDir)
|
|
_ = os.RemoveAll(generatedDir)
|
|
mockCtrl.Finish()
|
|
})
|
|
|
|
// prepares fixtures and expected data for extensions
|
|
// On older platforms, the extender looks for Dockerfiles in <generated>/<kind>/<extension-id>/Dockerfile.
|
|
// On newer platforms, it looks for Dockerfiles in <generated>/<extension-id>/kind.Dockerfile, and context directories are allowed.
|
|
prepareDockerfile := func(id, kind, contextDir string) extend.Dockerfile {
|
|
basePath := filepath.Join(generatedDir, kind, id)
|
|
dockerfilePath := filepath.Join(basePath, "Dockerfile")
|
|
|
|
if extender.PlatformAPI.AtLeast("0.13") {
|
|
basePath = filepath.Join(generatedDir, id)
|
|
dockerfilePath = filepath.Join(basePath, fmt.Sprintf("%s.Dockerfile", kind))
|
|
|
|
switch contextDir {
|
|
case "specific":
|
|
h.Mkdir(t, filepath.Join(basePath, fmt.Sprintf("context.%s", kind)))
|
|
case "shared":
|
|
h.Mkdir(t, filepath.Join(basePath, "context"))
|
|
case "app":
|
|
// nothing to do
|
|
default:
|
|
t.Fail()
|
|
}
|
|
} else {
|
|
h.AssertEq(t, contextDir, "app")
|
|
}
|
|
|
|
expectedDockerfile := extend.Dockerfile{
|
|
Path: dockerfilePath,
|
|
Args: []extend.Arg{{Name: fmt.Sprintf("arg%s", id), Value: fmt.Sprintf("value%s", id)}},
|
|
}
|
|
h.Mkdir(t, basePath)
|
|
h.Mkfile(t, "some dockerfile content", dockerfilePath)
|
|
|
|
buf := new(bytes.Buffer)
|
|
var data extend.Config
|
|
switch kind {
|
|
case "run":
|
|
data = extend.Config{Run: extend.BuildConfig{Args: expectedDockerfile.Args}}
|
|
case "build":
|
|
data = extend.Config{Build: extend.BuildConfig{Args: expectedDockerfile.Args}}
|
|
default:
|
|
t.Fail()
|
|
}
|
|
h.AssertNil(t, toml.NewEncoder(buf).Encode(data))
|
|
h.Mkfile(t, buf.String(), filepath.Join(basePath, "extend-config.toml"))
|
|
return expectedDockerfile
|
|
}
|
|
|
|
for _, platformAPI := range []*api.Version{
|
|
api.MustParse("0.12"),
|
|
api.MustParse("0.13"),
|
|
} {
|
|
when(fmt.Sprintf("using the platform API %s", platformAPI), func() {
|
|
it.Before(func() {
|
|
extender.PlatformAPI = platformAPI
|
|
})
|
|
|
|
when("build base image", func() {
|
|
it("applies the Dockerfile with the expected args and opts", func() {
|
|
var expectedDockerfileA, expectedDockerfileB extend.Dockerfile
|
|
var expectedBuildContextA, expectedBuildContextB string
|
|
|
|
if platformAPI.AtLeast("0.13") {
|
|
expectedDockerfileA = prepareDockerfile("A", "build", "specific")
|
|
expectedBuildContextA = filepath.Join(generatedDir, "A", "context.build")
|
|
expectedDockerfileB = prepareDockerfile("B", "build", "shared")
|
|
expectedBuildContextB = filepath.Join(generatedDir, "B", "context")
|
|
} else {
|
|
expectedDockerfileA = prepareDockerfile("A", "build", "app")
|
|
expectedBuildContextA = "some-app-dir"
|
|
expectedDockerfileB = prepareDockerfile("B", "build", "app")
|
|
expectedBuildContextB = "some-app-dir"
|
|
}
|
|
|
|
fakeDockerfileApplier.EXPECT().ImageFor(extender.ImageRef).Return(someFakeImage, nil)
|
|
someFakeImage.ManifestReturns(&v1.Manifest{Layers: []v1.Descriptor{}}, nil)
|
|
|
|
// first dockerfile
|
|
|
|
firstConfig := &v1.ConfigFile{Config: v1.Config{
|
|
User: "0:5678",
|
|
}}
|
|
someFakeImage.ConfigFileReturnsOnCall(0, firstConfig, nil)
|
|
fakeDockerfileApplier.EXPECT().Apply(
|
|
gomock.Any(),
|
|
gomock.Any(), // we mutate the provided image so we can't expect the fake image
|
|
extend.Options{
|
|
BuildContext: expectedBuildContextA,
|
|
IgnorePaths: []string{"some-app-dir", "some-layers-dir", "some-platform-dir"},
|
|
CacheTTL: 7 * (24 * time.Hour),
|
|
},
|
|
logger,
|
|
).DoAndReturn(
|
|
func(dockerfile extend.Dockerfile, _ v1.Image, _ extend.Options, _ llog.Logger) (v1.Image, error) {
|
|
h.AssertEq(t, dockerfile.Path, expectedDockerfileA.Path)
|
|
h.AssertEq(t, len(dockerfile.Args), 4)
|
|
h.AssertEq(t, dockerfile.Args[0].Name, "build_id")
|
|
_, err := uuid.Parse(dockerfile.Args[0].Value)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, dockerfile.Args[1].Name, "user_id")
|
|
h.AssertEq(t, dockerfile.Args[1].Value, "0")
|
|
h.AssertEq(t, dockerfile.Args[2].Name, "group_id")
|
|
h.AssertEq(t, dockerfile.Args[2].Value, "5678")
|
|
h.AssertEq(t, dockerfile.Args[3], expectedDockerfileA.Args[0])
|
|
|
|
return someFakeImage, nil
|
|
})
|
|
secondConfig := &v1.ConfigFile{Config: v1.Config{
|
|
User: "2345:6789",
|
|
Env: []string{"SOME_VAR=some-val"},
|
|
}}
|
|
someFakeImage.ConfigFileReturnsOnCall(1, secondConfig, nil)
|
|
|
|
// second dockerfile
|
|
|
|
someFakeImage.ConfigFileReturnsOnCall(2, secondConfig, nil)
|
|
fakeDockerfileApplier.EXPECT().Apply(
|
|
gomock.Any(),
|
|
someFakeImage,
|
|
extend.Options{
|
|
BuildContext: expectedBuildContextB,
|
|
IgnorePaths: []string{"some-app-dir", "some-layers-dir", "some-platform-dir"},
|
|
CacheTTL: 7 * (24 * time.Hour),
|
|
},
|
|
logger,
|
|
).DoAndReturn(
|
|
func(dockerfile extend.Dockerfile, _ v1.Image, _ extend.Options, _ llog.Logger) (v1.Image, error) {
|
|
h.AssertEq(t, dockerfile.Path, expectedDockerfileB.Path)
|
|
h.AssertEq(t, len(dockerfile.Args), 4)
|
|
h.AssertEq(t, dockerfile.Args[0].Name, "build_id")
|
|
_, err := uuid.Parse(dockerfile.Args[0].Value)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, dockerfile.Args[1].Name, "user_id")
|
|
h.AssertEq(t, dockerfile.Args[1].Value, "2345")
|
|
h.AssertEq(t, dockerfile.Args[2].Name, "group_id")
|
|
h.AssertEq(t, dockerfile.Args[2].Value, "6789")
|
|
h.AssertEq(t, dockerfile.Args[3], expectedDockerfileB.Args[0])
|
|
|
|
return someFakeImage, nil
|
|
})
|
|
someFakeImage.ConfigFileReturnsOnCall(3, secondConfig, nil)
|
|
|
|
fakeDockerfileApplier.EXPECT().Cleanup().Return(nil)
|
|
|
|
h.AssertNil(t, extender.Extend("build", logger))
|
|
h.AssertEq(t, os.Getenv("SOME_VAR"), "some-val")
|
|
h.AssertNil(t, os.Unsetenv("SOME_VAR"))
|
|
})
|
|
})
|
|
|
|
when("run base image", func() {
|
|
type testCase struct {
|
|
firstDockerfileRebasable bool
|
|
secondDockerfileRebasable bool
|
|
expectedImageSHA string
|
|
}
|
|
var (
|
|
rebasableSHA = "sha256:2407b98f3bcee33d24f47fc3d7beaf0928b3fa799a8a84aa9f9f239fcded6e62"
|
|
notRebasableSHA = "sha256:cc3b90b798b76e7d41de01d4f02f2917694ef0b09d0d923314c8d6c31c4ae8e9"
|
|
)
|
|
for _, tc := range []testCase{
|
|
{
|
|
firstDockerfileRebasable: true,
|
|
secondDockerfileRebasable: true,
|
|
expectedImageSHA: rebasableSHA,
|
|
},
|
|
{
|
|
firstDockerfileRebasable: true,
|
|
secondDockerfileRebasable: false,
|
|
expectedImageSHA: notRebasableSHA,
|
|
},
|
|
{
|
|
firstDockerfileRebasable: false,
|
|
secondDockerfileRebasable: true,
|
|
expectedImageSHA: notRebasableSHA,
|
|
},
|
|
{
|
|
firstDockerfileRebasable: false,
|
|
secondDockerfileRebasable: false,
|
|
expectedImageSHA: notRebasableSHA,
|
|
},
|
|
} {
|
|
when := when
|
|
desc := func(b bool) string {
|
|
if b {
|
|
return "rebasable"
|
|
}
|
|
return "not rebasable"
|
|
}
|
|
when(fmt.Sprintf("first Dockerfile is %s, second Dockerfile is %s", desc(tc.firstDockerfileRebasable), desc(tc.secondDockerfileRebasable)), func() {
|
|
it("applies the Dockerfile with the expected args and opts", func() {
|
|
expectedDockerfileA := prepareDockerfile("A", "run", "app")
|
|
expectedDockerfileB := prepareDockerfile("B", "run", "app")
|
|
|
|
fakeDockerfileApplier.EXPECT().ImageFor(extender.ImageRef).Return(someFakeImage, nil)
|
|
someFakeImage.ManifestReturns(&v1.Manifest{Layers: []v1.Descriptor{}}, nil)
|
|
someFakeImage.ConfigFileReturnsOnCall(0, &v1.ConfigFile{Config: v1.Config{
|
|
User: "1234:5678",
|
|
}}, nil)
|
|
|
|
// first dockerfile
|
|
|
|
fakeDockerfileApplier.EXPECT().Apply(
|
|
gomock.Any(),
|
|
gomock.Any(), // we mutate the provided image so we can't expect the fake image
|
|
extend.Options{
|
|
BuildContext: "some-app-dir",
|
|
IgnorePaths: []string{"some-app-dir", "some-layers-dir", "some-platform-dir"},
|
|
CacheTTL: 7 * (24 * time.Hour),
|
|
},
|
|
logger,
|
|
).DoAndReturn(
|
|
func(dockerfile extend.Dockerfile, _ v1.Image, _ extend.Options, _ llog.Logger) (v1.Image, error) {
|
|
h.AssertEq(t, dockerfile.Path, expectedDockerfileA.Path)
|
|
h.AssertEq(t, len(dockerfile.Args), 4) // build_id, user_id, group_id
|
|
h.AssertEq(t, dockerfile.Args[0].Name, "build_id")
|
|
_, err := uuid.Parse(dockerfile.Args[0].Value)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, dockerfile.Args[1].Name, "user_id")
|
|
h.AssertEq(t, dockerfile.Args[1].Value, "1234")
|
|
h.AssertEq(t, dockerfile.Args[2].Name, "group_id")
|
|
h.AssertEq(t, dockerfile.Args[2].Value, "5678")
|
|
h.AssertEq(t, dockerfile.Args[3], expectedDockerfileA.Args[0])
|
|
|
|
return someFakeImage, nil
|
|
})
|
|
firstConfig := &v1.ConfigFile{Config: v1.Config{
|
|
User: "1234:5678",
|
|
Labels: map[string]string{phase.RebasableLabel: fmt.Sprintf("%t", tc.firstDockerfileRebasable)},
|
|
}}
|
|
someFakeImage.ConfigFileReturnsOnCall(1, firstConfig, nil)
|
|
|
|
// second dockerfile
|
|
|
|
fakeDockerfileApplier.EXPECT().Apply(
|
|
gomock.Any(),
|
|
someFakeImage,
|
|
extend.Options{
|
|
BuildContext: "some-app-dir",
|
|
IgnorePaths: []string{"some-app-dir", "some-layers-dir", "some-platform-dir"},
|
|
CacheTTL: 7 * (24 * time.Hour),
|
|
},
|
|
logger,
|
|
).DoAndReturn(
|
|
func(dockerfile extend.Dockerfile, _ v1.Image, _ extend.Options, _ llog.Logger) (v1.Image, error) {
|
|
h.AssertEq(t, dockerfile.Path, expectedDockerfileB.Path)
|
|
h.AssertEq(t, len(dockerfile.Args), 4) // build_id, user_id, group_id
|
|
h.AssertEq(t, dockerfile.Args[0].Name, "build_id")
|
|
_, err := uuid.Parse(dockerfile.Args[0].Value)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, dockerfile.Args[1].Name, "user_id")
|
|
h.AssertEq(t, dockerfile.Args[1].Value, "1234")
|
|
h.AssertEq(t, dockerfile.Args[2].Name, "group_id")
|
|
h.AssertEq(t, dockerfile.Args[2].Value, "5678")
|
|
h.AssertEq(t, dockerfile.Args[3], expectedDockerfileB.Args[0])
|
|
|
|
return someFakeImage, nil
|
|
})
|
|
secondConfig := &v1.ConfigFile{Config: v1.Config{
|
|
User: "1234:5678",
|
|
Labels: map[string]string{phase.RebasableLabel: fmt.Sprintf("%t", tc.secondDockerfileRebasable)},
|
|
}}
|
|
someFakeImage.ConfigFileReturnsOnCall(2, secondConfig, nil)
|
|
|
|
// set label
|
|
|
|
someFakeImage.ConfigFileReturnsOnCall(3, secondConfig, nil)
|
|
someFakeImage.ConfigFileReturnsOnCall(4, secondConfig, nil)
|
|
|
|
// save without base layers
|
|
|
|
imageHash := v1.Hash{Algorithm: "sha256", Hex: "some-image-hex"}
|
|
someFakeImage.DigestReturns(imageHash, nil)
|
|
someFakeImage.ConfigNameReturns(v1.Hash{Algorithm: "sha256", Hex: "some-config-hex"}, nil)
|
|
|
|
fakeDockerfileApplier.EXPECT().Cleanup().Return(nil)
|
|
|
|
h.AssertNil(t, extender.Extend("run", logger))
|
|
outputImagePath := filepath.Join(extendedDir, "run", tc.expectedImageSHA)
|
|
h.AssertPathExists(t, outputImagePath)
|
|
fis, err := os.ReadDir(outputImagePath)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, len(fis), 3)
|
|
})
|
|
})
|
|
}
|
|
|
|
it("errors if the last extension leaves the user as root", func() {
|
|
expectedDockerfileA := prepareDockerfile("A", "run", "app")
|
|
|
|
fakeDockerfileApplier.EXPECT().ImageFor(extender.ImageRef).Return(someFakeImage, nil)
|
|
firstConfig := &v1.ConfigFile{Config: v1.Config{
|
|
User: "0:5678",
|
|
}}
|
|
someFakeImage.ConfigFileReturns(firstConfig, nil)
|
|
someFakeImage.ManifestReturns(&v1.Manifest{Layers: []v1.Descriptor{}}, nil)
|
|
|
|
fakeDockerfileApplier.EXPECT().Apply(
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
gomock.Any(),
|
|
logger,
|
|
).DoAndReturn(
|
|
func(dockerfile extend.Dockerfile, _ v1.Image, _ extend.Options, _ llog.Logger) (v1.Image, error) {
|
|
h.AssertEq(t, dockerfile.Path, expectedDockerfileA.Path)
|
|
h.AssertEq(t, len(dockerfile.Args), 4)
|
|
h.AssertEq(t, dockerfile.Args[0].Name, "build_id")
|
|
_, err := uuid.Parse(dockerfile.Args[0].Value)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, dockerfile.Args[1].Name, "user_id")
|
|
h.AssertEq(t, dockerfile.Args[1].Value, "0")
|
|
h.AssertEq(t, dockerfile.Args[2].Name, "group_id")
|
|
h.AssertEq(t, dockerfile.Args[2].Value, "5678")
|
|
h.AssertEq(t, dockerfile.Args[3], expectedDockerfileA.Args[0])
|
|
|
|
return someFakeImage, nil
|
|
})
|
|
|
|
err := extender.Extend("run", logger)
|
|
h.AssertError(t, err, "extending run image: the final user ID is 0 (root); please add another extension that resets the user to non-root")
|
|
})
|
|
})
|
|
})
|
|
}
|
|
})
|
|
}
|