Skip to content

Commit

Permalink
feat: add hash to lockfile to support headless installs (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
marwanhawari authored Apr 7, 2024
1 parent ce6d663 commit 484b5f6
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 60 deletions.
17 changes: 9 additions & 8 deletions cmd/browse.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,21 @@ func Browse(cliInput string) {
stew.CatchAndExit(err)
fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath))

binaryName, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, false, "")
binaryName, binaryHash, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, false, "", "")
if err != nil {
os.RemoveAll(downloadPath)
stew.CatchAndExit(err)
}

packageData := stew.PackageData{
Source: "github",
Owner: githubProject.Owner,
Repo: githubProject.Repo,
Tag: tag,
Asset: asset,
Binary: binaryName,
URL: downloadURL,
Source: "github",
Owner: githubProject.Owner,
Repo: githubProject.Repo,
Tag: tag,
Asset: asset,
Binary: binaryName,
URL: downloadURL,
BinaryHash: binaryHash,
}

lockFile.Packages = append(lockFile.Packages, packageData)
Expand Down
34 changes: 19 additions & 15 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func installOne(pkg stew.PackageData, userOS, userArch string, systemInfo stew.S
tag := pkg.Tag
asset := pkg.Asset
desiredBinaryRename := pkg.Binary
expectedBinaryHash := pkg.BinaryHash
downloadURL := pkg.URL

lockFile, err := stew.NewLockFile(stewLockFilePath, userOS, userArch)
Expand Down Expand Up @@ -126,33 +127,36 @@ func installOne(pkg stew.PackageData, userOS, userArch string, systemInfo stew.S
}
fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath))

binaryName, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, installingFromLockFile, desiredBinaryRename)
binaryName, binaryHash, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, installingFromLockFile, desiredBinaryRename, expectedBinaryHash)
if err != nil {
if err := os.RemoveAll(downloadPath); err != nil {
return err
}
return err
}

var packageData stew.PackageData
if source == "github" {
packageData = stew.PackageData{
Source: "github",
Owner: githubProject.Owner,
Repo: githubProject.Repo,
Tag: tag,
Asset: asset,
Binary: binaryName,
URL: downloadURL,
Source: "github",
Owner: githubProject.Owner,
Repo: githubProject.Repo,
Tag: tag,
Asset: asset,
Binary: binaryName,
URL: downloadURL,
BinaryHash: binaryHash,
}
} else {
packageData = stew.PackageData{
Source: "other",
Owner: "",
Repo: "",
Tag: "",
Asset: asset,
Binary: binaryName,
URL: downloadURL,
Source: "other",
Owner: "",
Repo: "",
Tag: "",
Asset: asset,
Binary: binaryName,
URL: downloadURL,
BinaryHash: binaryHash,
}
}

Expand Down
4 changes: 3 additions & 1 deletion cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,18 @@ func upgradeOne(binaryName, userOS, userArch string, lockFile stew.LockFile, sys
}
fmt.Printf("✅ Downloaded %v to %v\n", constants.GreenColor(asset), constants.GreenColor(stewPkgPath))

_, err = stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, true, pkg.Binary)
_, binaryHash, err := stew.InstallBinary(downloadPath, repo, systemInfo, &lockFile, true, pkg.Binary, pkg.BinaryHash)
if err != nil {
if err := os.RemoveAll(downloadPath); err != nil {
return err
}
return err
}

lockFile.Packages[indexInLockFile].Tag = tag
lockFile.Packages[indexInLockFile].Asset = asset
lockFile.Packages[indexInLockFile].URL = downloadURL
lockFile.Packages[indexInLockFile].BinaryHash = binaryHash
if err := stew.WriteLockFileJSON(lockFile, stewLockFilePath); err != nil {
return err
}
Expand Down
33 changes: 26 additions & 7 deletions lib/stewfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package stew

import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"

