pkg/container_backend/legacy_stage_image_container.go

Summary

Maintainability
B
5 hrs
Test Coverage
F
55%
package container_backend

import (
    "context"
    "encoding/base64"
    "fmt"
    "strings"

    "github.com/docker/docker/api/types"

    "github.com/werf/logboek"
    "github.com/werf/werf/v2/pkg/docker"
    "github.com/werf/werf/v2/pkg/image"
    "github.com/werf/werf/v2/pkg/stapel"
    "github.com/werf/werf/v2/pkg/util"
)

type LegacyStageImageContainer struct {
    image                      *LegacyStageImage
    name                       string
    runCommands                []string
    serviceRunCommands         []string
    runOptions                 *LegacyStageImageContainerOptions
    commitChangeOptions        *LegacyStageImageContainerOptions
    serviceCommitChangeOptions *LegacyStageImageContainerOptions
}

func newLegacyStageImageContainer(img *LegacyStageImage) *LegacyStageImageContainer {
    c := &LegacyStageImageContainer{}
    c.image = img
    c.name = fmt.Sprintf("%s%v", image.StageContainerNamePrefix, util.GenerateConsistentRandomString(10))
    c.runOptions = newLegacyStageContainerOptions()
    c.commitChangeOptions = newLegacyStageContainerOptions()
    c.serviceCommitChangeOptions = newLegacyStageContainerOptions()
    return c
}

func (c *LegacyStageImageContainer) Name() string {
    return c.name
}

func (c *LegacyStageImageContainer) UserCommitChanges() []string {
    return c.commitChangeOptions.toCommitChanges(c.image.commitChangeOptions)
}

func (c *LegacyStageImageContainer) UserRunCommands() []string {
    return c.runCommands
}

func (c *LegacyStageImageContainer) AddRunCommands(commands ...string) {
    c.runCommands = append(c.runCommands, commands...)
}

func (c *LegacyStageImageContainer) AddServiceRunCommands(commands ...string) {
    c.serviceRunCommands = append(c.serviceRunCommands, commands...)
}

func (c *LegacyStageImageContainer) RunOptions() LegacyContainerOptions {
    return c.runOptions
}

func (c *LegacyStageImageContainer) CommitChangeOptions() LegacyContainerOptions {
    return c.commitChangeOptions
}

func (c *LegacyStageImageContainer) ServiceCommitChangeOptions() LegacyContainerOptions {
    return c.serviceCommitChangeOptions
}

func (c *LegacyStageImageContainer) prepareRunArgs(ctx context.Context) ([]string, error) {
    var args []string
    args = append(args, fmt.Sprintf("--name=%s", c.name))

    if c.image.GetTargetPlatform() != "" {
        args = append(args, fmt.Sprintf("--platform=%s", c.image.GetTargetPlatform()))
    }

    runOptions, err := c.prepareRunOptions(ctx)
    if err != nil {
        return nil, err
    }

    runArgs, err := runOptions.toRunArgs()
    if err != nil {
        return nil, err
    }

    setColumnsEnv := fmt.Sprintf("--env=COLUMNS=%d", logboek.Context(ctx).Streams().ContentWidth())
    runArgs = append(runArgs, setColumnsEnv)

    fromImageId := c.image.fromImage.GetID()

    args = append(args, runArgs...)
    args = append(args, fromImageId)
    args = append(args, "-ec")
    args = append(args, c.prepareRunCommand())

    return args, nil
}

func (c *LegacyStageImageContainer) prepareRunCommand() string {
    return ShelloutPack(strings.Join(c.prepareRunCommands(), " && "))
}

func (c *LegacyStageImageContainer) prepareRunCommands() []string {
    runCommands := c.prepareAllRunCommands()
    if len(runCommands) != 0 {
        return runCommands
    } else {
        return []string{stapel.TrueBinPath()}
    }
}

func (c *LegacyStageImageContainer) prepareAllRunCommands() []string {
    var commands []string

    if debugDockerRunCommand() {
        commands = append(commands, "set -x")
    }

    commands = append(commands, c.serviceRunCommands...)
    commands = append(commands, c.runCommands...)

    return commands
}

