Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RFC-0096 Remove Stacks & Mixins: phase 1 #1660

Merged
merged 5 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions builder/config_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type Config struct {
OrderExtensions dist.Order `toml:"order-extensions"`
Stack StackConfig `toml:"stack"`
Lifecycle LifecycleConfig `toml:"lifecycle"`
Run RunConfig `toml:"run"`
Build BuildConfig `toml:"build"`
}

// ModuleCollection is a list of ModuleConfigs
Expand Down Expand Up @@ -55,6 +57,22 @@ type LifecycleConfig struct {
Version string `toml:"version"`
}

// RunConfig set of run image configuration
type RunConfig struct {
Images []RunImageConfig `toml:"images"`
}

// RunImageConfig run image id and mirrors
type RunImageConfig struct {
Image string `toml:"image"`
Mirrors []string `toml:"run-image-mirrors,omitempty"`
}

// BuildConfig build image configuration
type BuildConfig struct {
Image string `toml:"image"`
}

// ReadConfig reads a builder configuration from the file path provided and returns the
// configuration along with any warnings encountered while parsing
func ReadConfig(path string) (config Config, warnings []string, err error) {
Expand All @@ -73,26 +91,57 @@ func ReadConfig(path string) (config Config, warnings []string, err error) {
warnings = append(warnings, fmt.Sprintf("empty %s definition", style.Symbol("order")))
}

config.mergeStackWithImages()

return config, warnings, nil
}

// ValidateConfig validates the config
func ValidateConfig(c Config) error {
if c.Stack.ID == "" {
return errors.New("stack.id is required")
if c.Build.Image == "" && c.Stack.BuildImage == "" {
return errors.New("build.image is required")
} else if c.Build.Image != "" && c.Stack.BuildImage != "" && c.Build.Image != c.Stack.BuildImage {
return errors.New("build.image and stack.build-image do not match")
}

if c.Stack.BuildImage == "" {
return errors.New("stack.build-image is required")
if len(c.Run.Images) == 0 && (c.Stack.RunImage == "" || c.Stack.ID == "") {
return errors.New("run.images are required")
}

if c.Stack.RunImage == "" {
return errors.New("stack.run-image is required")
for _, runImage := range c.Run.Images {
if runImage.Image == "" {
return errors.New("run.images.image is required")
}
}

if c.Stack.RunImage != "" && c.Run.Images[0].Image != c.Stack.RunImage {
return errors.New("run.images and stack.run-image do not match")
}

return nil
}

func (c *Config) mergeStackWithImages() {
// RFC-0096
if c.Build.Image != "" {
c.Stack.BuildImage = c.Build.Image
} else if c.Build.Image == "" && c.Stack.BuildImage != "" {
c.Build.Image = c.Stack.BuildImage
}

if len(c.Run.Images) != 0 {
// use the first run image as the "stack"
c.Stack.RunImage = c.Run.Images[0].Image
c.Stack.RunImageMirrors = c.Run.Images[0].Mirrors
} else if len(c.Run.Images) == 0 && c.Stack.RunImage != "" {
c.Run.Images = []RunImageConfig{{
Image: c.Stack.RunImage,
Mirrors: c.Stack.RunImageMirrors,
},
}
}
}

// parseConfig reads a builder configuration from file
func parseConfig(file *os.File) (Config, error) {
builderConfig := Config{}
Expand Down
44 changes: 40 additions & 4 deletions builder/config_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ uri = "noop-buildpack.tgz"
testBuildImage = "test-build-image"
)

it("returns error if no id", func() {
it("returns error if no stack id and no run images", func() {
config := builder.Config{
Stack: builder.StackConfig{
BuildImage: testBuildImage,
RunImage: testRunImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "stack.id is required")
h.AssertError(t, builder.ValidateConfig(config), "run.images are required")
})

it("returns error if no build image", func() {
Expand All @@ -181,7 +181,7 @@ uri = "noop-buildpack.tgz"
ID: testID,
RunImage: testRunImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "stack.build-image is required")
h.AssertError(t, builder.ValidateConfig(config), "build.image is required")
})

it("returns error if no run image", func() {
Expand All @@ -190,7 +190,43 @@ uri = "noop-buildpack.tgz"
ID: testID,
BuildImage: testBuildImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "stack.run-image is required")
h.AssertError(t, builder.ValidateConfig(config), "run.images are required")
})

it("returns error if no run images image", func() {
config := builder.Config{
Build: builder.BuildConfig{
Image: testBuildImage,
},
Run: builder.RunConfig{
Images: []builder.RunImageConfig{{
Image: "",
}},
}}
h.AssertError(t, builder.ValidateConfig(config), "run.images.image is required")
})

it("returns error if no stack or run image", func() {
config := builder.Config{
Build: builder.BuildConfig{
Image: testBuildImage,
}}
h.AssertError(t, builder.ValidateConfig(config), "run.images are required")
})

it("returns error if no stack and no build image", func() {
config := builder.Config{
Run: builder.RunConfig{
Images: []builder.RunImageConfig{{
Image: testBuildImage,
}},
}}
h.AssertError(t, builder.ValidateConfig(config), "build.image is required")
})

it("returns error if no stack, run, or build image", func() {
config := builder.Config{}
h.AssertError(t, builder.ValidateConfig(config), "build.image is required")
})
})
}
31 changes: 31 additions & 0 deletions internal/build/container_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,37 @@ func WriteStackToml(dstPath string, stack builder.StackMetadata, os string) Cont
}
}

