Skip to content

Commit

Permalink
feat(staged-dockerfile): refine ADD instruction digest calculation
Browse files Browse the repository at this point in the history
* Add simple unit test for add instruction digest.
* Build context files checksum calculation stubbed implementation.

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Oct 26, 2022
1 parent 45e7382 commit 2ef3d11
Show file tree
Hide file tree
Showing 32 changed files with 391 additions and 93 deletions.
2 changes: 1 addition & 1 deletion pkg/build/build_phase.go
Expand Up @@ -696,7 +696,7 @@ func (phase *BuildPhase) prepareStageInstructions(ctx context.Context, img *imag
})
} else {
stageImage.Builder.DockerfileStageBuilder().SetBuildContextArchive(phase.buildContextArchive)
stageImage.Builder.DockerfileStageBuilder().AppendPostInstruction(backend_instruction.NewLabel(*dockerfile_instruction.NewLabel(serviceLabels)))
stageImage.Builder.DockerfileStageBuilder().AppendPostInstruction(backend_instruction.NewLabel(*dockerfile_instruction.NewLabel("", serviceLabels)))
}

err := stg.PrepareImage(ctx, phase.Conveyor, phase.Conveyor.ContainerBackend, phase.StagesIterator.GetPrevBuiltImage(img, stg), stageImage, phase.buildContextArchive)
Expand Down
11 changes: 11 additions & 0 deletions pkg/build/image/build_context_archive.go
Expand Up @@ -105,3 +105,14 @@ func (a *BuildContextArchive) CleanupExtractedDir(ctx context.Context) {
logboek.Context(ctx).Warn().LogF("WARNING: unable to remove extracted context dir %q: %s", a.extractionDir, err)
}
}

func (a *BuildContextArchive) CalculatePathsChecksum(ctx context.Context, paths []string) (string, error) {
dir, err := a.ExtractOrGetExtractedDir(ctx)
if err != nil {
return "", fmt.Errorf("unable to access context directory: %w", err)
}

_ = dir

return "", nil
}
14 changes: 13 additions & 1 deletion pkg/build/stage/dependencies_test.go
Expand Up @@ -16,7 +16,7 @@ var _ = Describe("DependenciesStage", func() {
ctx := context.Background()

conveyor := NewConveyorStubForDependencies(NewGiterminismManagerStub(NewLocalGitRepoStub("9d8059842b6fde712c58315ca0ab4713d90761c0"), NewGiterminismInspectorStub()), data.Dependencies)
containerBackend := NewContainerBackendMock()
containerBackend := NewContainerBackendStub()

stage := newDependenciesStage(nil, GetConfigDependencies(data.Dependencies), "example-stage", &BaseStageOptions{
ImageName: "example-image",
Expand Down Expand Up @@ -270,3 +270,15 @@ var _ = Describe("getDependencies helper", func() {
})
})
})