func ShelloutPack(command string) string {
    return fmt.Sprintf("eval $(echo %s | %s --decode)", base64.StdEncoding.EncodeToString([]byte(command)), stapel.Base64BinPath())
}

func (c *LegacyStageImageContainer) prepareIntrospectBeforeArgs(ctx context.Context) ([]string, error) {
    args, err := c.prepareIntrospectArgsBase(ctx)
    if err != nil {
        return nil, err
    }

    fromImageId := c.image.fromImage.GetID()

    args = append(args, fromImageId)
    args = append(args, "-ec")
    args = append(args, stapel.BashBinPath())

    return args, nil
}

func (c *LegacyStageImageContainer) prepareIntrospectArgs(ctx context.Context) ([]string, error) {
    args, err := c.prepareIntrospectArgsBase(ctx)
    if err != nil {
        return nil, err
    }

    imageId := c.image.GetID()

    args = append(args, imageId)
    args = append(args, "-ec")
    args = append(args, stapel.BashBinPath())

    return args, nil
}

func (c *LegacyStageImageContainer) prepareIntrospectArgsBase(ctx context.Context) ([]string, error) {
    var args []string

    runOptions, err := c.prepareIntrospectOptions(ctx)
    if err != nil {
        return nil, err
    }

    runArgs, err := runOptions.toRunArgs()
    if err != nil {
        return nil, err
    }

    args = append(args, []string{"-ti", "--rm"}...)
    args = append(args, runArgs...)

    return args, nil
}

func (c *LegacyStageImageContainer) prepareRunOptions(ctx context.Context) (*LegacyStageImageContainerOptions, error) {
    serviceRunOptions, err := c.prepareServiceRunOptions(ctx)
    if err != nil {
        return nil, err
    }
    return serviceRunOptions.merge(c.runOptions), nil
}

func (c *LegacyStageImageContainer) prepareServiceRunOptions(ctx context.Context) (*LegacyStageImageContainerOptions, error) {
    serviceRunOptions := newLegacyStageContainerOptions()
    serviceRunOptions.Workdir = "/"
    serviceRunOptions.Entrypoint = stapel.BashBinPath()
    serviceRunOptions.User = "0:0"

    stapelContainerName, err := stapel.GetOrCreateContainer(ctx)
    if err != nil {
        return nil, err
    }

    serviceRunOptions.VolumesFrom = []string{stapelContainerName}

    return serviceRunOptions, nil
}

func (c *LegacyStageImageContainer) prepareIntrospectOptions(ctx context.Context) (*LegacyStageImageContainerOptions, error) {
    return c.prepareRunOptions(ctx)
}

func (c *LegacyStageImageContainer) prepareCommitChanges(ctx context.Context, opts LegacyCommitChangeOptions) ([]string, error) {
    commitOptions, err := c.prepareCommitOptions(ctx)
    if err != nil {
        return nil, err
    }

    commitChanges, err := commitOptions.prepareCommitChanges(ctx, opts)
    if err != nil {
        return nil, err
    }
    return commitChanges, nil
}

func (c *LegacyStageImageContainer) prepareCommitOptions(ctx context.Context) (*LegacyStageImageContainerOptions, error) {
    inheritedCommitOptions, err := c.prepareInheritedCommitOptions(ctx)
    if err != nil {
        return nil, err
    }

    commitOptions := inheritedCommitOptions.merge(c.serviceCommitChangeOptions.merge(c.commitChangeOptions))
    return commitOptions, nil
}

