Skip to content

Commit

Permalink
feat(staged-dockerfile): refactored container backend dockerfile builder
Browse files Browse the repository at this point in the history
* Unified interface of instructions.
* No switch-case by instruction type, use polymorphism instead.
* Introduced BuildContext to keep unpacked context tar between executing multiple instructions.
* Renaming of primitives and some packages.

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Oct 10, 2022
1 parent 62b2181 commit a210944
Show file tree
Hide file tree
Showing 28 changed files with 632 additions and 266 deletions.
4 changes: 2 additions & 2 deletions pkg/build/build_phase.go
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/werf/werf/pkg/build/image"
"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/container_backend"
backend_instruction "github.com/werf/werf/pkg/container_backend/instruction"
"github.com/werf/werf/pkg/docker_registry"
"github.com/werf/werf/pkg/git_repo"
imagePkg "github.com/werf/werf/pkg/image"
Expand Down Expand Up @@ -676,8 +677,7 @@ func (phase *BuildPhase) prepareStageInstructions(ctx context.Context, img *imag
return stageImage.Builder.DockerfileBuilder().Cleanup(ctx)
})
} else {
stageImage.Builder.DockerfileStageBuilder().AppendPostCommands(&container_backend.InstructionLabel{Labels: serviceLabels})
// staged dockerfile
stageImage.Builder.DockerfileStageBuilder().AppendPostInstruction(backend_instruction.NewLabel(serviceLabels))
}

err := stg.PrepareImage(ctx, phase.Conveyor, phase.Conveyor.ContainerBackend, phase.StagesIterator.GetPrevBuiltImage(img, stg), stageImage)
Expand Down
6 changes: 3 additions & 3 deletions pkg/build/image/dockerfile.go
Expand Up @@ -12,9 +12,9 @@ import (

"github.com/werf/logboek"
"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/build/stage/dockerfile_instruction"
stage_instruction "github.com/werf/werf/pkg/build/stage/instruction"
"github.com/werf/werf/pkg/config"
"github.com/werf/werf/pkg/container_backend"
backend_instruction "github.com/werf/werf/pkg/container_backend/instruction"
"github.com/werf/werf/pkg/dockerfile"
"github.com/werf/werf/pkg/path_matcher"
"github.com/werf/werf/pkg/util"
Expand Down Expand Up @@ -82,7 +82,7 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,
return nil, fmt.Errorf("unable to create image %q: %w", "test", err)
}

