pkg/tmp_manager/gc.go

Summary

Maintainability
D
2 days
Test Coverage
F
10%
package tmp_manager

import (
    "context"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    "runtime"
    "strings"
    "time"

    "github.com/werf/logboek"
    "github.com/werf/werf/v2/pkg/container_backend"
    "github.com/werf/werf/v2/pkg/werf"
)

func ShouldRunAutoGC() (bool, error) {
    releasedProjectsDir := filepath.Join(GetReleasedTmpDirs(), projectsServiceDir)
    if _, err := os.Stat(releasedProjectsDir); !os.IsNotExist(err) {
        var err error
        releasedProjectsDirs, err := ioutil.ReadDir(releasedProjectsDir)
        if err != nil {
            return false, fmt.Errorf("unable to list released projects tmp dirs in %s: %w", releasedProjectsDir, err)
        }

        if len(releasedProjectsDirs) > 50 {
            return true, nil
        }
    }

    now := time.Now()

    createdDockerConfigsDir := filepath.Join(GetCreatedTmpDirs(), dockerConfigsServiceDir)
    if _, err := os.Stat(createdDockerConfigsDir); !os.IsNotExist(err) {
        var err error
        createdDirs, err := ioutil.ReadDir(createdDockerConfigsDir)
        if err != nil {
            return false, fmt.Errorf("unable to list created docker configs in %s: %w", createdDockerConfigsDir, err)
        }

        for _, info := range createdDirs {
            if now.Sub(info.ModTime()) > 24*time.Hour {
                return true, nil
            }
        }
    }

    createdKubeConfigsDir := filepath.Join(GetCreatedTmpDirs(), kubeConfigsServiceDir)
    if _, err := os.Stat(createdKubeConfigsDir); !os.IsNotExist(err) {
        var err error
        createdDirs, err := ioutil.ReadDir(createdKubeConfigsDir)
        if err != nil {
            return false, fmt.Errorf("unable to list created kubeconfigs in %s: %w", createdKubeConfigsDir, err)
        }

        for _, info := range createdDirs {
            if now.Sub(info.ModTime()) > 24*time.Hour {
                return true, nil
            }
        }
    }

    createdWerfConfigRenderFiles := filepath.Join(GetCreatedTmpDirs(), werfConfigRendersServiceDir)
    if _, err := os.Stat(createdWerfConfigRenderFiles); !os.IsNotExist(err) {
        var err error
        createdFiles, err := ioutil.ReadDir(createdWerfConfigRenderFiles)
        if err != nil {
            return false, fmt.Errorf("unable to list created werf config render files in %s: %w", createdWerfConfigRenderFiles, err)
        }

        for _, info := range createdFiles {
            if now.Sub(info.ModTime()) > 24*time.Hour {
                return true, nil
            }
        }
    }

    return false, nil
}

func RunGC(ctx context.Context, dryRun bool, containerBackend container_backend.ContainerBackend) error {
    projectDirsToRemove := []string{}
    pathsToRemove := []string{}

    if err := gcReleasedProjectDirs(&projectDirsToRemove, &pathsToRemove); err != nil {
        return err
    }

    if err := gcCreatedProjectDirs(&projectDirsToRemove, &pathsToRemove); err != nil {
        return err
    }

    if err := gcCreatedDockerConfigs(&pathsToRemove); err != nil {
        return err
    }

    if err := gcCreatedKubeConfigs(&pathsToRemove); err != nil {
        return fmt.Errorf("unable to gc kubeconfigs: %w", err)
    }

    if err := gcCreatedWerfConfigRenders(&pathsToRemove); err != nil {
        return err
    }

    var removeErrors []error

    if len(projectDirsToRemove) > 0 {
        for _, projectDirToRemove := range projectDirsToRemove {
            logboek.Context(ctx).LogLn(projectDirToRemove)
        }

        if !dryRun {
            if runtime.GOOS == "windows" {
                for _, path := range projectDirsToRemove {
                    if err := os.RemoveAll(path); err != nil {
                        removeErrors = append(removeErrors, fmt.Errorf("unable to remove tmp project dir %s: %w", path, err))
                    }
                }
            } else {
                if err := containerBackend.RemoveHostDirs(ctx, werf.GetTmpDir(), projectDirsToRemove); err != nil {
                    removeErrors = append(removeErrors, fmt.Errorf("unable to remove tmp projects dirs %s: %w", strings.Join(projectDirsToRemove, ", "), err))
                }
            }
        }
    }

    for _, path := range pathsToRemove {
        logboek.Context(ctx).LogLn(path)

        if !dryRun {
            err := os.RemoveAll(path)
            if err != nil {
                removeErrors = append(removeErrors, fmt.Errorf("unable to remove path %s: %w", path, err))
            }
        }
    }

    if len(removeErrors) > 0 {
        msg := ""
        for _, err := range removeErrors {
            msg += fmt.Sprintf("%s\n", err)
        }
        return fmt.Errorf("%s", msg)
    }

    return nil
}

// Remove all released project dirs
func gcReleasedProjectDirs(projectDirsToRemove, pathsToRemove *[]string) error {
    releasedProjectDirsLinks, err := getLinks(filepath.Join(GetReleasedTmpDirs(), projectsServiceDir))
    if err != nil {
        return fmt.Errorf("unable to get released tmp projects dirs: %w", err)
    }

    dirs, err := readLinks(releasedProjectDirsLinks)
    if err != nil {
        return fmt.Errorf("unable to read links: %w", err)
    }
    *projectDirsToRemove = append(*projectDirsToRemove, dirs...)

    for _, link := range releasedProjectDirsLinks {
        *pathsToRemove = append(*pathsToRemove, link.LinkPath)
    }

    return nil
}