func (c *LegacyStageImageContainer) prepareInheritedCommitOptions(ctx context.Context) (*LegacyStageImageContainerOptions, error) {
    inheritedOptions := newLegacyStageContainerOptions()

    if c.image.fromImage == nil {
        panic(fmt.Sprintf("runtime error: FromImage should be (%s)", c.image.name))
    }

    if err := c.image.fromImage.MustResetInfo(ctx); err != nil {
        return nil, fmt.Errorf("unable to reset info for image %s: %w", c.image.fromImage.Name(), err)
    }

    dockerServerBackend := c.image.ContainerBackend.(*DockerServerBackend)

    fromImageInspect, err := dockerServerBackend.GetImageInspect(ctx, c.image.fromImage.Name())
    if err != nil {
        return nil, fmt.Errorf("unable to get image inspect: %w", err)
    }

    if len(fromImageInspect.Config.Cmd) != 0 {
        inheritedOptions.Cmd = fmt.Sprintf("[\"%s\"]", strings.Join(fromImageInspect.Config.Cmd, "\", \""))
    }

    if len(fromImageInspect.Config.Entrypoint) != 0 {
        inheritedOptions.Entrypoint = fmt.Sprintf("[\"%s\"]", strings.Join(fromImageInspect.Config.Entrypoint, "\", \""))
    }

    inheritedOptions.User = fromImageInspect.Config.User
    if fromImageInspect.Config.WorkingDir != "" {
        inheritedOptions.Workdir = fromImageInspect.Config.WorkingDir
    } else {
        inheritedOptions.Workdir = "/"
    }
    return inheritedOptions, nil
}

func (c *LegacyStageImageContainer) run(ctx context.Context) error {
    _ = c.image.ContainerBackend.(*DockerServerBackend)

    runArgs, err := c.prepareRunArgs(ctx)
    if err != nil {
        return err
    }

    RegisterRunningContainer(c.name, ctx)
    err = docker.CliRun_LiveOutput(ctx, runArgs...)
    UnregisterRunningContainer(c.name)
    if err != nil {
        return fmt.Errorf("container run failed: %w", err)
    }
    return nil
}

func (c *LegacyStageImageContainer) introspect(ctx context.Context) error {
    _ = c.image.ContainerBackend.(*DockerServerBackend)

    runArgs, err := c.prepareIntrospectArgs(ctx)
    if err != nil {
        return err
    }

    if err := docker.CliRun_LiveOutput(ctx, runArgs...); err != nil {
        if !strings.Contains(err.Error(), "Code: ") || IsStartContainerErr(err) {
            return err
        }
    }

    return nil
}

func (c *LegacyStageImageContainer) introspectBefore(ctx context.Context) error {
    _ = c.image.ContainerBackend.(*DockerServerBackend)

    runArgs, err := c.prepareIntrospectBeforeArgs(ctx)
    if err != nil {
        return err
    }

    if err := docker.CliRun_LiveOutput(ctx, runArgs...); err != nil {
        if !strings.Contains(err.Error(), "Code: ") || IsStartContainerErr(err) {
            return err
        }
    }

    return nil
}

// https://docs.docker.com/engine/reference/run/#exit-status
func IsStartContainerErr(err error) bool {
    for _, code := range []string{"125", "126", "127"} {
        if strings.HasPrefix(err.Error(), fmt.Sprintf("Code: %s", code)) {
            return true
        }
    }

    return false
}

func (c *LegacyStageImageContainer) commit(ctx context.Context) (string, error) {
    _ = c.image.ContainerBackend.(*DockerServerBackend)

    commitChanges, err := c.prepareCommitChanges(ctx, c.image.commitChangeOptions)
    if err != nil {
        return "", err
    }

    commitOptions := types.ContainerCommitOptions{Changes: commitChanges}
    id, err := docker.ContainerCommit(ctx, c.name, commitOptions)
    if err != nil {
        return "", err
    }

    return id, nil
}

func (c *LegacyStageImageContainer) rm(ctx context.Context) error {
    _ = c.image.ContainerBackend.(*DockerServerBackend)

    err := docker.ContainerRemove(ctx, c.name, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
    if err != nil {
        if strings.Contains(err.Error(), fmt.Sprintf("removal of container %s is already in progress", c.name)) {
            return nil
        }
        return fmt.Errorf("unable to remove container %s: %w", c.name, err)
    }
    return nil
}