img.stages = append(img.stages, dockerfile_instruction.NewRun(&container_backend.InstructionRun{Command: []string{"ls", "/"}}, nil, false, &stage.BaseStageOptions{
img.stages = append(img.stages, stage_instruction.NewRun(backend_instruction.NewRun([]string{"ls", "/"}), nil, false, &stage.BaseStageOptions{
ImageName: img.Name,
ImageTmpDir: img.TmpDir,
ContainerWerfDir: img.ContainerWerfDir,
Expand Down
@@ -1,4 +1,4 @@
package dockerfile_instruction
package instruction

import (
"github.com/werf/werf/pkg/build/stage"
Expand Down
@@ -1,4 +1,4 @@
package dockerfile_instruction
package instruction

import "github.com/werf/werf/pkg/build/stage"

Expand Down
@@ -1,31 +1,32 @@
package dockerfile_instruction
package instruction

import (
"context"

"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/config"
"github.com/werf/werf/pkg/container_backend"
backend_instruction "github.com/werf/werf/pkg/container_backend/instruction"
"github.com/werf/werf/pkg/util"
)

type Run struct {
*Base
instruction *container_backend.InstructionRun
instruction *backend_instruction.Run
}

func NewRun(instruction *container_backend.InstructionRun, dependencies []*config.Dependency, hasPrevStage bool, opts *stage.BaseStageOptions) *Run {
func NewRun(i *backend_instruction.Run, dependencies []*config.Dependency, hasPrevStage bool, opts *stage.BaseStageOptions) *Run {
return &Run{
Base: NewBase("RUN", dependencies, hasPrevStage, opts),
instruction: instruction,
Base: NewBase(InstructionRun, dependencies, hasPrevStage, opts),
instruction: i,
}
}

func (stage *Run) GetDependencies(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevImage, prevBuiltImage *stage.StageImage) (string, error) {
return util.Sha256Hash(stage.instruction.Command...), nil
return util.Sha256Hash(append([]string{string(InstructionRun)}, stage.instruction.Command...)...), nil
}

func (stage *Run) PrepareImage(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevBuiltImage, stageImage *stage.StageImage) error {
stageImage.Builder.DockerfileStageBuilder().AppendMainCommands(stage.instruction)
stageImage.Builder.DockerfileStageBuilder().AppendInstruction(stage.instruction)
return nil
}
54 changes: 54 additions & 0 deletions pkg/container_backend/build_context/build_context.go
@@ -0,0 +1,54 @@
package build_context

import (
"context"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/werf/werf/pkg/util"
)

type BuildContext struct {
ContextTarReader io.ReadCloser
TmpDir string

contextTmpDir string
}

func NewBuildContext(tmpDir string, contextTarReader io.ReadCloser) *BuildContext {
return &BuildContext{TmpDir: tmpDir, ContextTarReader: contextTarReader}
}

func (c *BuildContext) GetContextDir(ctx context.Context) (string, error) {
if c.contextTmpDir != "" {
return c.contextTmpDir, nil
}

contextTmpDir, err := ioutil.TempDir(c.TmpDir, "context")
if err != nil {
return "", fmt.Errorf("unable to create context tmp dir: %w", err)
}

if err := util.ExtractTar(c.ContextTarReader, contextTmpDir, util.ExtractTarOptions{}); err != nil {
return "", fmt.Errorf("unable to extract context tar to tmp context dir: %w", err)
}
if err := c.ContextTarReader.Close(); err != nil {
return "", fmt.Errorf("error closing context tar: %w", err)
}

c.contextTmpDir = contextTmpDir

return c.contextTmpDir, nil
}

func (c *BuildContext) Terminate() error {
if c.contextTmpDir == "" {
return nil
}
if err := os.RemoveAll(c.contextTmpDir); err != nil {
return fmt.Errorf("unable to remove dir %q: %w", c.contextTmpDir, err)
}
return nil
}
169 changes: 26 additions & 143 deletions pkg/container_backend/buildah_backend.go
Expand Up @@ -20,6 +20,7 @@ import (
copyrec "github.com/werf/copy-recurse"
"github.com/werf/logboek"
"github.com/werf/werf/pkg/buildah"
"github.com/werf/werf/pkg/container_backend/build_context"
"github.com/werf/werf/pkg/image"
"github.com/werf/werf/pkg/path_matcher"
"github.com/werf/werf/pkg/util"
Expand Down Expand Up @@ -422,10 +423,22 @@ func (runtime *BuildahBackend) applyDependenciesImports(ctx context.Context, con
return nil
}

func (runtime *BuildahBackend) BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...any) (string, error) {
func (runtime *BuildahBackend) BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...InstructionInterface) (string, *build_context.BuildContext, error) {
buildContext := opts.BuildContext
if buildContext == nil {
for _, instruction := range instructions {
if instruction.UsesBuildContext() {
if opts.ContextTarReader == nil {
panic(fmt.Sprintf("opts.ContextTarReader needed for %q instruction", instruction.Name()))
}
buildContext = build_context.NewBuildContext(runtime.TmpDir, opts.ContextTarReader)
}
}
}

var container *containerDesc
if c, err := runtime.createContainers(ctx, []string{baseImage}); err != nil {
return "", err
return "", nil, err
} else {
container = c[0]
}
Expand All @@ -439,7 +452,7 @@ func (runtime *BuildahBackend) BuildDockerfileStage(ctx context.Context, baseIma

logboek.Context(ctx).Debug().LogF("Mounting build container %s\n", container.Name)
if err := runtime.mountContainers(ctx, []*containerDesc{container}); err != nil {
return "", fmt.Errorf("unable to mount build container %s: %w", container.Name, err)
return "", buildContext, fmt.Errorf("unable to mount build container %s: %w", container.Name, err)
}
defer func() {
logboek.Context(ctx).Debug().LogF("Unmounting build container %s\n", container.Name)
Expand All @@ -448,127 +461,11 @@ func (runtime *BuildahBackend) BuildDockerfileStage(ctx context.Context, baseIma
}
}()

var contextTmpDir string
if opts.ContextTar != nil {
var err error

// TODO(staged-dockerfile): build-context object param
contextTmpDir, err = runtime.ExtractContext(opts.ContextTar)
if err != nil {
return "", fmt.Errorf("error extracting context: %w", err)
}

defer func() {
if err := os.RemoveAll(contextTmpDir); err != nil {
logboek.Context(ctx).Error().LogF("ERROR: unable to remove temporary context dir %s: %s\n", contextTmpDir, err)
}
}()
}

logboek.Context(ctx).Debug().LogF("Executing commands for build container %s: %#v\n", container.Name, instructions)

for _, i := range instructions {
switch instruction := i.(type) {
case *InstructionLabel:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Labels: instruction.LabelsAsList(),
}); err != nil {
return "", fmt.Errorf("error setting labels %v for container %s: %w", instruction.LabelsAsList(), container.Name, err)
}
case *InstructionExpose:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Expose: instruction.Ports,
}); err != nil {
return "", fmt.Errorf("error setting exposed ports %v for container %s: %w", instruction.Ports, container.Name, err)
}
case *InstructionVolume:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Volumes: instruction.Volumes,
}); err != nil {
return "", fmt.Errorf("error setting volumes %v for container %s: %w", instruction.Volumes, container.Name, err)
}
case *InstructionOnBuild:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
OnBuild: instruction.Instruction,
}); err != nil {
return "", fmt.Errorf("error setting onbuild %v for container %s: %w", instruction.Instruction, container.Name, err)
}
case *InstructionStopSignal:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
StopSignal: instruction.Signal,
}); err != nil {
return "", fmt.Errorf("error setting stop signal %v for container %s: %w", instruction.Signal, container.Name, err)
}
case *InstructionShell:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Shell: instruction.Shell,
}); err != nil {
return "", fmt.Errorf("error setting shell %v for container %s: %w", instruction.Shell, container.Name, err)
}
case *InstructionEnv:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Envs: instruction.Envs,
}); err != nil {
return "", fmt.Errorf("error setting envs %v for container %s: %w", instruction.Envs, container.Name, err)
}
case *InstructionRun:
if err := runtime.buildah.RunCommand(ctx, container.Name, instruction.Command, buildah.RunCommandOpts{
// FIXME(ilya-lesikov): should we suppress or not?
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
}); err != nil {
return "", fmt.Errorf("error running command %v for container %s: %w", instruction.Command, container.Name, err)
}
case *InstructionCopy:
if err := runtime.buildah.Copy(ctx, container.Name, contextTmpDir, instruction.Src, instruction.Dst, buildah.CopyOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
From: instruction.From,
}); err != nil {
return "", fmt.Errorf("error copying %v to %s for container %s: %w", instruction.Src, instruction.Dst, container.Name, err)
}
case *InstructionAdd:
if err := runtime.buildah.Add(ctx, container.Name, instruction.Src, instruction.Dst, buildah.AddOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
ContextDir: contextTmpDir,
}); err != nil {
return "", fmt.Errorf("error adding %v to %s for container %s: %w", instruction.Src, instruction.Dst, container.Name, err)
}
case *InstructionUser:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
User: instruction.User,
}); err != nil {
return "", fmt.Errorf("error setting user %s for container %s: %w", instruction.User, container.Name, err)
}
case *InstructionWorkdir:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Workdir: instruction.Workdir,
}); err != nil {
return "", fmt.Errorf("error setting workdir %s for container %s: %w", instruction.Workdir, container.Name, err)
}
case *InstructionEntrypoint:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Entrypoint: instruction.Entrypoint,
}); err != nil {
return "", fmt.Errorf("error setting entrypoint %v for container %s: %w", instruction.Entrypoint, container.Name, err)
}
case *InstructionCmd:
if err := runtime.buildah.Config(ctx, container.Name, buildah.ConfigOpts{
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
Cmd: instruction.Cmd,
}); err != nil {
return "", fmt.Errorf("error setting cmd %v for container %s: %w", instruction.Cmd, container.Name, err)
}
default:
panic(fmt.Sprintf("invalid command type: %T", i))
for _, instruction := range instructions {
if err := instruction.Apply(ctx, container.Name, runtime.buildah, runtime.getBuildahCommonOpts(ctx, false), buildContext); err != nil {
return "", buildContext, fmt.Errorf("unable to apply instruction %s: %w", instruction.Name(), err)
}
}

