client/pkg/repo/use.go

Summary

Maintainability
A
0 mins
Test Coverage
D
69%
package repo

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

    "github.com/werf/trdl/client/pkg/util"
)

func (c Client) UseChannelReleaseBinDir(group, channel, shell string, opts UseSourceOptions) (string, error) {
    name, data := c.prepareSourceScriptFileNameAndData(group, channel, shell, opts)
    sourceScriptPath, err := c.syncSourceScriptFile(group, channel, name, data)
    if err != nil {
        return "", err
    }

    return sourceScriptPath, nil
}

type UseSourceOptions struct {
    NoSelfUpdate bool
}

func (c Client) prepareSourceScriptFileNameAndData(group, channel, shell string, opts UseSourceOptions) (string, []byte) {
    basename := c.prepareSourceScriptBasename(group, channel, shell, opts)
    logPathBackgroundUpdateStdout := filepath.Join(c.logsDir, basename+"_background_update_stdout.log")
    logPathBackgroundUpdateStderr := filepath.Join(c.logsDir, basename+"_background_update_stderr.log")

    commonArgs := []string{c.repoName, group, channel}
    foregroundUpdateArgs := commonArgs[0:]
    backgroundUpdateArgs := append(
        append([]string{}, commonArgs[0:]...),
        "--in-background",
        fmt.Sprintf("--background-stdout-file=%q", logPathBackgroundUpdateStdout),
        fmt.Sprintf("--background-stderr-file=%q", logPathBackgroundUpdateStderr),
    )

    if opts.NoSelfUpdate {
        foregroundUpdateArgs = append(foregroundUpdateArgs, "--no-self-update")
        backgroundUpdateArgs = append(backgroundUpdateArgs, "--no-self-update")
    }

    commonArgsString := strings.Join(commonArgs, " ")
    foregroundUpdateArgsString := strings.Join(foregroundUpdateArgs, " ")
    backgroundUpdateArgsString := strings.Join(backgroundUpdateArgs, " ")
    _ = logPathBackgroundUpdateStderr
    trdlBinaryPath := os.Args[0]
    trdlUseRepoGroupChannelEnvName := fmt.Sprintf("TRDL_USE_%s_GROUP_CHANNEL", strings.ToUpper(c.repoName))
    trdlUseRepoGroupChannelEnvValue := fmt.Sprintf("%s %s", group, channel)

    var tmpl string
    var ext string
    switch shell {
    case "pwsh":
        ext = "ps1"
        tmpl = `
if (Test-Path %[4]q -PathType Leaf) {
  $trdlStderrLog = Get-Content %[4]q
  if (!([String]::IsNullOrWhiteSpace($trdlStderrLog))) {
    'Previous run of "trdl update" in background generated following errors:'
    $trdlStderrLog
  }
}

if ((Invoke-Expression -Command "%[5]s bin-path %[1]s" 2> $null | Out-String -OutVariable trdlRepoBinPath) -and ($LastExitCode -eq 0)) {
   %[5]s update %[3]s
} else {
   %[5]s update %[2]s
   $trdlRepoBinPath = %[5]s bin-path %[1]s
}

[System.Environment]::SetEnvironmentVariable('%[6]s','%[7]s',[System.EnvironmentVariableTarget]::Process);

$trdlRepoBinPath = $trdlRepoBinPath.Trim()
$oldPath = [System.Environment]::GetEnvironmentVariable('PATH',[System.EnvironmentVariableTarget]::Process)
$newPath = "$trdlRepoBinPath;$oldPath"
[System.Environment]::SetEnvironmentVariable('Path',$newPath,[System.EnvironmentVariableTarget]::Process);
`
    default: // unix shell
        ext = ""
        tmpl = `
if [ -s %[4]q ]; then
   echo Previous run of "trdl update" in background generated following errors:
   cat %[4]q
fi

if trdl_repo_bin_path="$(%[5]q bin-path %[1]s 2>/dev/null)"; then
   %[5]q update %[3]s
else
   %[5]q update %[2]s
   trdl_repo_bin_path="$(%[5]q bin-path %[1]s)"
fi

export %[6]s="%[7]s"

export PATH="$trdl_repo_bin_path${PATH:+:${PATH}}"
`
    }

    script := fmt.Sprintf(tmpl,
        commonArgsString,                // %[1]s: REPO GROUP CHANNEL            (common args string)
        foregroundUpdateArgsString,      // %[2]s: REPO GROUP CHANNEL [flag ...] (foreground update args string)
        backgroundUpdateArgsString,      // %[3]s: REPO GROUP CHANNEL [flag ...] (background update args string)
        logPathBackgroundUpdateStderr,   // %[4]s: <path>                        (background update error file path)
        trdlBinaryPath,                  // %[5]s: <path>                        (trdl binary path)
        trdlUseRepoGroupChannelEnvName,  // %[6]s: <env name>                    (TRDL_USE_<REPO>_GROUP_CHANNEL)
        trdlUseRepoGroupChannelEnvValue, // %[7]s: <env value>                   (TRDL_USE_<REPO>_GROUP_CHANNEL value)
    )

    name := "source_script"
    if ext != "" {
        name = strings.Join([]string{name, ext}, ".")
    }

    data := []byte(fmt.Sprintln(strings.TrimSpace(script)))

    return name, data
}

func (c Client) prepareSourceScriptBasename(group, channel, shell string, opts UseSourceOptions) string {
    basename := fmt.Sprintf("use_%s_%s_%s", group, channel, shell)

    if opts.NoSelfUpdate {
        basename += "_" + util.MurmurHash(fmt.Sprintf("%+v", opts))
    }

    return basename
}

func (c Client) syncSourceScriptFile(group, channel, name string, data []byte) (string, error) {
    scriptPath := filepath.Join(c.channelScriptsDir(group, channel), name)
    scriptTmpPath := filepath.Join(c.channelScriptsTmpDir(group, channel), name)

    exist, err := util.IsRegularFileExist(scriptPath)
    if err != nil {
        return "", fmt.Errorf("unable to check existence of file %q: %w", scriptPath, err)
    }

    if exist {
        currentData, err := ioutil.ReadFile(scriptPath)
        if err != nil {
            return "", fmt.Errorf("unable to read file %q: %w", scriptPath, err)
        }

        if bytes.Equal(currentData, data) {
            return scriptPath, nil
        }
    }

    // create tmp file
    {
        if err := os.MkdirAll(filepath.Dir(scriptTmpPath), 0o755); err != nil {
            return "", err
        }

        if err := ioutil.WriteFile(scriptTmpPath, data, os.ModePerm); err != nil {
            return "", err
        }
    }

    // rename file
    {
        if err := os.MkdirAll(filepath.Dir(scriptPath), 0o755); err != nil {
            return "", err
        }

        if err := os.Rename(scriptTmpPath, scriptPath); err != nil {
            return "", err
        }
    }

    return scriptPath, nil
}