// Remove only these created project dirs, which can be removed
func gcCreatedProjectDirs(projectDirsToRemove, pathsToRemove *[]string) error {
    createdProjectDirsLinks, err := getLinks(filepath.Join(GetCreatedTmpDirs(), projectsServiceDir))
    if err != nil {
        return fmt.Errorf("unable to get created tmp projects dirs: %w", err)
    }

    linksToRemove, err := getCreatedFilesToRemove(createdProjectDirsLinks)
    if err != nil {
        return fmt.Errorf("cannot get created tmp files to remove: %w", err)
    }

    dirs, err := readLinks(linksToRemove)
    if err != nil {
        return fmt.Errorf("unable to read links: %w", err)
    }
    *projectDirsToRemove = append(*projectDirsToRemove, dirs...)

    for _, link := range linksToRemove {
        *pathsToRemove = append(*pathsToRemove, link.LinkPath)
    }

    return nil
}

// Remove only these created docker configs, which can be removed
func gcCreatedDockerConfigs(pathsToRemove *[]string) error {
    createdDockerConfigsLinks, err := getLinks(filepath.Join(GetCreatedTmpDirs(), dockerConfigsServiceDir))
    if err != nil {
        return fmt.Errorf("unable to get created tmp docker configs: %w", err)
    }

    linksToRemove, err := getCreatedFilesToRemove(createdDockerConfigsLinks)
    if err != nil {
        return fmt.Errorf("cannot get created tmp files to remove: %w", err)
    }

    dirs, err := readLinks(linksToRemove)
    if err != nil {
        return fmt.Errorf("unable to read links: %w", err)
    }
    *pathsToRemove = append(*pathsToRemove, dirs...)

    for _, link := range linksToRemove {
        *pathsToRemove = append(*pathsToRemove, link.LinkPath)
    }

    return nil
}

// Remove only these created kubeconfigs, which can be removed
func gcCreatedKubeConfigs(pathsToRemove *[]string) error {
    kubeConfigs, err := getLinks(filepath.Join(GetCreatedTmpDirs(), kubeConfigsServiceDir))
    if err != nil {
        return fmt.Errorf("unable to get created tmp kubeconfigs: %w", err)
    }

    linksToRemove, err := getCreatedFilesToRemove(kubeConfigs)
    if err != nil {
        return fmt.Errorf("cannot get created tmp files to remove: %w", err)
    }

    kubeConfigsToRemove, err := readLinks(linksToRemove)
    if err != nil {
        return fmt.Errorf("unable to read kubeconfigs links: %w", err)
    }

    *pathsToRemove = append(*pathsToRemove, kubeConfigsToRemove...)
    for _, link := range linksToRemove {
        *pathsToRemove = append(*pathsToRemove, link.LinkPath)
    }

    return nil
}

// Remove only these created werf config render files, which can be removed
func gcCreatedWerfConfigRenders(pathsToRemove *[]string) error {
    createdWerfConfigRendersLinks, err := getLinks(filepath.Join(GetCreatedTmpDirs(), werfConfigRendersServiceDir))
    if err != nil {
        return fmt.Errorf("unable to get created tmp werf config render files: %w", err)
    }

    linksToRemove, err := getCreatedFilesToRemove(createdWerfConfigRendersLinks)
    if err != nil {
        return fmt.Errorf("cannot get created tmp files to remove: %w", err)
    }

    dirs, err := readLinks(linksToRemove)
    if err != nil {
        return fmt.Errorf("unable to read links: %w", err)
    }
    *pathsToRemove = append(*pathsToRemove, dirs...)

    for _, link := range linksToRemove {
        *pathsToRemove = append(*pathsToRemove, link.LinkPath)
    }

    return nil
}

type LinkDesc struct {
    FileInfo os.FileInfo
    LinkPath string
}

func getLinks(dir string) ([]*LinkDesc, error) {
    var res []*LinkDesc

    if _, err := os.Stat(dir); !os.IsNotExist(err) {
        infos, err := ioutil.ReadDir(dir)
        if err != nil {
            return nil, fmt.Errorf("unable to list files in %s: %w", dir, err)
        }

        for _, info := range infos {
            res = append(res, &LinkDesc{FileInfo: info, LinkPath: filepath.Join(dir, info.Name())})
        }
    }

    return res, nil
}

func readLinks(links []*LinkDesc) ([]string, error) {
    var res []string

    for _, desc := range links {
        origDir, err := os.Readlink(desc.LinkPath)
        if err != nil {
            return nil, fmt.Errorf("unable to read link %s: %w", desc.LinkPath, err)
        }
        res = append(res, origDir)
    }

    return res, nil
}

func getCreatedFilesToRemove(createdFiles []*LinkDesc) ([]*LinkDesc, error) {
    var res []*LinkDesc

    now := time.Now()

    for _, desc := range createdFiles {
        if now.Sub(desc.FileInfo.ModTime()) < 2*time.Hour {
            continue
        }

        res = append(res, desc)
    }

    return res, nil
}