Expand All @@ -577,10 +474,10 @@ func (runtime *BuildahBackend) BuildDockerfileStage(ctx context.Context, baseIma
CommonOpts: runtime.getBuildahCommonOpts(ctx, true),
})
if err != nil {
return "", fmt.Errorf("error committing container %s: %w", container.Name, err)
return "", buildContext, fmt.Errorf("error committing container %s: %w", container.Name, err)
}

return imageID, nil
return imageID, buildContext, nil
}

func (runtime *BuildahBackend) BuildStapelStage(ctx context.Context, baseImage string, opts BuildStapelStageOptions) (string, error) {
Expand Down Expand Up @@ -728,13 +625,14 @@ func (runtime *BuildahBackend) BuildDockerfile(ctx context.Context, dockerfileCo
buildArgs[argParts[0]] = argParts[1]
}

contextTmpDir, err := runtime.ExtractContext(opts.ContextTar)
buildContext := build_context.NewBuildContext(runtime.TmpDir, opts.ContextTar)
contextTmpDir, err := buildContext.GetContextDir(ctx)
if err != nil {
return "", fmt.Errorf("error extracting context: %w", err)
return "", fmt.Errorf("unable to get context dir: %w", err)
}
defer func() {
if err := os.RemoveAll(contextTmpDir); err != nil {
logboek.Context(ctx).Error().LogF("ERROR: unable to remove temporary context dir %s: %s\n", contextTmpDir, err)
if err := buildContext.Terminate(); err != nil {
logboek.Context(ctx).Error().LogF("ERROR: unable to terminate dockerfile building context: %s\n", err)
}
}()

Expand Down Expand Up @@ -876,21 +774,6 @@ func (runtime *BuildahBackend) RemoveHostDirs(ctx context.Context, mountDir stri
})
}

