pkg/build/stage/full_dockerfile.go

Summary

Maintainability
F
5 days
Test Coverage
D
63%
package stage

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "os"
    "path/filepath"
    "strconv"
    "strings"

    "github.com/moby/buildkit/frontend/dockerfile/instructions"
    "github.com/moby/buildkit/frontend/dockerfile/parser"
    "github.com/moby/buildkit/frontend/dockerfile/shell"

    "github.com/werf/logboek"
    "github.com/werf/werf/pkg/config"
    "github.com/werf/werf/pkg/container_backend"
    "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"
    "github.com/werf/werf/pkg/path_matcher"
    "github.com/werf/werf/pkg/util"
)

var ErrInvalidBaseImage = errors.New("invalid base image")

func IsErrInvalidBaseImage(err error) bool {
    return err != nil && errors.Is(err, ErrInvalidBaseImage)
}

func GenerateFullDockerfileStage(dockerRunArgs *DockerRunArgs, dockerStages *DockerStages, contextChecksum *ContextChecksum, baseStageOptions *BaseStageOptions, dependencies []*config.Dependency) *FullDockerfileStage {
    return newFullDockerfileStage(dockerRunArgs, dockerStages, contextChecksum, baseStageOptions, dependencies)
}

func newFullDockerfileStage(dockerRunArgs *DockerRunArgs, dockerStages *DockerStages, contextChecksum *ContextChecksum, baseStageOptions *BaseStageOptions, dependencies []*config.Dependency) *FullDockerfileStage {
    s := &FullDockerfileStage{}
    s.DockerRunArgs = dockerRunArgs
    s.DockerStages = dockerStages
    s.ContextChecksum = contextChecksum
    s.BaseStage = NewBaseStage(Dockerfile, baseStageOptions)
    s.dependencies = dependencies

    return s
}

type FullDockerfileStage struct {
    dependencies []*config.Dependency

    *DockerRunArgs
    *DockerStages
    *ContextChecksum
    *BaseStage
}

func NewDockerRunArgs(dockerfile []byte, dockerfilePath, target, context string, contextAddFiles []string, buildArgs map[string]interface{}, addHost []string, network, ssh string) *DockerRunArgs {
    return &DockerRunArgs{
        dockerfile:      dockerfile,
        dockerfilePath:  dockerfilePath,
        target:          target,
        context:         context,
        contextAddFiles: contextAddFiles,
        buildArgs:       buildArgs,
        addHost:         addHost,
        network:         network,
        ssh:             ssh,
    }
}

type DockerRunArgs struct {
    dockerfile      []byte
    dockerfilePath  string
    target          string
    context         string
    contextAddFiles []string
    buildArgs       map[string]interface{}
    addHost         []string
    network         string
    ssh             string
}

func (d *DockerRunArgs) contextRelativeToGitWorkTree(giterminismManager giterminism_manager.Interface) string {
    return filepath.Join(giterminismManager.RelativeToGitProjectDir(), d.context)
}

func (d *DockerRunArgs) contextAddFilesRelativeToGitWorkTree(giterminismManager giterminism_manager.Interface) []string {
    var result []string
    for _, addFile := range d.contextAddFiles {
        result = append(result, filepath.Join(d.contextRelativeToGitWorkTree(giterminismManager), addFile))
    }

    return result
}

type DockerStages struct {
    dockerStages           []instructions.Stage
    dockerTargetStageIndex int
    dockerBuildArgsHash    map[string]string
    dockerMetaArgs         []instructions.ArgCommand
    dockerStageArgsHash    map[int]map[string]string
    dockerStageEnvs        map[int]map[string]string

    imageOnBuildInstructions map[string][]string
}

func NewDockerStages(dockerStages []instructions.Stage, dockerBuildArgsHash map[string]string, dockerMetaArgs []instructions.ArgCommand, dockerTargetStageIndex int) *DockerStages {
    ds := &DockerStages{
        dockerStages:             dockerStages,
        dockerTargetStageIndex:   dockerTargetStageIndex,
        dockerBuildArgsHash:      dockerBuildArgsHash,
        dockerMetaArgs:           dockerMetaArgs,
        dockerStageArgsHash:      map[int]map[string]string{},
        dockerStageEnvs:          map[int]map[string]string{},
        imageOnBuildInstructions: map[string][]string{},
    }

    return ds
}