// WriteRunToml writes a `run.toml` based on the RunConfig provided to the destination path.
func WriteRunToml(dstPath string, runImages []builder.RunImageMetadata, os string) ContainerOperation {
return func(ctrClient DockerClient, ctx context.Context, containerID string, stdout, stderr io.Writer) error {
buf := &bytes.Buffer{}
err := toml.NewEncoder(buf).Encode(builder.RunImages{
Images: runImages,
})
if err != nil {
return errors.Wrap(err, "marshaling run metadata")
}

tarBuilder := archive.TarBuilder{}

tarPath := dstPath
if os == "windows" {
tarPath = paths.WindowsToSlash(dstPath)
}

tarBuilder.AddFile(tarPath, 0755, archive.NormalizedDateTime, buf.Bytes())
reader := tarBuilder.Reader(archive.DefaultTarWriterFactory())
defer reader.Close()

if os == "windows" {
dirName := paths.WindowsDir(dstPath)
return copyDirWindows(ctx, ctrClient, containerID, reader, dirName, stdout, stderr)
}

return ctrClient.CopyToContainer(ctx, containerID, "/", reader, types.CopyToContainerOptions{})
}
}

func createReader(src, dst string, uid, gid int, includeRoot bool, fileFilter func(string) bool) (io.ReadCloser, error) {
fi, err := os.Stat(src)
if err != nil {
Expand Down
96 changes: 96 additions & 0 deletions internal/build/container_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,102 @@ drwxr-xr-x 2 123 456 (.*) some-vol
})
})

when("#WriteRunToml", func() {
it("writes file", func() {
containerDir := "/layers-vol"
containerPath := "/layers-vol/run.toml"
if osType == "windows" {
containerDir = `c:\layers-vol`
containerPath = `c:\layers-vol\run.toml`
}

ctrCmd := []string{"ls", "-al", "/layers-vol/run.toml"}
if osType == "windows" {
ctrCmd = []string{"cmd", "/c", `dir /q /n c:\layers-vol\run.toml`}
}
ctx := context.Background()
ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
h.AssertNil(t, err)
defer cleanupContainer(ctx, ctr.ID)

writeOp := build.WriteRunToml(containerPath, []builder.RunImageMetadata{builder.RunImageMetadata{
Image: "image-1",
Mirrors: []string{
"mirror-1",
"mirror-2",
},
},
}, osType)

var outBuf, errBuf bytes.Buffer
err = writeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)

err = container.RunWithHandler(ctx, ctrClient, ctr.ID, container.DefaultHandler(&outBuf, &errBuf))
h.AssertNil(t, err)

h.AssertEq(t, errBuf.String(), "")
if osType == "windows" {
h.AssertContains(t, outBuf.String(), `01/01/1980 12:00 AM 68 ... run.toml`)
} else {
h.AssertContains(t, outBuf.String(), `-rwxr-xr-x 1 root root 68 Jan 1 1980 /layers-vol/run.toml`)
}
})

it("has expected contents", func() {
containerDir := "/layers-vol"
containerPath := "/layers-vol/run.toml"
if osType == "windows" {
containerDir = `c:\layers-vol`
containerPath = `c:\layers-vol\run.toml`
}

ctrCmd := []string{"cat", "/layers-vol/run.toml"}
if osType == "windows" {
ctrCmd = []string{"cmd", "/c", `type c:\layers-vol\run.toml`}
}

ctx := context.Background()
ctr, err := createContainer(ctx, imageName, containerDir, osType, ctrCmd...)
h.AssertNil(t, err)
defer cleanupContainer(ctx, ctr.ID)

writeOp := build.WriteRunToml(containerPath, []builder.RunImageMetadata{
{
Image: "image-1",
Mirrors: []string{
"mirror-1",
"mirror-2",
},
},
{
Image: "image-2",
Mirrors: []string{
"mirror-3",
"mirror-4",
},
},
}, osType)

var outBuf, errBuf bytes.Buffer
err = writeOp(ctrClient, ctx, ctr.ID, &outBuf, &errBuf)
h.AssertNil(t, err)

err = container.RunWithHandler(ctx, ctrClient, ctr.ID, container.DefaultHandler(&outBuf, &errBuf))
h.AssertNil(t, err)

h.AssertEq(t, errBuf.String(), "")
h.AssertContains(t, outBuf.String(), `[[images]]
image = "image-1"
mirrors = ["mirror-1", "mirror-2"]

[[images]]
image = "image-2"
mirrors = ["mirror-3", "mirror-4"]
`)
})
})