func (runtime *BuildahBackend) ExtractContext(contextTar io.Reader) (string, error) {
contextTmpDir, err := ioutil.TempDir(runtime.TmpDir, "context")
if err != nil {
return "", fmt.Errorf("unable to create context tmp dir: %w", err)
}

if contextTar != nil {
if err := util.ExtractTar(contextTar, contextTmpDir, util.ExtractTarOptions{}); err != nil {
return "", fmt.Errorf("unable to extract context tar to tmp context dir: %w", err)
}
}

return contextTmpDir, nil
}

func parseVolume(volume string) (string, string, error) {
volumeParts := strings.SplitN(volume, ":", 2)
if len(volumeParts) != 2 {
Expand Down
3 changes: 2 additions & 1 deletion pkg/container_backend/docker_server_backend.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/google/uuid"

"github.com/werf/logboek"
"github.com/werf/werf/pkg/container_backend/build_context"
"github.com/werf/werf/pkg/docker"
"github.com/werf/werf/pkg/image"
"github.com/werf/werf/pkg/util"
Expand Down Expand Up @@ -79,7 +80,7 @@ func (runtime *DockerServerBackend) BuildDockerfile(ctx context.Context, _ []byt
return tempID, docker.CliBuild_LiveOutputWithCustomIn(ctx, opts.ContextTar, cliArgs...)
}

func (runtime *DockerServerBackend) BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...any) (string, error) {
func (runtime *DockerServerBackend) BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...InstructionInterface) (string, *build_context.BuildContext, error) {
panic("not implemented")
}

Expand Down

0 comments on commit a210944

Please sign in to comment.