func (ds *DockerStages) resolveDockerMetaArgs(resolvedDependenciesArgsHash map[string]string) (map[string]string, error) {
    resolved := map[string]string{}

    for _, argInstruction := range ds.dockerMetaArgs {
        for _, keyValuePairOptional := range argInstruction.Args {
            key := keyValuePairOptional.Key

            var value string
            if keyValuePairOptional.Value != nil {
                value = *keyValuePairOptional.Value
            }

            resolvedKey, resolvedValue, err := ds.resolveDockerMetaArg(key, value, resolved, resolvedDependenciesArgsHash)
            if err != nil {
                return nil, fmt.Errorf("unable to resolve docker meta arg: %w", err)
            }

            resolved[resolvedKey] = resolvedValue
        }
    }

    return resolved, nil
}

// addDockerMetaArg function sets --build-arg value or resolved meta ARG value
func (ds *DockerStages) resolveDockerMetaArg(key, value string, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash map[string]string) (string, string, error) {
    resolvedKey, err := ds.ShlexProcessWordWithMetaArgs(key, resolvedDockerMetaArgsHash)
    if err != nil {
        return "", "", err
    }

    var resolvedValue string

    dependencyArgValue, ok := resolvedDependenciesArgsHash[resolvedKey]
    if ok {
        resolvedValue = dependencyArgValue
    } else {
        if buildArgValue, ok := ds.dockerBuildArgsHash[resolvedKey]; ok {
            resolvedValue = buildArgValue
        } else {
            rValue, err := ds.ShlexProcessWordWithMetaArgs(value, resolvedDockerMetaArgsHash)
            if err != nil {
                return "", "", err
            }

            resolvedValue = rValue
        }
    }

    return resolvedKey, resolvedValue, err
}

// resolveDockerStageArg function sets dependency arg value, or --build-arg value, or resolved dockerfile stage ARG value, or resolved meta ARG value (if stage ARG value is empty)
func (ds *DockerStages) resolveDockerStageArg(dockerStageID int, key, value string, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash map[string]string) (string, string, error) {
    resolvedKey, err := ds.ShlexProcessWordWithStageArgsAndEnvs(dockerStageID, key)
    if err != nil {
        return "", "", err
    }

    var resolvedValue string
    dependencyArgValue, ok := resolvedDependenciesArgsHash[resolvedKey]
    if ok {
        resolvedValue = dependencyArgValue
    } else {
        buildArgValue, ok := ds.dockerBuildArgsHash[resolvedKey]
        switch {
        case ok:
            resolvedValue = buildArgValue
        case value == "":
            resolvedValue = resolvedDockerMetaArgsHash[resolvedKey]
        default:
            rValue, err := ds.ShlexProcessWordWithStageArgsAndEnvs(dockerStageID, value)
            if err != nil {
                return "", "", err
            }
            resolvedValue = rValue
        }
    }

    return resolvedKey, resolvedValue, nil
}

func (ds *DockerStages) AddDockerStageEnv(dockerStageID int, key, value string) (string, string, error) {
    resolvedKey, err := ds.ShlexProcessWordWithStageArgsAndEnvs(dockerStageID, key)
    if err != nil {
        return "", "", err
    }

    resolvedValue, err := ds.ShlexProcessWordWithStageArgsAndEnvs(dockerStageID, value)
    if err != nil {
        return "", "", err
    }

    ds.DockerStageEnvs(dockerStageID)[resolvedKey] = resolvedValue
    return resolvedKey, resolvedValue, nil
}

func (ds *DockerStages) ShlexProcessWordWithMetaArgs(value string, resolvedDockerMetaArgsHash map[string]string) (string, error) {
    return shlexProcessWord(value, toArgsArray(resolvedDockerMetaArgsHash))
}

func (ds *DockerStages) ShlexProcessWordWithStageArgsAndEnvs(dockerStageID int, value string) (string, error) {
    return shlexProcessWord(value, toArgsArray(ds.DockerStageArgsHash(dockerStageID), ds.DockerStageEnvs(dockerStageID)))
}

