pkg/host_cleaning/host_cleanup.go

Summary

Maintainability
A
3 hrs
Test Coverage
F
53%
package host_cleaning

import (
    "context"
    "fmt"
    "os"
    "os/exec"
    "strings"

    "github.com/werf/logboek"
    "github.com/werf/werf/v2/pkg/container_backend"
    "github.com/werf/werf/v2/pkg/git_repo/gitdata"
    "github.com/werf/werf/v2/pkg/tmp_manager"
)

const (
    DefaultAllowedDockerStorageVolumeUsagePercentage       float64 = 70.0
    DefaultAllowedDockerStorageVolumeUsageMarginPercentage float64 = 5.0
    DefaultAllowedLocalCacheVolumeUsagePercentage          float64 = 70.0
    DefaultAllowedLocalCacheVolumeUsageMarginPercentage    float64 = 5.0
)

type HostCleanupOptions struct {
    AllowedDockerStorageVolumeUsagePercentage       *uint
    AllowedDockerStorageVolumeUsageMarginPercentage *uint
    AllowedLocalCacheVolumeUsagePercentage          *uint
    AllowedLocalCacheVolumeUsageMarginPercentage    *uint
    DockerServerStoragePath                         *string

    CleanupDockerServer bool
    DryRun              bool
    Force               bool
}

type AutoHostCleanupOptions struct {
    HostCleanupOptions

    ForceShouldRun bool
}

func getOptionValueOrDefault(optionValue *uint, defaultValue float64) float64 {
    var res float64
    if optionValue != nil {
        res = float64(*optionValue)
    } else {
        res = defaultValue
    }
    return res
}

func RunAutoHostCleanup(ctx context.Context, options AutoHostCleanupOptions) error {
    if !options.ForceShouldRun {
        shouldRun, err := ShouldRunAutoHostCleanup(ctx, options.HostCleanupOptions)
        if err != nil {
            logboek.Context(ctx).Warn().LogF("WARNING: unable to check if auto host cleanup should be run: %s\n", err)
            return nil
        }
        if !shouldRun {
            return nil
        }
    }

    logboek.Context(ctx).Debug().LogF("RunAutoHostCleanup forking ...\n")

    var args []string

    args = append(args,
        "host", "cleanup",
        fmt.Sprintf("--dry-run=%v", options.DryRun),
        fmt.Sprintf("--force=%v", options.Force),
    )

    if options.AllowedDockerStorageVolumeUsagePercentage != nil {
        args = append(args, "--allowed-docker-storage-volume-usage", fmt.Sprintf("%d", *options.AllowedDockerStorageVolumeUsagePercentage))
    }
    if options.AllowedDockerStorageVolumeUsageMarginPercentage != nil {
        args = append(args, "--allowed-docker-storage-volume-usage-margin", fmt.Sprintf("%d", *options.AllowedDockerStorageVolumeUsageMarginPercentage))
    }
    if options.AllowedLocalCacheVolumeUsagePercentage != nil {
        args = append(args, "--allowed-local-cache-volume-usage", fmt.Sprintf("%d", *options.AllowedLocalCacheVolumeUsagePercentage))
    }
    if options.AllowedLocalCacheVolumeUsageMarginPercentage != nil {
        args = append(args, "--allowed-docker-storage-volume-usage-margin", fmt.Sprintf("%d", *options.AllowedLocalCacheVolumeUsageMarginPercentage))
    }
    if options.DockerServerStoragePath != nil && *options.DockerServerStoragePath != "" {
        args = append(args, "--docker-server-storage-path", *options.DockerServerStoragePath)
    }

    executableName := os.Getenv("WERF_ORIGINAL_EXECUTABLE")
    if executableName == "" {
        executableName = os.Args[0]
    }

    cmd := exec.Command(executableName, args...)

    var env []string
    for _, spec := range os.Environ() {
        k := strings.SplitN(spec, "=", 2)[0]
        if k == "WERF_ENABLE_PROCESS_EXTERMINATOR" {
            continue
        }

        env = append(env, spec)
    }
    env = append(env, "_WERF_BACKGROUND_MODE_ENABLED=1")

    cmd.Env = env

    if err := cmd.Start(); err != nil {
        logboek.Context(ctx).Warn().LogF("WARNING: unable to start background auto host cleanup process: %s\n", err)
        return nil
    }

    if err := cmd.Process.Release(); err != nil {
        logboek.Context(ctx).Warn().LogF("WARNING: unable to detach background auto host cleanup process: %s\n", err)
        return nil
    }

    return nil
}

