From a2109442d3e128593df7989192a969809573f3a5 Mon Sep 17 00:00:00 2001 From: Timofey Kirillov Date: Mon, 10 Oct 2022 21:32:04 +0300 Subject: [PATCH] feat(staged-dockerfile): refactored container backend dockerfile builder * 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 --- pkg/build/build_phase.go | 4 +- pkg/build/image/dockerfile.go | 6 +- .../base.go | 2 +- .../instruction.go} | 2 +- .../run.go | 15 +- .../build_context/build_context.go | 54 ++++++ pkg/container_backend/buildah_backend.go | 169 +++--------------- .../docker_server_backend.go | 3 +- .../dockerfile_instructions.go | 84 --------- pkg/container_backend/instruction.go | 14 ++ pkg/container_backend/instruction/add.go | 38 ++++ pkg/container_backend/instruction/cmd.go | 32 ++++ pkg/container_backend/instruction/copy.go | 39 ++++ .../instruction/entrypoint.go | 32 ++++ pkg/container_backend/instruction/env.go | 32 ++++ pkg/container_backend/instruction/expose.go | 35 ++++ .../instruction/healthcheck.go | 14 ++ pkg/container_backend/instruction/label.go | 40 +++++ pkg/container_backend/instruction/on_build.go | 32 ++++ pkg/container_backend/instruction/run.go | 35 ++++ pkg/container_backend/instruction/shell.go | 32 ++++ .../instruction/stop_signal.go | 32 ++++ pkg/container_backend/instruction/user.go | 32 ++++ pkg/container_backend/instruction/volume.go | 32 ++++ pkg/container_backend/instruction/workdir.go | 32 ++++ pkg/container_backend/interface.go | 6 +- .../perf_check_container_backend.go | 5 +- .../stage_builder/dockerfile_stage_builder.go | 45 ++--- 28 files changed, 632 insertions(+), 266 deletions(-) rename pkg/build/stage/{dockerfile_instruction => instruction}/base.go (94%) rename pkg/build/stage/{dockerfile_instruction/dockerfile_instruction.go => instruction/instruction.go} (96%) rename pkg/build/stage/{dockerfile_instruction => instruction}/run.go (51%) create mode 100644 pkg/container_backend/build_context/build_context.go delete mode 100644 pkg/container_backend/dockerfile_instructions.go create mode 100644 pkg/container_backend/instruction.go create mode 100644 pkg/container_backend/instruction/add.go create mode 100644 pkg/container_backend/instruction/cmd.go create mode 100644 pkg/container_backend/instruction/copy.go create mode 100644 pkg/container_backend/instruction/entrypoint.go create mode 100644 pkg/container_backend/instruction/env.go create mode 100644 pkg/container_backend/instruction/expose.go create mode 100644 pkg/container_backend/instruction/healthcheck.go create mode 100644 pkg/container_backend/instruction/label.go create mode 100644 pkg/container_backend/instruction/on_build.go create mode 100644 pkg/container_backend/instruction/run.go create mode 100644 pkg/container_backend/instruction/shell.go create mode 100644 pkg/container_backend/instruction/stop_signal.go create mode 100644 pkg/container_backend/instruction/user.go create mode 100644 pkg/container_backend/instruction/volume.go create mode 100644 pkg/container_backend/instruction/workdir.go diff --git a/pkg/build/build_phase.go b/pkg/build/build_phase.go index 176e89befd..3e45719dbd 100644 --- a/pkg/build/build_phase.go +++ b/pkg/build/build_phase.go @@ -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" @@ -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) diff --git a/pkg/build/image/dockerfile.go b/pkg/build/image/dockerfile.go index 9e7de86312..1e0e24e69f 100644 --- a/pkg/build/image/dockerfile.go +++ b/pkg/build/image/dockerfile.go @@ -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" @@ -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, diff --git a/pkg/build/stage/dockerfile_instruction/base.go b/pkg/build/stage/instruction/base.go similarity index 94% rename from pkg/build/stage/dockerfile_instruction/base.go rename to pkg/build/stage/instruction/base.go index 87b3e28b8b..edebe440a1 100644 --- a/pkg/build/stage/dockerfile_instruction/base.go +++ b/pkg/build/stage/instruction/base.go @@ -1,4 +1,4 @@ -package dockerfile_instruction +package instruction import ( "github.com/werf/werf/pkg/build/stage" diff --git a/pkg/build/stage/dockerfile_instruction/dockerfile_instruction.go b/pkg/build/stage/instruction/instruction.go similarity index 96% rename from pkg/build/stage/dockerfile_instruction/dockerfile_instruction.go rename to pkg/build/stage/instruction/instruction.go index a0428d09db..314519624d 100644 --- a/pkg/build/stage/dockerfile_instruction/dockerfile_instruction.go +++ b/pkg/build/stage/instruction/instruction.go @@ -1,4 +1,4 @@ -package dockerfile_instruction +package instruction import "github.com/werf/werf/pkg/build/stage" diff --git a/pkg/build/stage/dockerfile_instruction/run.go b/pkg/build/stage/instruction/run.go similarity index 51% rename from pkg/build/stage/dockerfile_instruction/run.go rename to pkg/build/stage/instruction/run.go index 6149c5d050..d964b85a24 100644 --- a/pkg/build/stage/dockerfile_instruction/run.go +++ b/pkg/build/stage/instruction/run.go @@ -1,4 +1,4 @@ -package dockerfile_instruction +package instruction import ( "context" @@ -6,26 +6,27 @@ import ( "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 } diff --git a/pkg/container_backend/build_context/build_context.go b/pkg/container_backend/build_context/build_context.go new file mode 100644 index 0000000000..556a6882e4 --- /dev/null +++ b/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 +} diff --git a/pkg/container_backend/buildah_backend.go b/pkg/container_backend/buildah_backend.go index cf0c010e07..81e5f61cef 100644 --- a/pkg/container_backend/buildah_backend.go +++ b/pkg/container_backend/buildah_backend.go @@ -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" @@ -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] } @@ -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) @@ -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) } } @@ -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) { @@ -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) } }() @@ -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 { diff --git a/pkg/container_backend/docker_server_backend.go b/pkg/container_backend/docker_server_backend.go index 0b8afef5f0..3051934203 100644 --- a/pkg/container_backend/docker_server_backend.go +++ b/pkg/container_backend/docker_server_backend.go @@ -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" @@ -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") } diff --git a/pkg/container_backend/dockerfile_instructions.go b/pkg/container_backend/dockerfile_instructions.go deleted file mode 100644 index 01a6449cd1..0000000000 --- a/pkg/container_backend/dockerfile_instructions.go +++ /dev/null @@ -1,84 +0,0 @@ -package container_backend - -import "fmt" - -type InstructionEnv struct { - Envs map[string]string -} - -type InstructionCopy struct { - From string - Src []string - Dst string -} - -type InstructionAdd struct { - Src []string - Dst string -} - -type InstructionRun struct { - Command []string -} - -type InstructionEntrypoint struct { - Entrypoint []string -} - -type InstructionCmd struct { - Cmd []string -} - -type InstructionUser struct { - User string -} - -type InstructionWorkdir struct { - Workdir string -} - -type InstructionExpose struct { - Ports []string -} - -type InstructionVolume struct { - Volumes []string -} - -type InstructionOnBuild struct { - Instruction string -} - -type InstructionStopSignal struct { - Signal string -} - -type InstructionShell struct { - Shell []string -} - -type InstructionHealthcheck struct { - Type HealthcheckType - Command string -} - -type HealthcheckType string - -var ( - HealthcheckTypeNone HealthcheckType = "NONE" - HealthcheckTypeCmd HealthcheckType = "CMD" - HealthcheckTypeCmdShell HealthcheckType = "CMD-SHELL" -) - -type InstructionLabel struct { - Labels map[string]string -} - -func (c *InstructionLabel) LabelsAsList() []string { - var labels []string - for k, v := range c.Labels { - labels = append(labels, fmt.Sprintf("%s=%s", k, v)) - } - - return labels -} diff --git a/pkg/container_backend/instruction.go b/pkg/container_backend/instruction.go new file mode 100644 index 0000000000..a5242102cf --- /dev/null +++ b/pkg/container_backend/instruction.go @@ -0,0 +1,14 @@ +package container_backend + +import ( + "context" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type InstructionInterface interface { + Name() string + Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error + UsesBuildContext() bool +} diff --git a/pkg/container_backend/instruction/add.go b/pkg/container_backend/instruction/add.go new file mode 100644 index 0000000000..07812176c9 --- /dev/null +++ b/pkg/container_backend/instruction/add.go @@ -0,0 +1,38 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Add struct { + Src []string + Dst string +} + +func NewAdd(src []string, dst string) *Add { + return &Add{Src: src, Dst: dst} +} + +func (i *Add) UsesBuildContext() bool { + return true +} + +func (i *Add) Name() string { + return "ADD" +} + +func (i *Add) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + contextDir, err := buildContext.GetContextDir(ctx) + if err != nil { + return fmt.Errorf("unable to get build context dir: %w", err) + } + + if err := drv.Add(ctx, containerName, i.Src, i.Dst, buildah.AddOpts{CommonOpts: drvOpts, ContextDir: contextDir}); err != nil { + return fmt.Errorf("error adding %v to %s for container %s: %w", i.Src, i.Dst, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/cmd.go b/pkg/container_backend/instruction/cmd.go new file mode 100644 index 0000000000..cc8752b895 --- /dev/null +++ b/pkg/container_backend/instruction/cmd.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Cmd struct { + Cmd []string +} + +func NewCmd(cmd []string) *Cmd { + return &Cmd{Cmd: cmd} +} + +func (i *Cmd) UsesBuildContext() bool { + return false +} + +func (i *Cmd) Name() string { + return "CMD" +} + +func (i *Cmd) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Cmd: i.Cmd}); err != nil { + return fmt.Errorf("error setting cmd %v for container %s: %w", i.Cmd, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/copy.go b/pkg/container_backend/instruction/copy.go new file mode 100644 index 0000000000..145b7b9d4c --- /dev/null +++ b/pkg/container_backend/instruction/copy.go @@ -0,0 +1,39 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Copy struct { + From string + Src []string + Dst string +} + +func NewCopy(from string, src []string, dst string) *Copy { + return &Copy{From: from, Src: src, Dst: dst} +} + +func (i *Copy) UsesBuildContext() bool { + return true +} + +func (i *Copy) Name() string { + return "COPY" +} + +func (i *Copy) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + contextDir, err := buildContext.GetContextDir(ctx) + if err != nil { + return fmt.Errorf("unable to get build context dir: %w", err) + } + + if err := drv.Copy(ctx, containerName, contextDir, i.Src, i.Dst, buildah.CopyOpts{CommonOpts: drvOpts, From: i.From}); err != nil { + return fmt.Errorf("error copying %v to %s for container %s: %w", i.Src, i.Dst, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/entrypoint.go b/pkg/container_backend/instruction/entrypoint.go new file mode 100644 index 0000000000..7fce77cec8 --- /dev/null +++ b/pkg/container_backend/instruction/entrypoint.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Entrypoint struct { + Entrypoint []string +} + +func NewEntrypoint(entrypoint []string) *Entrypoint { + return &Entrypoint{Entrypoint: entrypoint} +} + +func (i *Entrypoint) UsesBuildContext() bool { + return false +} + +func (i *Entrypoint) Name() string { + return "ENTRYPOINT" +} + +func (i *Entrypoint) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Entrypoint: i.Entrypoint}); err != nil { + return fmt.Errorf("error setting entrypoint %v for container %s: %w", i.Entrypoint, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/env.go b/pkg/container_backend/instruction/env.go new file mode 100644 index 0000000000..e4bd29ef60 --- /dev/null +++ b/pkg/container_backend/instruction/env.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Env struct { + Envs map[string]string +} + +func NewEnv(envs map[string]string) *Env { + return &Env{Envs: envs} +} + +func (i *Env) UsesBuildContext() bool { + return false +} + +func (i *Env) Name() string { + return "ENV" +} + +func (i *Env) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Envs: i.Envs}); err != nil { + return fmt.Errorf("error setting envs %v for container %s: %w", i.Envs, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/expose.go b/pkg/container_backend/instruction/expose.go new file mode 100644 index 0000000000..1cf52cc9f4 --- /dev/null +++ b/pkg/container_backend/instruction/expose.go @@ -0,0 +1,35 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Expose struct { + Ports []string +} + +func NewExpose(ports []string) *Expose { + return &Expose{Ports: ports} +} + +func (i *Expose) UsesBuildContext() bool { + return false +} + +func (i *Expose) Name() string { + return "EXPOSE" +} + +func (i *Expose) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{ + CommonOpts: drvOpts, + Expose: i.Ports, + }); err != nil { + return fmt.Errorf("error setting exposed ports %v for container %s: %w", i.Ports, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/healthcheck.go b/pkg/container_backend/instruction/healthcheck.go new file mode 100644 index 0000000000..d49794ed1c --- /dev/null +++ b/pkg/container_backend/instruction/healthcheck.go @@ -0,0 +1,14 @@ +package instruction + +type Healthcheck struct { + Type HealthcheckType + Command string +} + +type HealthcheckType string + +var ( + HealthcheckTypeNone HealthcheckType = "NONE" + HealthcheckTypeCmd HealthcheckType = "CMD" + HealthcheckTypeCmdShell HealthcheckType = "CMD-SHELL" +) diff --git a/pkg/container_backend/instruction/label.go b/pkg/container_backend/instruction/label.go new file mode 100644 index 0000000000..7cd617622f --- /dev/null +++ b/pkg/container_backend/instruction/label.go @@ -0,0 +1,40 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Label struct { + Labels map[string]string +} + +func NewLabel(labels map[string]string) *Label { + return &Label{Labels: labels} +} + +func (i *Label) UsesBuildContext() bool { + return false +} + +func (i *Label) Name() string { + return "LABEL" +} + +func (i *Label) LabelsAsList() []string { + var labels []string + for k, v := range i.Labels { + labels = append(labels, fmt.Sprintf("%s=%s", k, v)) + } + return labels +} + +func (i *Label) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Labels: i.LabelsAsList()}); err != nil { + return fmt.Errorf("error setting labels %v for container %s: %w", i.LabelsAsList(), containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/on_build.go b/pkg/container_backend/instruction/on_build.go new file mode 100644 index 0000000000..d5d6c4852e --- /dev/null +++ b/pkg/container_backend/instruction/on_build.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type OnBuild struct { + Instruction string +} + +func NewOnBuild(instruction string) *OnBuild { + return &OnBuild{Instruction: instruction} +} + +func (i *OnBuild) UsesBuildContext() bool { + return false +} + +func (i *OnBuild) Name() string { + return "ONBUILD" +} + +func (i *OnBuild) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, OnBuild: i.Instruction}); err != nil { + return fmt.Errorf("error setting onbuild %v for container %s: %w", i.Instruction, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/run.go b/pkg/container_backend/instruction/run.go new file mode 100644 index 0000000000..fa54e2d142 --- /dev/null +++ b/pkg/container_backend/instruction/run.go @@ -0,0 +1,35 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Run struct { + Command []string +} + +func NewRun(command []string) *Run { + return &Run{Command: command} +} + +func (i *Run) UsesBuildContext() bool { + return false +} + +func (i *Run) Name() string { + return "RUN" +} + +func (i *Run) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.RunCommand(ctx, containerName, i.Command, buildah.RunCommandOpts{ + // FIXME(ilya-lesikov): should we suppress or not? + CommonOpts: drvOpts, + }); err != nil { + return fmt.Errorf("error running command %v for container %s: %w", i.Command, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/shell.go b/pkg/container_backend/instruction/shell.go new file mode 100644 index 0000000000..e687e18495 --- /dev/null +++ b/pkg/container_backend/instruction/shell.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Shell struct { + Shell []string +} + +func NewShell(shell []string) *Shell { + return &Shell{Shell: shell} +} + +func (i *Shell) UsesBuildContext() bool { + return false +} + +func (i *Shell) Name() string { + return "SHELL" +} + +func (i *Shell) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Shell: i.Shell}); err != nil { + return fmt.Errorf("error setting shell %v for container %s: %w", i.Shell, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/stop_signal.go b/pkg/container_backend/instruction/stop_signal.go new file mode 100644 index 0000000000..5ebbcb8b39 --- /dev/null +++ b/pkg/container_backend/instruction/stop_signal.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type StopSignal struct { + Signal string +} + +func NewStopSignal(signal string) *StopSignal { + return &StopSignal{Signal: signal} +} + +func (i *StopSignal) UsesBuildContext() bool { + return false +} + +func (i *StopSignal) Name() string { + return "STOPSIGNAL" +} + +func (i *StopSignal) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, StopSignal: i.Signal}); err != nil { + return fmt.Errorf("error setting stop signal %v for container %s: %w", i.Signal, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/user.go b/pkg/container_backend/instruction/user.go new file mode 100644 index 0000000000..b227d05771 --- /dev/null +++ b/pkg/container_backend/instruction/user.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type User struct { + User string +} + +func NewUser(user string) *User { + return &User{User: user} +} + +func (i *User) UsesBuildContext() bool { + return false +} + +func (i *User) Name() string { + return "USER" +} + +func (i *User) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, User: i.User}); err != nil { + return fmt.Errorf("error setting user %s for container %s: %w", i.User, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/volume.go b/pkg/container_backend/instruction/volume.go new file mode 100644 index 0000000000..d083cc71f6 --- /dev/null +++ b/pkg/container_backend/instruction/volume.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Volume struct { + Volumes []string +} + +func NewVolume(volumes []string) *Volume { + return &Volume{Volumes: volumes} +} + +func (i *Volume) UsesBuildContext() bool { + return false +} + +func (i *Volume) Name() string { + return "VOLUME" +} + +func (i *Volume) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Volumes: i.Volumes}); err != nil { + return fmt.Errorf("error setting volumes %v for container %s: %w", i.Volumes, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/instruction/workdir.go b/pkg/container_backend/instruction/workdir.go new file mode 100644 index 0000000000..4d3657c7ae --- /dev/null +++ b/pkg/container_backend/instruction/workdir.go @@ -0,0 +1,32 @@ +package instruction + +import ( + "context" + "fmt" + + "github.com/werf/werf/pkg/buildah" + "github.com/werf/werf/pkg/container_backend/build_context" +) + +type Workdir struct { + Workdir string +} + +func NewWorkdir(workdir string) *Workdir { + return &Workdir{Workdir: workdir} +} + +func (i *Workdir) UsesBuildContext() bool { + return false +} + +func (i *Workdir) Name() string { + return "WORKDIR" +} + +func (i *Workdir) Apply(ctx context.Context, containerName string, drv buildah.Buildah, drvOpts buildah.CommonOpts, buildContext *build_context.BuildContext) error { + if err := drv.Config(ctx, containerName, buildah.ConfigOpts{CommonOpts: drvOpts, Workdir: i.Workdir}); err != nil { + return fmt.Errorf("error setting workdir %s for container %s: %w", i.Workdir, containerName, err) + } + return nil +} diff --git a/pkg/container_backend/interface.go b/pkg/container_backend/interface.go index d8dfd4c5e2..4dfe81f84f 100644 --- a/pkg/container_backend/interface.go +++ b/pkg/container_backend/interface.go @@ -4,6 +4,7 @@ import ( "context" "io" + "github.com/werf/werf/pkg/container_backend/build_context" "github.com/werf/werf/pkg/image" ) @@ -46,7 +47,8 @@ type BuildDockerfileOpts struct { type BuildDockerfileStageOptions struct { CommonOpts - ContextTar io.ReadCloser + BuildContext *build_context.BuildContext + ContextTarReader io.ReadCloser } type ContainerBackend interface { @@ -57,7 +59,7 @@ type ContainerBackend interface { GetImageInfo(ctx context.Context, ref string, opts GetImageInfoOpts) (*image.Info, error) BuildDockerfile(ctx context.Context, dockerfile []byte, opts BuildDockerfileOpts) (string, error) - BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, commands ...any) (string, error) + BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...InstructionInterface) (string, *build_context.BuildContext, error) BuildStapelStage(ctx context.Context, baseImage string, opts BuildStapelStageOptions) (string, error) CalculateDependencyImportChecksum(ctx context.Context, dependencyImport DependencyImportSpec) (string, error) diff --git a/pkg/container_backend/perf_check_container_backend.go b/pkg/container_backend/perf_check_container_backend.go index 70998944e8..a8a1d3a507 100644 --- a/pkg/container_backend/perf_check_container_backend.go +++ b/pkg/container_backend/perf_check_container_backend.go @@ -4,6 +4,7 @@ import ( "context" "github.com/werf/logboek" + "github.com/werf/werf/pkg/container_backend/build_context" "github.com/werf/werf/pkg/image" ) @@ -71,10 +72,10 @@ func (runtime *PerfCheckContainerBackend) BuildDockerfile(ctx context.Context, d return } -func (runtime *PerfCheckContainerBackend) BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...any) (resID string, resErr error) { +func (runtime *PerfCheckContainerBackend) BuildDockerfileStage(ctx context.Context, baseImage string, opts BuildDockerfileStageOptions, instructions ...InstructionInterface) (resID string, resBuildContext *build_context.BuildContext, resErr error) { logboek.Context(ctx).Default().LogProcess("ContainerBackend.BuildDockerfile"). Do(func() { - resID, resErr = runtime.ContainerBackend.BuildDockerfileStage(ctx, baseImage, opts, instructions...) + resID, resBuildContext, resErr = runtime.ContainerBackend.BuildDockerfileStage(ctx, baseImage, opts, instructions...) }) return } diff --git a/pkg/container_backend/stage_builder/dockerfile_stage_builder.go b/pkg/container_backend/stage_builder/dockerfile_stage_builder.go index c04a719d41..cf3e60ba39 100644 --- a/pkg/container_backend/stage_builder/dockerfile_stage_builder.go +++ b/pkg/container_backend/stage_builder/dockerfile_stage_builder.go @@ -3,24 +3,28 @@ package stage_builder import ( "context" "fmt" - "io" "github.com/werf/werf/pkg/container_backend" + "github.com/werf/werf/pkg/container_backend/build_context" ) type DockerfileStageBuilderInterface interface { - AppendPreCommands(commands ...any) DockerfileStageBuilderInterface - AppendMainCommands(commands ...any) DockerfileStageBuilderInterface - AppendPostCommands(commands ...any) DockerfileStageBuilderInterface - SetContext(contextTar io.ReadCloser) DockerfileStageBuilderInterface + DockerfileStageIntructionBuilderInterface + SetBuildContext(buildContext *build_context.BuildContext) DockerfileStageBuilderInterface Build(ctx context.Context, opts container_backend.BuildOptions) error } +type DockerfileStageIntructionBuilderInterface interface { + AppendPreInstruction(i container_backend.InstructionInterface) DockerfileStageBuilderInterface + AppendInstruction(i container_backend.InstructionInterface) DockerfileStageBuilderInterface + AppendPostInstruction(i container_backend.InstructionInterface) DockerfileStageBuilderInterface +} + type DockerfileStageBuilder struct { - preCommands []any - mainCommands []any - postCommands []any - contextTar io.ReadCloser + preInstructions []container_backend.InstructionInterface + instructions []container_backend.InstructionInterface + postInstructions []container_backend.InstructionInterface + buildContext *build_context.BuildContext baseImage string resultImage container_backend.ImageInterface @@ -35,33 +39,34 @@ func NewDockerfileStageBuilder(containerBackend container_backend.ContainerBacke } } -func (b *DockerfileStageBuilder) AppendPreCommands(commands ...any) DockerfileStageBuilderInterface { - b.preCommands = append(b.preCommands, commands...) +func (b *DockerfileStageBuilder) AppendPreInstruction(i container_backend.InstructionInterface) DockerfileStageBuilderInterface { + b.preInstructions = append(b.preInstructions, i) return b } -func (b *DockerfileStageBuilder) AppendMainCommands(commands ...any) DockerfileStageBuilderInterface { - b.mainCommands = append(b.mainCommands, commands...) +func (b *DockerfileStageBuilder) AppendInstruction(i container_backend.InstructionInterface) DockerfileStageBuilderInterface { + b.instructions = append(b.instructions, i) return b } -func (b *DockerfileStageBuilder) AppendPostCommands(commands ...any) DockerfileStageBuilderInterface { - b.postCommands = append(b.postCommands, commands...) +func (b *DockerfileStageBuilder) AppendPostInstruction(i container_backend.InstructionInterface) DockerfileStageBuilderInterface { + b.postInstructions = append(b.postInstructions, i) return b } -func (b *DockerfileStageBuilder) SetContext(contextTar io.ReadCloser) DockerfileStageBuilderInterface { - b.contextTar = contextTar +func (b *DockerfileStageBuilder) SetBuildContext(buildContext *build_context.BuildContext) DockerfileStageBuilderInterface { + b.buildContext = buildContext return b } func (b *DockerfileStageBuilder) Build(ctx context.Context, opts container_backend.BuildOptions) error { - commands := append(append(b.preCommands, b.mainCommands...), b.postCommands...) - backendOpts := container_backend.BuildDockerfileStageOptions{ContextTar: b.contextTar} + instructions := append(append(b.preInstructions, b.instructions...), b.postInstructions...) + backendOpts := container_backend.BuildDockerfileStageOptions{BuildContext: b.buildContext} - if builtID, err := b.containerBackend.BuildDockerfileStage(ctx, b.baseImage, backendOpts, commands...); err != nil { + if builtID, buildContext, err := b.containerBackend.BuildDockerfileStage(ctx, b.baseImage, backendOpts, instructions...); err != nil { return fmt.Errorf("error building dockerfile stage: %w", err) } else { + b.buildContext = buildContext b.resultImage.SetBuiltID(builtID) }