Expand All @@ -19,13 +22,14 @@ type LockFile struct {

// PackageData contains the information for an installed binary
type PackageData struct {
Source string `json:"source"`
Owner string `json:"owner"`
Repo string `json:"repo"`
Tag string `json:"tag"`
Asset string `json:"asset"`
Binary string `json:"binary"`
URL string `json:"url"`
Source string `json:"source"`
Owner string `json:"owner"`
Repo string `json:"repo"`
Tag string `json:"tag"`
Asset string `json:"asset"`
Binary string `json:"binary"`
URL string `json:"url"`
BinaryHash string `json:"binaryHash"`
}

func readLockFileJSON(lockFilePath string) (LockFile, error) {
Expand Down Expand Up @@ -143,3 +147,18 @@ func DeleteAssetAndBinary(stewPkgPath, stewBinPath, asset, binary string) error
}
return nil
}

func CalculateFileHash(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()

hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
return "", err
}

return hex.EncodeToString(hasher.Sum(nil)), nil
}
46 changes: 29 additions & 17 deletions lib/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,38 +139,50 @@ func walkDir(rootDir string) ([]string, error) {
type ExecutableFileInfo struct {
fileName string
filePath string
fileHash string
}

func getBinary(filePaths []string, desiredBinaryRename string) (string, string, error) {
func getBinary(filePaths []string, desiredBinaryRename, expectedBinaryHash string) (string, string, string, error) {
executableFiles := []ExecutableFileInfo{}
for _, fullPath := range filePaths {
fileNameBase := filepath.Base(fullPath)
fileIsExecutable, err := isExecutableFile(fullPath)
if err != nil {
return "", "", err
return "", "", "", err
}
fileHash, err := CalculateFileHash(fullPath)
if err != nil {
return "", "", "", err
}
if desiredBinaryRename != "" && expectedBinaryHash != "" && expectedBinaryHash == fileHash {
return fullPath, desiredBinaryRename, expectedBinaryHash, nil
}
if !fileIsExecutable {
continue
}
executableFiles = append(executableFiles, ExecutableFileInfo{fileName: fileNameBase, filePath: fullPath})
executableFiles = append(executableFiles, ExecutableFileInfo{fileName: fileNameBase, filePath: fullPath, fileHash: fileHash})
}

if len(executableFiles) != 1 {
binaryFilePath, err := WarningPromptSelect("Could not automatically detect the binary. Please select it manually:", filePaths)
if err != nil {
return "", "", err
return "", "", "", err
}
binaryName, err := PromptRenameBinary(filepath.Base(binaryFilePath))
if err != nil {
return "", "", nil
return "", "", "", err
}
return binaryFilePath, binaryName, nil
binaryHash, err := CalculateFileHash(binaryFilePath)
if err != nil {
return "", "", "", err
}
return binaryFilePath, binaryName, binaryHash, nil
}

if desiredBinaryRename != "" {
return executableFiles[0].filePath, desiredBinaryRename, nil
return executableFiles[0].filePath, desiredBinaryRename, executableFiles[0].fileHash, nil
}
return executableFiles[0].filePath, executableFiles[0].fileName, nil
return executableFiles[0].filePath, executableFiles[0].fileName, executableFiles[0].fileHash, nil
}

// ValidateCLIInput makes sure the CLI input isn't empty
Expand Down Expand Up @@ -275,37 +287,37 @@ func extractBinary(downloadedFilePath, tmpExtractionPath, desiredBinaryRename st
}

// InstallBinary will extract the binary and copy it to the ~/.stew/bin path
func InstallBinary(downloadedFilePath string, repo string, systemInfo SystemInfo, lockFile *LockFile, overwriteFromUpgrade bool, desiredBinaryRename string) (string, error) {
func InstallBinary(downloadedFilePath string, repo string, systemInfo SystemInfo, lockFile *LockFile, overwriteFromUpgrade bool, desiredBinaryRename, expectedBinaryHash string) (string, string, error) {
tmpExtractionPath, stewPkgPath, binaryInstallPath := systemInfo.StewTmpPath, systemInfo.StewPkgPath, systemInfo.StewBinPath
if err := extractBinary(downloadedFilePath, tmpExtractionPath, desiredBinaryRename); err != nil {
return "", err
return "", "", err
}

allFilePaths, err := walkDir(tmpExtractionPath)
if err != nil {
return "", err
return "", "", err
}

binaryFileInTmpExtractionPath, binaryName, err := getBinary(allFilePaths, desiredBinaryRename)
binaryFileInTmpExtractionPath, binaryName, binaryHash, err := getBinary(allFilePaths, desiredBinaryRename, expectedBinaryHash)
if err != nil {
return "", err
return "", "", err
}

if err = handleExistingBinary(lockFile, binaryName, downloadedFilePath, stewPkgPath, overwriteFromUpgrade); err != nil {
return "", err
return "", "", err
}

err = copyFile(binaryFileInTmpExtractionPath, filepath.Join(binaryInstallPath, binaryName))
if err != nil {
return "", err
return "", "", err
}

err = os.RemoveAll(tmpExtractionPath)
if err != nil {
return "", err
return "", "", err
}

return binaryName, nil
return binaryName, binaryHash, nil
}

func handleExistingBinary(lockFile *LockFile, binaryName, newlyDownloadedAssetPath, stewPkgPath string, overwriteFromUpgrade bool) error {
Expand Down
28 changes: 16 additions & 12 deletions lib/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,17 +316,21 @@ func Test_getBinary(t *testing.T) {

wantBinaryFile := filepath.Join(tempDir, tt.binaryName)
wantBinaryName := filepath.Base(wantBinaryFile)
wantBinaryHash, _ := CalculateFileHash(wantBinaryFile)

got, got1, err := getBinary(testFilePaths, "")
gotBinaryFile, gotBinaryName, gotBinaryHash, err := getBinary(testFilePaths, "", "")
if (err != nil) != tt.wantErr {
t.Errorf("getBinary() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != wantBinaryFile {
t.Errorf("getBinary() got = %v, want %v", got, wantBinaryFile)
if gotBinaryFile != wantBinaryFile {
t.Errorf("getBinary() gotBinaryFile = %v, want %v", gotBinaryFile, wantBinaryFile)
}
if got1 != wantBinaryName {
t.Errorf("getBinary() got1 = %v, want %v", got1, wantBinaryName)
if gotBinaryName != wantBinaryName {
t.Errorf("getBinary() gotBinaryName = %v, want %v", gotBinaryName, wantBinaryName)
}
if gotBinaryHash != wantBinaryHash {
t.Errorf("getBinary() gotBinaryHash = %v, want %v", gotBinaryHash, wantBinaryHash)
}
})
}
Expand Down Expand Up @@ -363,16 +367,16 @@ func Test_getBinaryError(t *testing.T) {
wantBinaryFile := ""
wantBinaryName := ""

got, got1, err := getBinary(testFilePaths, "")
gotBinaryFile, gotBinaryName, _, err := getBinary(testFilePaths, "", "")
if (err != nil) != tt.wantErr {
t.Errorf("getBinary() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != wantBinaryFile {
t.Errorf("getBinary() got = %v, want %v", got, wantBinaryFile)
if gotBinaryFile != wantBinaryFile {
t.Errorf("getBinary() gotBinaryFile = %v, want %v", gotBinaryFile, wantBinaryFile)
}
if got1 != wantBinaryName {
t.Errorf("getBinary() got1 = %v, want %v", got1, wantBinaryName)
if gotBinaryName != wantBinaryName {
t.Errorf("getBinary() gotBinaryName = %v, want %v", gotBinaryName, wantBinaryName)
}
})
}
Expand Down Expand Up @@ -689,7 +693,7 @@ func TestInstallBinary(t *testing.T) {
t.Errorf("Could not download file to %v", downloadedFilePath)
}

got, err := InstallBinary(downloadedFilePath, repo, systemInfo, &lockFile, true, "")
got, _, err := InstallBinary(downloadedFilePath, repo, systemInfo, &lockFile, true, "", "")
if (err != nil) != tt.wantErr {
t.Errorf("InstallBinary() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -753,7 +757,7 @@ func TestInstallBinary_Fail(t *testing.T) {
t.Errorf("Could not download file to %v", downloadedFilePath)
}

got, err := InstallBinary(downloadedFilePath, repo, systemInfo, &lockFile, false, "")
got, _, err := InstallBinary(downloadedFilePath, repo, systemInfo, &lockFile, false, "", "")
if (err != nil) != tt.wantErr {
t.Errorf("InstallBinary() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down

0 comments on commit 484b5f6

Please sign in to comment.