func (ds *DockerStages) ShlexProcessWordWithStageEnvs(dockerStageID int, value string) (string, error) {
    return shlexProcessWord(value, toArgsArray(ds.DockerStageEnvs(dockerStageID)))
}

func (ds *DockerStages) DockerStageArgsHash(dockerStageID int) map[string]string {
    _, ok := ds.dockerStageArgsHash[dockerStageID]
    if !ok {
        ds.dockerStageArgsHash[dockerStageID] = map[string]string{}
    }

    return ds.dockerStageArgsHash[dockerStageID]
}

func (ds *DockerStages) DockerStageEnvs(dockerStageID int) map[string]string {
    _, ok := ds.dockerStageEnvs[dockerStageID]
    if !ok {
        ds.dockerStageEnvs[dockerStageID] = map[string]string{}
    }

    return ds.dockerStageEnvs[dockerStageID]
}

func toArgsArray(argsHashes ...map[string]string) []string {
    var argsArray []string

    isAddedKey := map[string]bool{}
    for i := len(argsHashes) - 1; i >= 0; i-- {
        for _, argsHash := range argsHashes {
            for key, value := range argsHash {
                if _, ok := isAddedKey[key]; ok {
                    continue
                }

                argsArray = append(argsArray, fmt.Sprintf("%s=%s", key, value))
                isAddedKey[key] = true
            }
        }
    }

    return argsArray
}

func shlexProcessWord(value string, argsArray []string) (string, error) {
    shlex := shell.NewLex(parser.DefaultEscapeToken)
    resolvedValue, err := shlex.ProcessWord(value, argsArray)
    if err != nil {
        return "", err
    }

    return resolvedValue, nil
}

func NewContextChecksum(dockerignorePathMatcher path_matcher.PathMatcher) *ContextChecksum {
    return &ContextChecksum{
        dockerignorePathMatcher: dockerignorePathMatcher,
    }
}

type ContextChecksum struct {
    dockerignorePathMatcher path_matcher.PathMatcher
}

type dockerfileInstructionInterface interface {
    String() string
    Name() string
}

func (s *FullDockerfileStage) FetchDependencies(ctx context.Context, c Conveyor, containerBackend container_backend.ContainerBackend, dockerRegistry docker_registry.GenericApiInterface) error {
    resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.targetPlatform, s.dependencies, c)

    resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
    if err != nil {
        return fmt.Errorf("unable to resolve docker meta args: %w", err)
    }

outerLoop:
    for ind, stage := range s.dockerStages {
        resolvedBaseName, err := s.ShlexProcessWordWithMetaArgs(stage.BaseName, resolvedDockerMetaArgsHash)
        if err != nil {
            return err
        }

        if resolvedBaseName == "" {
            return ErrInvalidBaseImage
        }

        for relatedStageIndex, relatedStage := range s.dockerStages {
            if ind == relatedStageIndex {
                continue
            }

            if resolvedBaseName == relatedStage.Name {
                continue outerLoop
            }
        }

        _, ok := s.imageOnBuildInstructions[resolvedBaseName]
        if ok || resolvedBaseName == "scratch" {
            continue
        }

        getBaseImageOnBuildLocally := func() ([]string, error) {
            info, err := containerBackend.GetImageInfo(ctx, resolvedBaseName, container_backend.GetImageInfoOpts{})
            if err != nil {
                return nil, err
            }

            if info == nil {
                return nil, errImageNotExistLocally
            }

            return info.OnBuild, nil
        }

        getBaseImageOnBuildRemotely := func() ([]string, error) {
            configFile, err := dockerRegistry.GetRepoImageConfigFile(ctx, resolvedBaseName)
            if err != nil {
                return nil, fmt.Errorf("get repo image %q config file failed: %w", resolvedBaseName, err)
            }

            return configFile.Config.OnBuild, nil
        }

        var onBuild []string
        if onBuild, err = getBaseImageOnBuildLocally(); err != nil && err != errImageNotExistLocally {
            return err
        } else if err == errImageNotExistLocally {
            var getRemotelyErr error
            if onBuild, getRemotelyErr = getBaseImageOnBuildRemotely(); getRemotelyErr != nil {
                if isUnsupportedMediaTypeError(getRemotelyErr) {
                    logboek.Context(ctx).Warn().LogF("WARNING: Could not get base image manifest from local docker and from docker registry: %s\n", getRemotelyErr)
                    logboek.Context(ctx).Warn().LogLn("WARNING: The base image pulling is necessary for calculating digest of image correctly\n")
                    if err := logboek.Context(ctx).Default().LogProcess("Pulling base image %s", resolvedBaseName).DoError(func() error {
                        return containerBackend.Pull(ctx, resolvedBaseName, container_backend.PullOpts{})
                    }); err != nil {
                        return err
                    }

                    if onBuild, err = getBaseImageOnBuildLocally(); err != nil {
                        return err
                    }
                } else {
                    return getRemotelyErr
                }
            }
        }

        s.imageOnBuildInstructions[resolvedBaseName] = onBuild
    }

    return nil
}