func NewConveyorStubForDependencies(giterminismManager *GiterminismManagerStub, dependencies []*TestDependency) *ConveyorStub {
lastStageImageNameByImageName := make(map[string]string)
lastStageImageIDByImageName := make(map[string]string)

for _, dep := range dependencies {
lastStageImageNameByImageName[dep.ImageName] = dep.GetDockerImageName()
lastStageImageIDByImageName[dep.ImageName] = dep.DockerImageID
}

return NewConveyorStub(giterminismManager, lastStageImageNameByImageName, lastStageImageIDByImageName)
}
20 changes: 2 additions & 18 deletions pkg/build/stage/full_dockerfile.go
Expand Up @@ -6,7 +6,6 @@ import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
Expand All @@ -21,6 +20,7 @@ import (
"github.com/werf/werf/pkg/container_backend/stage_builder"
"github.com/werf/werf/pkg/context_manager"
"github.com/werf/werf/pkg/docker_registry"
"github.com/werf/werf/pkg/dockerfile"
"github.com/werf/werf/pkg/git_repo"
"github.com/werf/werf/pkg/giterminism_manager"
"github.com/werf/werf/pkg/image"
Expand Down Expand Up @@ -758,7 +758,7 @@ func (s *FullDockerfileStage) calculateFilesChecksum(ctx context.Context, giterm
var checksum string
var err error

normalizedWildcards := normalizeCopyAddSources(wildcards)
normalizedWildcards := dockerfile.NormalizeCopyAddSourcesForPathMatcher(wildcards)

logProcess := logboek.Context(ctx).Debug().LogProcess("Calculating files checksum (%v) from local git repo", normalizedWildcards)
logProcess.Start()
Expand Down Expand Up @@ -835,22 +835,6 @@ func (s *FullDockerfileStage) calculateFilesChecksumWithGit(ctx context.Context,
return util.Sha256Hash(lsTreeResultChecksum), nil
}

func normalizeCopyAddSources(wildcards []string) []string {
var result []string
for _, wildcard := range wildcards {
normalizedWildcard := path.Clean(wildcard)
if normalizedWildcard == "/" {
normalizedWildcard = "."
} else if strings.HasPrefix(normalizedWildcard, "/") {
normalizedWildcard = strings.TrimPrefix(normalizedWildcard, "/")
}

result = append(result, normalizedWildcard)
}

return result
}

func dockerfileStageDependenciesDebug() bool {
return os.Getenv("WERF_DEBUG_DOCKERFILE_STAGE_DEPENDENCIES") == "1"
}
6 changes: 3 additions & 3 deletions pkg/build/stage/full_dockerfile_test.go
Expand Up @@ -65,7 +65,7 @@ var _ = Describe("FullDockerfileStage", func() {
ctx := context.Background()

conveyor := NewConveyorStubForDependencies(NewGiterminismManagerStub(NewLocalGitRepoStub("9d8059842b6fde712c58315ca0ab4713d90761c0"), NewGiterminismInspectorStub()), data.TestDependencies.Dependencies)
containerBackend := NewContainerBackendMock()
containerBackend := NewContainerBackendStub()

dockerStages, dockerMetaArgs := testDockerfileToDockerStages(data.DockerfileData)

Expand Down Expand Up @@ -295,7 +295,7 @@ RUN echo hello

stage := newTestFullDockerfileStage(dockerfile, "", nil, dockerStages, dockerMetaArgs, nil)

containerBackend := NewContainerBackendMock()
containerBackend := NewContainerBackendStub()

dockerRegistry := NewDockerRegistryApiStub()

Expand Down Expand Up @@ -328,7 +328,7 @@ RUN --mount=type=bind,from=build,source=/usr/local/test_project/dist,target=/usr

stage := newTestFullDockerfileStage(dockerfile, "", nil, dockerStages, dockerMetaArgs, nil)

containerBackend := NewContainerBackendMock()
containerBackend := NewContainerBackendStub()

img := NewLegacyImageStub()
stageBuilder := stage_builder.NewStageBuilder(containerBackend, "", img)
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/git_mapping_test.go
Expand Up @@ -127,7 +127,7 @@ var _ = Describe("GitMapping", func() {
func(data BaseCommitForPrevBuiltImageCheckData, checkResultFunc func(string, BaseCommitForPrevBuiltImageCheckData)) {
ctx := context.Background()
c := NewConveyorStub(stage.VirtualMergeOptions{VirtualMerge: data.IsCurrentCommitVirtualMerge})
containerBackend := stage.NewContainerBackendMock()
containerBackend := stage.NewContainerBackendStub()

gitRepo := NewGitRepoStub("own", true, data.CurrentCommit)
gitMapping.SetGitRepo(gitRepo)
Expand Down
15 changes: 15 additions & 0 deletions pkg/build/stage/instruction/add.go
Expand Up @@ -2,6 +2,7 @@ package instruction

import (
"context"
"fmt"

"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/config"
Expand All @@ -22,10 +23,24 @@ func NewAdd(name stage.StageName, i *dockerfile.DockerfileStageInstruction[*dock

func (stage *Add) GetDependencies(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevImage, prevBuiltImage *stage.StageImage, buildContextArchive container_backend.BuildContextArchiver) (string, error) {
var args []string

args = append(args, stage.instruction.Data.Name())
args = append(args, stage.instruction.Data.Raw)
args = append(args, stage.instruction.Data.Src...)
args = append(args, stage.instruction.Data.Dst)
args = append(args, stage.instruction.Data.Chown)
args = append(args, stage.instruction.Data.Chmod)

// TODO(staged-dockerfile): support http src and --checksum option: https://docs.docker.com/engine/reference/builder/#verifying-a-remote-file-checksum-add---checksumchecksum-http-src-dest
// TODO(staged-dockerfile): support git ref: https://docs.docker.com/engine/reference/builder/#adding-a-git-repository-add-git-ref-dir
// TODO(staged-dockerfile): support --keep-git-dir for git: https://docs.docker.com/engine/reference/builder/#adding-a-git-repository-add-git-ref-dir
// TODO(staged-dockerfile): support --link

pathsChecksum, err := buildContextArchive.CalculatePathsChecksum(ctx, stage.instruction.Data.Src)
if err != nil {
return "", fmt.Errorf("unable to calculate build context paths checksum: %w", err)
}
args = append(args, fmt.Sprintf("src-checksum=%s", pathsChecksum))

return util.Sha256Hash(args...), nil
}
167 changes: 167 additions & 0 deletions pkg/build/stage/instruction/add_test.go
@@ -0,0 +1,167 @@
package instruction

import (
"context"
"fmt"
"strings"

"github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/container_backend"
"github.com/werf/werf/pkg/dockerfile"
dockerfile_instruction "github.com/werf/werf/pkg/dockerfile/instruction"
"github.com/werf/werf/pkg/util"
)

var (
Entry = ginkgo.Entry
DescribeTable = ginkgo.DescribeTable
)

var _ = DescribeTable("calculating digest and configuring builder",
func(data *TestData) {
ctx := context.Background()

digest, err := data.Stage.GetDependencies(ctx, data.Conveyor, data.ContainerBackend, nil, data.StageImage, data.BuildContext)
Expect(err).To(Succeed())

fmt.Printf("calculated digest: %s\n", digest)
fmt.Printf("expected digest: %s\n", data.ExpectedDigest)

Expect(digest).To(Equal(data.ExpectedDigest))
},

Entry("ADD basic", NewTestData(
NewAdd("ADD", dockerfile.NewDockerfileStageInstruction(
dockerfile_instruction.NewAdd("", []string{"src"}, "/app", "1000:1000", "")), nil, false,
&stage.BaseStageOptions{
ImageName: "example-image",
ProjectName: "example-project",
},
),
"bf369f0c1d046108f1a7314b086cefe109b8edc01878033eccf41f1ff47e2c73",
[]*FileData{
{Name: "src/main/java/worker/Worker.java", Data: []byte(`package worker;`)},
{Name: "src/Worker/Program.cs", Data: []byte(`namespace Worker {}`)},
},
)),

Entry("ADD with changed chown", NewTestData(
NewAdd("ADD", dockerfile.NewDockerfileStageInstruction(
dockerfile_instruction.NewAdd("", []string{"src"}, "/app", "1000:1001", "")), nil, false,
&stage.BaseStageOptions{
ImageName: "example-image",
ProjectName: "example-project",
},
),
"2a9fc5cdddb5443ff19bcc6366a3ad42f2ac8e86a3779dadf0ac34857b3928cb",
[]*FileData{
{Name: "src/main/java/worker/Worker.java", Data: []byte(`package worker;`)},
{Name: "src/Worker/Program.cs", Data: []byte(`namespace Worker {}`)},
{Name: "pom.xml", Data: []byte(`<?xml version="1.0" encoding="UTF-8"?>`)},
},
)),

Entry("ADD with changed chmod", NewTestData(
NewAdd("ADD", dockerfile.NewDockerfileStageInstruction(
dockerfile_instruction.NewAdd("", []string{"src"}, "/app", "1000:1001", "0777")), nil, false,
&stage.BaseStageOptions{
ImageName: "example-image",
ProjectName: "example-project",
},
),
"e2c17b3d62eb61470f16544a4ea42b66b37326dc5f10d61b7a9ebab04e53e7a7",
[]*FileData{
{Name: "src/main/java/worker/Worker.java", Data: []byte(`package worker;`)},
{Name: "src/Worker/Program.cs", Data: []byte(`namespace Worker {}`)},
{Name: "pom.xml", Data: []byte(`<?xml version="1.0" encoding="UTF-8"?>`)},
},
)),

Entry("ADD with changed sources paths", NewTestData(
NewAdd("ADD", dockerfile.NewDockerfileStageInstruction(
dockerfile_instruction.NewAdd("", []string{"src", "pom.xml"}, "/app", "1000:1000", "0777")), nil, false,
&stage.BaseStageOptions{
ImageName: "example-image",
ProjectName: "example-project",
},
),
"cc635522d684dd86345d3a074722ecaf82fd33c33912c6667caea62177ba2540",
[]*FileData{
{Name: "src/main/java/worker/Worker.java", Data: []byte(`package worker;`)},
{Name: "src/Worker/Program.cs", Data: []byte(`namespace Worker {}`)},
{Name: "pom.xml", Data: []byte(`<?xml version="1.0" encoding="UTF-8"?>`)},
},
)),

Entry("ADD with changed source files", NewTestData(
NewAdd("ADD", dockerfile.NewDockerfileStageInstruction(
dockerfile_instruction.NewAdd("", []string{"src", "pom.xml"}, "/app", "1000:1000", "0777")), nil, false,
&stage.BaseStageOptions{
ImageName: "example-image",
ProjectName: "example-project",
},
),
"15add63c912f20138ed0eb4a2bb906bc3c3fa70d0d0ac26c7871363484a84c94",
[]*FileData{
{Name: "src/main/java/worker/Worker.java", Data: []byte(`package worker2;`)},
{Name: "src/Worker/Program.cs", Data: []byte(`namespace Worker2 {}`)},
{Name: "pom.xml", Data: []byte(`<?xml version="1.0" encoding="UTF-8"?>`)},
},
)),

Entry("ADD with changed destination path", NewTestData(
NewAdd("ADD", dockerfile.NewDockerfileStageInstruction(
dockerfile_instruction.NewAdd("", []string{"src", "pom.xml"}, "/app2", "1000:1000", "0777")), nil, false,
&stage.BaseStageOptions{
ImageName: "example-image",
ProjectName: "example-project",
},
),
"15b2f0b0d9b569a55f0af7c8326e2d5d8792f5a7df1f8fd4e612034f74a015e1",
[]*FileData{
{Name: "src/main/java/worker/Worker.java", Data: []byte(`package worker2;`)},
{Name: "src/Worker/Program.cs", Data: []byte(`namespace Worker2 {}`)},
{Name: "pom.xml", Data: []byte(`<?xml version="1.0" encoding="UTF-8"?>`)},
},
)),
)

type BuildContextStub struct {
container_backend.BuildContextArchiver

Files []*FileData
}

type FileData struct {
Name string
Data []byte
}

func NewBuildContextStub(files []*FileData) *BuildContextStub {
return &BuildContextStub{Files: files}
}

func (buildContext *BuildContextStub) CalculatePathsChecksum(ctx context.Context, paths []string) (string, error) {
var args []string

for _, p := range paths {
for _, f := range buildContext.Files {
if f.Name == p {
args = append(args, string(f.Data))
break
}
}

for _, f := range buildContext.Files {
if strings.HasPrefix(f.Name, p) {
args = append(args, string(f.Data))
break
}
}
}

return util.Sha256Hash(args...), nil
}
43 changes: 43 additions & 0 deletions pkg/build/stage/instruction/stubs_test.go
@@ -0,0 +1,43 @@
package instruction

import (
"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/container_backend/stage_builder"
)

type TestData struct {
Stage stage.Interface
ExpectedDigest string

Conveyor *stage.ConveyorStub
ContainerBackend *stage.ContainerBackendStub
Image *stage.LegacyImageStub
StageBuilder *stage_builder.StageBuilder
StageImage *stage.StageImage
BuildContext *BuildContextStub
}

func NewTestData(stg stage.Interface, expectedDigest string, files []*FileData) *TestData {
conveyor := stage.NewConveyorStub(stage.NewGiterminismManagerStub(stage.NewLocalGitRepoStub("9d8059842b6fde712c58315ca0ab4713d90761c0"), stage.NewGiterminismInspectorStub()), nil, nil)
containerBackend := stage.NewContainerBackendStub()

img := stage.NewLegacyImageStub()
stageBuilder := stage_builder.NewStageBuilder(containerBackend, "", img)
stageImage := &stage.StageImage{
Image: img,
Builder: stageBuilder,
}

buildContext := NewBuildContextStub(files)

return &TestData{
Stage: stg,
ExpectedDigest: expectedDigest,
Conveyor: conveyor,
ContainerBackend: containerBackend,
Image: img,
StageBuilder: stageBuilder,
StageImage: stageImage,
BuildContext: buildContext,
}
}
13 changes: 13 additions & 0 deletions pkg/build/stage/instruction/suite_test.go
@@ -0,0 +1,13 @@
package instruction

import (
"testing"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
)

func TestStage(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Dockerfile Instruction Suite")
}

0 comments on commit 2ef3d11

Please sign in to comment.