147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
func WingetInstallArgs(pkg Package) []string {
|
|
args := []string{
|
|
"install", "--id", pkg.WingetID, "--exact", "--source", pkg.WingetSource,
|
|
"--accept-source-agreements", "--accept-package-agreements", "--disable-interactivity",
|
|
}
|
|
if pkg.WingetOverride != "" {
|
|
args = append(args, "--override", pkg.WingetOverride)
|
|
}
|
|
return args
|
|
}
|
|
|
|
func WingetListArgs(pkg Package) []string {
|
|
return []string{
|
|
"list", "--id", pkg.WingetID, "--exact", "--source", pkg.WingetSource,
|
|
"--accept-source-agreements", "--disable-interactivity",
|
|
}
|
|
}
|
|
|
|
func WingetOutputShowsInstalled(pkg Package, output string) bool {
|
|
lower := strings.ToLower(output)
|
|
if strings.Contains(lower, "no installed package found") || strings.Contains(lower, "no package found") {
|
|
return false
|
|
}
|
|
return strings.Contains(lower, strings.ToLower(pkg.WingetID))
|
|
}
|
|
|
|
type PackageState struct {
|
|
Package Package
|
|
Installed bool
|
|
CheckErr error
|
|
}
|
|
|
|
func CheckPackages(ctx context.Context, runner Runner, packages []Package, workers int) []PackageState {
|
|
if workers < 1 {
|
|
workers = 1
|
|
}
|
|
states := make([]PackageState, len(packages))
|
|
type job struct {
|
|
index int
|
|
pkg Package
|
|
}
|
|
jobs := make(chan job)
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < workers; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for current := range jobs {
|
|
result := runner.Run(ctx, "winget", WingetListArgs(current.pkg)...)
|
|
state := PackageState{Package: current.pkg}
|
|
if result.Err != nil && !strings.Contains(strings.ToLower(result.CombinedOutput()), "no installed package found") {
|
|
state.CheckErr = fmt.Errorf("%s: %w", result.CombinedOutput(), result.Err)
|
|
} else {
|
|
state.Installed = WingetOutputShowsInstalled(current.pkg, result.CombinedOutput())
|
|
}
|
|
states[current.index] = state
|
|
}
|
|
}()
|
|
}
|
|
for i, pkg := range packages {
|
|
jobs <- job{index: i, pkg: pkg}
|
|
}
|
|
close(jobs)
|
|
wg.Wait()
|
|
return states
|
|
}
|
|
|
|
func PendingPackages(states []PackageState) []Package {
|
|
var pending []Package
|
|
for _, state := range states {
|
|
if !state.Installed {
|
|
pending = append(pending, state.Package)
|
|
}
|
|
}
|
|
return pending
|
|
}
|
|
|
|
func UnknownPackageStates(packages []Package) []PackageState {
|
|
states := make([]PackageState, 0, len(packages))
|
|
for _, pkg := range packages {
|
|
states = append(states, PackageState{Package: pkg})
|
|
}
|
|
return states
|
|
}
|
|
|
|
func InstallPackages(ctx context.Context, runner Runner, packages []Package) Issues {
|
|
var issues Issues
|
|
for _, pkg := range packages {
|
|
prepared, err := PreparePackageForInstall(pkg)
|
|
if err != nil {
|
|
issues = append(issues, Issue{Step: "prepare " + pkg.Name, Err: err})
|
|
continue
|
|
}
|
|
result := runner.Run(ctx, "winget", WingetInstallArgs(prepared)...)
|
|
if result.Err != nil {
|
|
issues = append(issues, Issue{
|
|
Step: "install " + pkg.Name,
|
|
Err: fmt.Errorf("%w: %s", result.Err, result.CombinedOutput()),
|
|
})
|
|
}
|
|
}
|
|
return issues
|
|
}
|
|
|
|
func PreparePackageForInstall(pkg Package) (Package, error) {
|
|
if pkg.WingetConfigAsset == "" {
|
|
return pkg, nil
|
|
}
|
|
path, err := writeInstallAsset(pkg.WingetConfigAsset)
|
|
if err != nil {
|
|
return pkg, err
|
|
}
|
|
pkg.WingetOverride = strings.ReplaceAll(pkg.WingetOverride, "{config}", `"`+path+`"`)
|
|
return pkg, nil
|
|
}
|
|
|
|
func writeInstallAsset(asset string) (string, error) {
|
|
data, err := managedAssets.ReadFile(asset)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
root, err := os.UserCacheDir()
|
|
if err != nil || root == "" {
|
|
root = os.TempDir()
|
|
}
|
|
dir := filepath.Join(root, "bootstrap_windows_env")
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
target := filepath.Join(dir, filepath.Base(asset))
|
|
if err := os.WriteFile(target, data, 0o644); err != nil {
|
|
return "", err
|
|
}
|
|
return target, nil
|
|
}
|