func isUnsupportedMediaTypeError(err error) bool {
    return strings.Contains(err.Error(), "unsupported MediaType")
}

var errImageNotExistLocally = errors.New("IMAGE_NOT_EXIST_LOCALLY")

func (s *FullDockerfileStage) GetDependencies(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, prevImage, prevBuiltImage *StageImage, buildContextArchive container_backend.BuildContextArchiver) (string, error) {
    resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.targetPlatform, s.dependencies, c)

    resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
    if err != nil {
        return "", fmt.Errorf("unable to resolve docker meta args: %w", err)
    }

    var stagesDependencies [][]string
    var stagesOnBuildDependencies [][]string

    for ind, stage := range s.dockerStages {
        var dependencies []string
        var onBuildDependencies []string

        dependencies = append(dependencies, s.addHost...)

        resolvedBaseName, err := s.ShlexProcessWordWithMetaArgs(stage.BaseName, resolvedDockerMetaArgsHash)
        if err != nil {
            return "", err
        }

        dependencies = append(dependencies, resolvedBaseName)

        onBuildInstructions, ok := s.imageOnBuildInstructions[resolvedBaseName]
        if ok {
            for _, instruction := range onBuildInstructions {
                _, iOnBuildDependencies, err := s.dockerfileOnBuildInstructionDependencies(ctx, c.GiterminismManager(), resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash, ind, instruction, true)
                if err != nil {
                    return "", err
                }

                dependencies = append(dependencies, iOnBuildDependencies...)
            }
        }

        for _, cmd := range stage.Commands {
            cmdDependencies, cmdOnBuildDependencies, err := s.dockerfileInstructionDependencies(ctx, c.GiterminismManager(), resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash, ind, cmd, false, false)
            if err != nil {
                return "", err
            }

            dependencies = append(dependencies, cmdDependencies...)
            onBuildDependencies = append(onBuildDependencies, cmdOnBuildDependencies...)
        }

        stagesDependencies = append(stagesDependencies, dependencies)
        stagesOnBuildDependencies = append(stagesOnBuildDependencies, onBuildDependencies)
    }

    for ind, stage := range s.dockerStages {
        for relatedStageIndex, relatedStage := range s.dockerStages {
            if ind == relatedStageIndex {
                continue
            }

            if stage.BaseName == relatedStage.Name {
                stagesDependencies[ind] = append(stagesDependencies[ind], stagesDependencies[relatedStageIndex]...)
                stagesDependencies[ind] = append(stagesDependencies[ind], stagesOnBuildDependencies[relatedStageIndex]...)
            }
        }

        for _, cmd := range stage.Commands {
            switch typedCmd := cmd.(type) {
            case *instructions.CopyCommand:
                relatedStageIndex, err := strconv.Atoi(typedCmd.From)
                if err == nil && relatedStageIndex < len(stagesDependencies) {
                    stagesDependencies[ind] = append(stagesDependencies[ind], stagesDependencies[relatedStageIndex]...)
                }

            case *instructions.RunCommand:
                for _, mount := range instructions.GetMounts(typedCmd) {
                    relatedStageIndex, err := strconv.Atoi(mount.From)
                    if err == nil && relatedStageIndex < len(stagesDependencies) {
                        stagesDependencies[ind] = append(stagesDependencies[ind], stagesDependencies[relatedStageIndex]...)
                    }
                }
            }
        }
    }

    dockerfileStageDependencies := stagesDependencies[s.dockerTargetStageIndex]

    if dockerfileStageDependenciesDebug() {
        logboek.Context(ctx).LogLn(dockerfileStageDependencies)
    }

    return util.Sha256Hash(dockerfileStageDependencies...), nil
}