func RunHostCleanup(ctx context.Context, containerBackend container_backend.ContainerBackend, options HostCleanupOptions) error {
    if err := logboek.Context(ctx).LogProcess("Running GC for tmp data").DoError(func() error {
        if err := tmp_manager.RunGC(ctx, options.DryRun, containerBackend); err != nil {
            return fmt.Errorf("tmp files GC failed: %w", err)
        }
        return nil
    }); err != nil {
        return err
    }

    allowedLocalCacheVolumeUsagePercentage := getOptionValueOrDefault(options.AllowedLocalCacheVolumeUsagePercentage, DefaultAllowedLocalCacheVolumeUsagePercentage)
    allowedLocalCacheVolumeUsageMarginPercentage := getOptionValueOrDefault(options.AllowedLocalCacheVolumeUsageMarginPercentage, DefaultAllowedLocalCacheVolumeUsageMarginPercentage)

    allowedDockerStorageVolumeUsagePercentage := getOptionValueOrDefault(options.AllowedDockerStorageVolumeUsagePercentage, DefaultAllowedDockerStorageVolumeUsagePercentage)
    allowedDockerStorageVolumeUsageMarginPercentage := getOptionValueOrDefault(options.AllowedDockerStorageVolumeUsageMarginPercentage, DefaultAllowedDockerStorageVolumeUsageMarginPercentage)

    if err := logboek.Context(ctx).Default().LogProcess("Running GC for git data").DoError(func() error {
        if err := gitdata.RunGC(ctx, allowedLocalCacheVolumeUsagePercentage, allowedLocalCacheVolumeUsageMarginPercentage); err != nil {
            return fmt.Errorf("git repo GC failed: %w", err)
        }
        return nil
    }); err != nil {
        return err
    }

    if options.CleanupDockerServer {
        dockerServerStoragePath, err := getDockerServerStoragePath(ctx, options.DockerServerStoragePath)
        if err != nil {
            return fmt.Errorf("error getting local docker server storage path: %w", err)
        }

        return logboek.Context(ctx).Default().LogProcess("Running GC for local docker server").DoError(func() error {
            if err := RunGCForLocalDockerServer(ctx, allowedDockerStorageVolumeUsagePercentage, allowedDockerStorageVolumeUsageMarginPercentage, dockerServerStoragePath, options.Force, options.DryRun); err != nil {
                return fmt.Errorf("local docker server GC failed: %w", err)
            }
            return nil
        })
    }

    return nil
}

func ShouldRunAutoHostCleanup(ctx context.Context, options HostCleanupOptions) (bool, error) {
    shouldRun, err := tmp_manager.ShouldRunAutoGC()
    if err != nil {
        return false, fmt.Errorf("failed to check tmp manager GC: %w", err)
    }
    if shouldRun {
        return true, nil
    }

    if options.CleanupDockerServer {
        allowedLocalCacheVolumeUsagePercentage := getOptionValueOrDefault(options.AllowedLocalCacheVolumeUsagePercentage, DefaultAllowedLocalCacheVolumeUsagePercentage)
        allowedDockerStorageVolumeUsagePercentage := getOptionValueOrDefault(options.AllowedDockerStorageVolumeUsagePercentage, DefaultAllowedDockerStorageVolumeUsagePercentage)

        shouldRun, err = gitdata.ShouldRunAutoGC(ctx, allowedLocalCacheVolumeUsagePercentage)
        if err != nil {
            return false, fmt.Errorf("failed to check git repo GC: %w", err)
        }
        if shouldRun {
            return true, nil
        }

        dockerServerStoragePath, err := getDockerServerStoragePath(ctx, options.DockerServerStoragePath)
        if err != nil {
            return false, fmt.Errorf("error getting local docker server storage path: %w", err)
        }

        shouldRun, err = ShouldRunAutoGCForLocalDockerServer(ctx, allowedDockerStorageVolumeUsagePercentage, dockerServerStoragePath)
        if err != nil {
            return false, fmt.Errorf("failed to check local docker server host cleaner GC: %w", err)
        }
        if shouldRun {
            return true, nil
        }
    }

    return false, nil
}