client/pkg/repo/client.go

Summary

Maintainability
A
1 hr
Test Coverage
D
60%
package repo

import (
    "fmt"
    "io/ioutil"
    "os"
    "path"
    "path/filepath"
    "strings"

    "github.com/werf/lockgate"
    "github.com/werf/lockgate/pkg/file_locker"
    "github.com/werf/trdl/client/pkg/tuf"
    "github.com/werf/trdl/client/pkg/util"
)

const (
    targetsChannels = "channels"
    targetsReleases = "releases"

    channelsDir = targetsChannels
    releasesDir = targetsReleases
    scriptsDir  = "scripts"
)

type Client struct {
    repoName    string
    dir         string
    tmpDir      string
    logsDir     string
    metafileDir string
    tufClient   TufInterface
    locker      lockgate.Locker
}

func NewClient(repoName, dir, repoUrl, locksPath, tmpDir, logsDir, metafileDir string) (Client, error) {
    c := Client{
        repoName:    repoName,
        dir:         dir,
        tmpDir:      tmpDir,
        logsDir:     logsDir,
        metafileDir: metafileDir,
    }

    if err := c.init(repoUrl, locksPath); err != nil {
        return c, err
    }

    return c, nil
}

func (c *Client) init(repoUrl, locksPath string) error {
    if err := c.initFileLocker(locksPath); err != nil {
        return fmt.Errorf("unable to init file locker: %w", err)
    }

    if err := c.initTufClient(repoUrl, locksPath); err != nil {
        return fmt.Errorf("unable to init tuf client: %w", err)
    }

    if err := os.MkdirAll(c.logsDir, os.ModePerm); err != nil {
        return fmt.Errorf("unable to create logs directory %q: %w", c.logsDir, err)
    }

    return nil
}

func (c *Client) initTufClient(repoUrl, locksPath string) (err error) {
    tufClient, err := tuf.NewClient(repoUrl, c.metaLocalStoreDir(), filepath.Join(locksPath, "tuf"))
    if err != nil {
        return err
    }

    c.tufClient = tufClient

    return nil
}

func (c *Client) initFileLocker(locksPath string) error {
    locker, err := file_locker.NewFileLocker(locksPath)
    if err != nil {
        return err
    }

    c.locker = locker

    return nil
}

func (c Client) channelTargetName(group, channel string) string {
    return path.Join(targetsChannels, group, channel)
}

func (c Client) releaseTargetNamePrefix(release string) string {
    return path.Join(targetsReleases, release)
}

func (c Client) channelPath(group, channel string) string {
    return filepath.Join(c.dir, channelsDir, group, channel)
}

func (c Client) channelReleaseDir(releaseName string) string {
    return filepath.Join(c.dir, releasesDir, releaseName)
}

func (c Client) channelScriptsDir(group, channel string) string {
    return filepath.Join(c.dir, scriptsDir, strings.Join([]string{group, channel}, "-"))
}

func (c Client) channelTmpPath(group, channel string) string {
    return filepath.Join(c.tmpDir, channelsDir, group, channel)
}

func (c Client) channelReleaseTmpDir(releaseName string) string {
    return filepath.Join(c.tmpDir, releasesDir, releaseName)
}

func (c Client) channelScriptsTmpDir(group, channel string) string {
    return filepath.Join(c.tmpDir, scriptsDir, strings.Join([]string{group, channel}, "-"))
}

func (c Client) findChannelReleaseBinPath(group, channel, optionalBinName string) (string, error) {
    dir, releaseName, err := c.findChannelReleaseBinDir(group, channel)
    if err != nil {
        return "", err
    }

    var glob string
    if optionalBinName == "" {
        glob = filepath.Join(dir, "*")
    } else {
        glob = filepath.Join(dir, optionalBinName)
    }

    matches, err := filepath.Glob(glob)
    if err != nil {
        return "", fmt.Errorf("unable to glob files: %w", err)
    }

    if len(matches) > 1 {
        var names []string
        for _, m := range matches {
            names = append(names, strings.TrimPrefix(m, dir+string(os.PathSeparator)))
        }

        return "", NewChannelReleaseSeveralFilesFoundError(c.repoName, group, channel, releaseName, names)
    } else if len(matches) == 0 {
        if optionalBinName == "" {
            return "", fmt.Errorf("binary file not found in release")
        } else {
            return "", fmt.Errorf("binary file %q not found in release", optionalBinName)
        }
    }

    return matches[0], nil
}

func (c Client) findChannelReleaseBinDir(group, channel string) (dir, release string, err error) {
    releaseDir, releaseName, err := c.findChannelReleaseDir(group, channel)
    if err != nil {
        return "", "", err
    }

    binDir := filepath.Join(releaseDir, "bin")
    exist, err := util.IsDirExist(binDir)
    if err != nil {
        return "", "", fmt.Errorf("unable to check existence of directory %q: %w", binDir, err)
    }

    if !exist {
        return "", "", fmt.Errorf("bin directory not found in the release %q directory (group: %q, channel: %q)", releaseName, group, channel)
    }

    return binDir, releaseName, nil
}

func (c Client) findChannelReleaseDir(group, channel string) (dir, release string, err error) {
    release, err = c.GetChannelRelease(group, channel)
    if err != nil {
        return "", "", err
    }

    dirGlob := filepath.Join(c.dir, releasesDir, release, "*")

    matches, err := filepath.Glob(dirGlob)
    if err != nil {
        return "", "", fmt.Errorf("unable to glob files: %w", err)
    }

    if len(matches) > 1 {
        return "", "", fmt.Errorf("unexpected files in release directory:\n - %s", strings.Join(matches, "\n - "))
    } else if len(matches) == 0 {
        return "", "", NewChannelReleaseNotFoundLocallyError(c.repoName, group, channel, release)
    }

    return matches[0], release, nil
}

func (c Client) GetChannelRelease(group, channel string) (string, error) {
    channelFilePath := c.channelPath(group, channel)
    exist, err := util.IsRegularFileExist(channelFilePath)
    if err != nil {
        return "", fmt.Errorf("unable to check existence of file %q: %w", channelFilePath, err)
    }

    if !exist {
        return "", NewChannelNotFoundLocallyError(c.repoName, group, channel)
    }

    release, err := readChannelRelease(channelFilePath)
    if err != nil {
        return "", err
    }

    if err := c.releaseMetafile(release).Reset(c.locker); err != nil {
        return "", fmt.Errorf("unable to reset release metafile: %w", err)
    }

    return release, nil
}

func readChannelRelease(path string) (string, error) {
    channelData, err := ioutil.ReadFile(path)
    if err != nil {
        return "", fmt.Errorf("unable to read file %q: %w", path, err)
    }

    releaseName := strings.TrimSpace(string(channelData))
    return releaseName, nil
}

func (c Client) metaLocalStoreDir() string {
    return filepath.Join(c.dir, ".meta")
}

func (c Client) channelLockName(group, channel string) string {
    return fmt.Sprintf("%s-%s", group, channel)
}

func (c Client) updateChannelLockName(group, channel string) string {
    return fmt.Sprintf("update-channel-%s-%s", group, channel)
}

func (c Client) updateReleaseLockName(release string) string {
    return fmt.Sprintf("update-release-%s", release)
}

func (c Client) releaseMetafile(release string) util.Metafile {
    filePath := filepath.Join(c.metafileDir, "releases", release)
    return util.NewMetafile(filePath)
}