func (s *FullDockerfileStage) HasPrevStage() bool {
    return false
}

func (s *FullDockerfileStage) IsStapelStage() bool {
    return false
}

func (s *FullDockerfileStage) UsesBuildContext() bool {
    return true
}

func (s *FullDockerfileStage) dockerfileInstructionDependencies(ctx context.Context, giterminismManager giterminism_manager.Interface, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash map[string]string, dockerStageID int, cmd interface{}, isOnbuildInstruction, isBaseImageOnbuildInstruction bool) ([]string, []string, error) {
    var dependencies []string
    var onBuildDependencies []string

    resolveValueFunc := func(value string) (string, error) {
        if isBaseImageOnbuildInstruction {
            return value, nil
        }

        var shlexProcessWordFunc func(int, string) (string, error)
        if isOnbuildInstruction {
            shlexProcessWordFunc = s.ShlexProcessWordWithStageEnvs
        } else {
            shlexProcessWordFunc = s.ShlexProcessWordWithStageArgsAndEnvs
        }

        resolvedValue, err := shlexProcessWordFunc(dockerStageID, value)
        if err != nil {
            return "", err
        }

        return resolvedValue, nil
    }

    resolveKeyAndValueFunc := func(key, value string) (string, string, error) {
        resolvedKey, err := resolveValueFunc(key)
        if err != nil {
            return "", "", err
        }

        resolvedValue, err := resolveValueFunc(value)
        if err != nil {
            return "", "", err
        }

        return resolvedKey, resolvedValue, nil
    }

    processArgFunc := func(key, value string) (string, string, error) {
        var resolvedKey, resolvedValue string
        var err error
        if !isOnbuildInstruction {
            resolvedKey, resolvedValue, err = s.resolveDockerStageArg(dockerStageID, key, value, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash)
            if err != nil {
                return "", "", err
            }

            s.DockerStageArgsHash(dockerStageID)[resolvedKey] = resolvedValue
        } else {
            resolvedKey, resolvedValue, err = resolveKeyAndValueFunc(key, value)
            if err != nil {
                return "", "", err
            }
        }

        return resolvedKey, resolvedValue, nil
    }

    processEnvFunc := func(key, value string) (string, string, error) {
        var resolvedKey, resolvedValue string
        var err error
        if !isOnbuildInstruction {
            resolvedKey, resolvedValue, err = s.AddDockerStageEnv(dockerStageID, key, value)
            if err != nil {
                return "", "", err
            }
        } else {
            resolvedKey, resolvedValue, err = resolveKeyAndValueFunc(key, value)
            if err != nil {
                return "", "", err
            }
        }

        return resolvedKey, resolvedValue, nil
    }

    resolveSourcesFunc := func(sources []string) ([]string, error) {
        var resolvedSources []string
        for _, source := range sources {
            resolvedSource, err := resolveValueFunc(source)
            if err != nil {
                return nil, err
            }

            resolvedSources = append(resolvedSources, resolvedSource)
        }

        return resolvedSources, nil
    }

    switch c := cmd.(type) {
    case *instructions.ArgCommand:
        for _, keyValuePairOptional := range c.Args {
            key := keyValuePairOptional.Key

            var value string
            if keyValuePairOptional.Value != nil {
                value = *keyValuePairOptional.Value
            }

            resolvedKey, resolvedValue, err := processArgFunc(key, value)
            if err != nil {
                return nil, nil, err
            }

            dependencies = append(dependencies, fmt.Sprintf("ARG %s=%s", resolvedKey, resolvedValue))
        }
    case *instructions.EnvCommand:
        for _, keyValuePair := range c.Env {
            resolvedKey, resolvedValue, err := processEnvFunc(keyValuePair.Key, keyValuePair.Value)
            if err != nil {
                return nil, nil, err
            }

            dependencies = append(dependencies, fmt.Sprintf("ENV %s=%s", resolvedKey, resolvedValue))
        }
    case *instructions.AddCommand:
        dependencies = append(dependencies, c.String())

        resolvedSources, err := resolveSourcesFunc(c.SourcePaths)
        if err != nil {
            return nil, nil, err
        }

        checksum, err := s.calculateFilesChecksum(ctx, giterminismManager, resolvedSources, c.String())
        if err != nil {
            return nil, nil, err
        }
        dependencies = append(dependencies, checksum)
    case *instructions.CopyCommand:
        dependencies = append(dependencies, c.String())
        if c.From == "" {
            resolvedSources, err := resolveSourcesFunc(c.SourcePaths)
            if err != nil {
                return nil, nil, err
            }

            checksum, err := s.calculateFilesChecksum(ctx, giterminismManager, resolvedSources, c.String())
            if err != nil {
                return nil, nil, err
            }
            dependencies = append(dependencies, checksum)
        }
    case *instructions.OnbuildCommand:
        cDependencies, cOnBuildDependencies, err := s.dockerfileOnBuildInstructionDependencies(ctx, giterminismManager, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash, dockerStageID, c.Expression, false)
        if err != nil {
            return nil, nil, err
        }

        dependencies = append(dependencies, cDependencies...)
        onBuildDependencies = append(onBuildDependencies, cOnBuildDependencies...)
    case dockerfileInstructionInterface:
        resolvedValue, err := resolveValueFunc(c.String())
        if err != nil {
            return nil, nil, err
        }

        dependencies = append(dependencies, resolvedValue)
    default:
        panic("runtime error")
    }

    return dependencies, onBuildDependencies, nil
}