when("#WriteProjectMetadata", func() {
it("writes file", func() {
containerDir := "/layers-vol"
Expand Down
5 changes: 5 additions & 0 deletions internal/build/fakes/fake_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type FakeBuilder struct {
ReturnForGID int
ReturnForLifecycleDescriptor builder.LifecycleDescriptor
ReturnForStack builder.StackMetadata
ReturnForRunImages []builder.RunImageMetadata
ReturnForOrderExtensions dist.Order
}

Expand Down Expand Up @@ -112,6 +113,10 @@ func (b *FakeBuilder) Stack() builder.StackMetadata {
return b.ReturnForStack
}

func (b *FakeBuilder) RunImages() []builder.RunImageMetadata {
return b.ReturnForRunImages
}

func WithBuilder(builder *FakeBuilder) func(*build.LifecycleOptions) {
return func(opts *build.LifecycleOptions) {
opts.Builder = builder
Expand Down
5 changes: 5 additions & 0 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@ func (l *LifecycleExecution) Analyze(ctx context.Context, buildCache, launchCach
}

stackOp := NullOp()
runOp := NullOp()
if !platformAPILessThan07 {
for _, tag := range l.opts.AdditionalTags {
args = append([]string{"-tag", tag}, args...)
Expand All @@ -536,6 +537,7 @@ func (l *LifecycleExecution) Analyze(ctx context.Context, buildCache, launchCach
}
args = append([]string{"-stack", l.mountPaths.stackPath()}, args...)
stackOp = WithContainerOperations(WriteStackToml(l.mountPaths.stackPath(), l.opts.Builder.Stack(), l.os))
runOp = WithContainerOperations(WriteRunToml(l.mountPaths.runPath(), l.opts.Builder.RunImages(), l.os))
}

flagsOp := WithFlags(flags...)
Expand All @@ -560,6 +562,7 @@ func (l *LifecycleExecution) Analyze(ctx context.Context, buildCache, launchCach
flagsOp,
cacheBindOp,
stackOp,
runOp,
)

analyze = phaseFactory.New(configProvider)
Expand All @@ -581,6 +584,7 @@ func (l *LifecycleExecution) Analyze(ctx context.Context, buildCache, launchCach
WithNetwork(l.opts.Network),
cacheBindOp,
stackOp,
runOp,
)

analyze = phaseFactory.New(configProvider)
Expand Down Expand Up @@ -695,6 +699,7 @@ func (l *LifecycleExecution) Export(ctx context.Context, buildCache, launchCache
WithNetwork(l.opts.Network),
cacheBindOp,
WithContainerOperations(WriteStackToml(l.mountPaths.stackPath(), l.opts.Builder.Stack(), l.os)),
WithContainerOperations(WriteRunToml(l.mountPaths.runPath(), l.opts.Builder.RunImages(), l.os)),
WithContainerOperations(WriteProjectMetadata(l.mountPaths.projectPath(), l.opts.ProjectMetadata, l.os)),
If(l.opts.SBOMDestinationDir != "", WithPostContainerRunOperations(
EnsureVolumeAccess(l.opts.Builder.UID(), l.opts.Builder.GID(), l.os, l.layersVolume, l.appVolume),
Expand Down
10 changes: 6 additions & 4 deletions internal/build/lifecycle_execution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1838,9 +1838,10 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
expectedBinds := []string{"some-cache:/cache"}
h.AssertSliceContains(t, configProvider.HostConfig().Binds, expectedBinds...)

h.AssertEq(t, len(configProvider.ContainerOps()), 2)
h.AssertEq(t, len(configProvider.ContainerOps()), 3)
h.AssertFunctionName(t, configProvider.ContainerOps()[0], "WriteStackToml")
h.AssertFunctionName(t, configProvider.ContainerOps()[1], "WriteProjectMetadata")
h.AssertFunctionName(t, configProvider.ContainerOps()[1], "WriteRunToml")
h.AssertFunctionName(t, configProvider.ContainerOps()[2], "WriteProjectMetadata")
})

when("default process type", func() {
Expand Down Expand Up @@ -1945,9 +1946,10 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
expectedBinds := []string{"some-cache:/cache", "some-launch-cache:/launch-cache"}
h.AssertSliceContains(t, configProvider.HostConfig().Binds, expectedBinds...)

h.AssertEq(t, len(configProvider.ContainerOps()), 2)
h.AssertEq(t, len(configProvider.ContainerOps()), 3)
h.AssertFunctionName(t, configProvider.ContainerOps()[0], "WriteStackToml")
h.AssertFunctionName(t, configProvider.ContainerOps()[1], "WriteProjectMetadata")
h.AssertFunctionName(t, configProvider.ContainerOps()[1], "WriteRunToml")
h.AssertFunctionName(t, configProvider.ContainerOps()[2], "WriteProjectMetadata")
})

when("default process type", func() {
Expand Down
1 change: 1 addition & 0 deletions internal/build/lifecycle_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Builder interface {
GID() int
LifecycleDescriptor() builder.LifecycleDescriptor
Stack() builder.StackMetadata
RunImages() []builder.RunImageMetadata
Image() imgutil.Image
OrderExtensions() dist.Order
}
Expand Down
Loading