func (s *FullDockerfileStage) dockerfileOnBuildInstructionDependencies(ctx context.Context, giterminismManager giterminism_manager.Interface, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash map[string]string, dockerStageID int, expression string, isBaseImageOnbuildInstruction bool) ([]string, []string, error) {
    p, err := parser.Parse(bytes.NewReader([]byte(expression)))
    if err != nil {
        return nil, nil, err
    }

    if len(p.AST.Children) != 1 {
        panic(fmt.Sprintf("unexpected condition: %s (%d children)", expression, len(p.AST.Children)))
    }

    instruction := p.AST.Children[0]
    cmd, err := instructions.ParseInstruction(instruction)
    if err != nil {
        return nil, nil, err
    }

    onBuildDependencies, _, err := s.dockerfileInstructionDependencies(ctx, giterminismManager, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash, dockerStageID, cmd, true, isBaseImageOnbuildInstruction)
    if err != nil {
        return nil, nil, err
    }

    return []string{expression}, onBuildDependencies, nil
}

func (s *FullDockerfileStage) PrepareImage(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, prevBuiltImage, stageImage *StageImage, buildContextArchive container_backend.BuildContextArchiver) error {
    if err := s.SetupDockerImageBuilder(stageImage.Builder.DockerfileBuilder(), c); err != nil {
        return err
    }

    stageImage.Builder.DockerfileBuilder().SetBuildContextArchive(buildContextArchive)

    stageImage.Builder.DockerfileBuilder().AppendLabels(fmt.Sprintf("%s=%s", image.WerfProjectRepoCommitLabel, c.GiterminismManager().HeadCommit()))

    if c.GiterminismManager().Dev() {
        stageImage.Builder.DockerfileBuilder().AppendLabels(fmt.Sprintf("%s=true", image.WerfDevLabel))
    }

    return nil
}

func (s *FullDockerfileStage) SetupDockerImageBuilder(b stage_builder.DockerfileBuilderInterface, c Conveyor) error {
    b.SetDockerfile(s.dockerfile)
    b.SetDockerfileCtxRelPath(s.dockerfilePath)

    if s.target != "" {
        b.SetTarget(s.target)
    }

    if len(s.buildArgs) > 0 {
        for key, value := range s.buildArgs {
            b.AppendBuildArgs(fmt.Sprintf("%s=%v", key, value))
        }
    }

    resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.targetPlatform, s.dependencies, c)
    if len(resolvedDependenciesArgsHash) > 0 {
        for key, value := range resolvedDependenciesArgsHash {
            b.AppendBuildArgs(fmt.Sprintf("%s=%v", key, value))
        }
    }

    if len(s.addHost) > 0 {
        b.AppendAddHost(s.addHost...)
    }

    if s.network != "" {
        b.SetNetwork(s.network)
    }

    if s.ssh != "" {
        b.SetSSH(s.ssh)
    }

    return nil
}

func (s *FullDockerfileStage) calculateFilesChecksum(ctx context.Context, giterminismManager giterminism_manager.Interface, wildcards []string, dockerfileLine string) (string, error) {
    var checksum string
    var err error

    normalizedWildcards := dockerfile.NormalizeCopyAddSourcesForPathMatcher(wildcards)

    logProcess := logboek.Context(ctx).Debug().LogProcess("Calculating files checksum (%v) from local git repo", normalizedWildcards)
    logProcess.Start()

    checksum, err = s.calculateFilesChecksumWithGit(ctx, giterminismManager, normalizedWildcards, dockerfileLine)
    if err != nil {
        logProcess.Fail()
        return "", err
    } else {
        logProcess.End()
    }

    if len(s.contextAddFiles) > 0 {
        logProcess = logboek.Context(ctx).Debug().LogProcess("Calculating contextAddFiles checksum")
        logProcess.Start()

        wildcardsPathMatcher := path_matcher.NewPathMatcher(path_matcher.PathMatcherOptions{
            BasePath:     s.contextRelativeToGitWorkTree(giterminismManager),
            IncludeGlobs: wildcards,
        })
        if contextAddChecksum, err := context_manager.ContextAddFilesChecksum(ctx, giterminismManager.ProjectDir(), s.context, s.contextAddFiles, wildcardsPathMatcher); err != nil {
            logProcess.Fail()
            return "", fmt.Errorf("unable to calculate checksum for contextAddFiles files list: %w", err)
        } else {
            if contextAddChecksum != "" {
                logboek.Context(ctx).Debug().LogLn()
                logboek.Context(ctx).Debug().LogLn(contextAddChecksum)
                checksum = util.Sha256Hash(checksum, contextAddChecksum)
            }

            logProcess.End()
        }
    }

    logboek.Context(ctx).Debug().LogF("Result checksum: %s\n", checksum)
    logboek.Context(ctx).Debug().LogOptionalLn()

    return checksum, nil
}

func (s *FullDockerfileStage) calculateFilesChecksumWithGit(ctx context.Context, giterminismManager giterminism_manager.Interface, wildcards []string, dockerfileLine string) (string, error) {
    contextPathRelativeToGitWorkTree := s.contextRelativeToGitWorkTree(giterminismManager)
    wildcardsPathMatcher := path_matcher.NewPathMatcher(path_matcher.PathMatcherOptions{
        BasePath:     contextPathRelativeToGitWorkTree,
        IncludeGlobs: wildcards,
    })
    lsTreeResultChecksum, err := giterminismManager.LocalGitRepo().GetOrCreateChecksum(ctx, git_repo.ChecksumOptions{
        LsTreeOptions: git_repo.LsTreeOptions{
            PathScope: contextPathRelativeToGitWorkTree,
            PathMatcher: path_matcher.NewMultiPathMatcher(
                s.dockerignorePathMatcher,
                wildcardsPathMatcher,
            ),
            AllFiles: false,
        },
        Commit: giterminismManager.HeadCommit(),
    })
    if err != nil {
        return "", err
    }

    pathMatcher := path_matcher.NewPathMatcher(path_matcher.PathMatcherOptions{
        ExcludeGlobs: s.contextAddFilesRelativeToGitWorkTree(giterminismManager),
        Matchers: []path_matcher.PathMatcher{
            wildcardsPathMatcher,
            s.dockerignorePathMatcher,
        },
    })

    if err := giterminismManager.Inspector().InspectBuildContextFiles(ctx, pathMatcher); err != nil {
        return "", err
    }

    return util.Sha256Hash(lsTreeResultChecksum), nil
}

func dockerfileStageDependenciesDebug() bool {
    return os.Getenv("WERF_DEBUG_DOCKERFILE_STAGE_DEPENDENCIES") == "1"
}