Skip to content

Commit

Permalink
Merge pull request #34 from ksctl/feat-ksctl-self-update
Browse files Browse the repository at this point in the history
KFP: add ksctl `self-update` sub command
  • Loading branch information
dipankardas011 authored Aug 2, 2024
2 parents 074083a + 697bf3e commit 833df8b
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 12 deletions.
280 changes: 280 additions & 0 deletions cli/cmd/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package cmd

import (
"archive/tar"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"

"github.com/pterm/pterm"

"github.com/spf13/cobra"
)

func fetchLatestVersion() ([]string, error) {

logCli.Print(ctx, "Fetching available versions")

type Release struct {
TagName string `json:"tag_name"`
}

resp, err := http.Get("https://api.github.com/repos/ksctl/cli/releases")
if err != nil {
return nil, err
}
defer resp.Body.Close()

var _releases []Release
if err := json.NewDecoder(resp.Body).Decode(&_releases); err != nil {
return nil, err
}

var releases []string
rcRegex := regexp.MustCompile(`.*-rc[0-9]+$`)
for _, release := range _releases {
if rcRegex.MatchString(release.TagName) {
continue
}
releases = append(releases, release.TagName)
}

return releases, nil
}

func filterToUpgradeableVersions(versions []string) []string {
var upgradeableVersions []string
for _, version := range versions {
if version > Version {
upgradeableVersions = append(upgradeableVersions, version)
}
}
return upgradeableVersions
}

func downloadFile(url, localFilename string) error {
logCli.Print(ctx, "Downloading file", "url", url, "localFilename", localFilename)

resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()

out, err := os.Create(localFilename)
if err != nil {
return err
}
defer out.Close()

_, err = io.Copy(out, resp.Body)
return err
}
func verifyChecksum(filePath, checksumfileLoc string) (bool, error) {
logCli.Print(ctx, "Verifying checksum", "file", filePath, "checksumfile", checksumfileLoc)

rawChecksum, err := os.ReadFile(checksumfileLoc)
if err != nil {
return false, err
}
checksums := strings.Split(string(rawChecksum), "\n")

var expectedChecksum string = "LOL"
for _, line := range checksums {
if strings.Contains(line, filePath) {
expectedChecksum = strings.Fields(line)[0]
break
}
}
if expectedChecksum == "LOL" {
return false, logCli.NewError(ctx, "Checksum not found in checksum file")
}

file, err := os.Open(filePath)
if err != nil {
return false, err
}
defer file.Close()

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

calculatedChecksum := hex.EncodeToString(hash.Sum(nil))
return calculatedChecksum == expectedChecksum, nil
}

func getOsArch() (string, error) {
arch := runtime.GOARCH

if arch != "amd64" && arch != "arm64" {
return "", logCli.NewError(ctx, "Unsupported architecture")
}
return arch, nil
}

func getOs() (string, error) {
os := runtime.GOOS

if os != "linux" && os != "darwin" {
return "", logCli.NewError(ctx, "Unsupported OS", "message", "will provide support for windows based OS soon")
}
return os, nil
}

func update(version string) error {
osName, err := getOs()
if err != nil {
return err
}
archName, err := getOsArch()
if err != nil {
return err
}

logCli.Print(ctx, "Delected System", "OS", osName, "Arch", archName)
downloadURLBase := fmt.Sprintf("https://github.com/ksctl/cli/releases/download/%s", version)
tarFile := fmt.Sprintf("ksctl-cli_%s_%s_%s.tar.gz", version[1:], osName, archName)
checksumFile := fmt.Sprintf("ksctl-cli_%s_checksums.txt", version[1:])

tarUri := fmt.Sprintf("%s/%s", downloadURLBase, tarFile)
checksumUri := fmt.Sprintf("%s/%s", downloadURLBase, checksumFile)

defer func() {
logCli.Print(ctx, "Cleaning up")
if err := os.Remove(checksumFile); err != nil {
logCli.Error("Failed to remove checksum file", "error", err)
}

if err := os.Remove(tarFile); err != nil {
logCli.Error("Failed to remove checksum file", "error", err)
}
}()

if err := downloadFile(tarUri, tarFile); err != nil {
return err
}

if err := downloadFile(checksumUri, checksumFile); err != nil {
return err
}

match, err := verifyChecksum(tarFile, checksumFile)
if err != nil {
return logCli.NewError(ctx, "Failed to verify checksum", "error", err)
}
if !match {
return logCli.NewError(ctx, "Checksum verification failed")
}
logCli.Success(ctx, "Checksum verification successful")

tempDir, err := os.MkdirTemp("", "ksctl-update")
if err != nil {
return logCli.NewError(ctx, "Failed to create temp dir", "error", err)
}
file, err := os.Open(tarFile)
if err != nil {
return logCli.NewError(ctx, "Failed to open tar file", "error", err)
}
defer file.Close()

gzr, err := gzip.NewReader(file)
if err != nil {
return logCli.NewError(ctx, "Failed to read gzip file", "error", err)
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return logCli.NewError(ctx, "Failed to read tar file", "error", err)
}
if header.Name == "ksctl" {
outFile, err := os.Create(filepath.Join(tempDir, "ksctl"))
if err != nil {
return logCli.NewError(ctx, "Failed to create ksctl binary", "error", err)
}
defer outFile.Close()

if _, err := io.Copy(outFile, tr); err != nil {
return logCli.NewError(ctx, "Failed to copy ksctl binary", "error", err)
}
break
}
}

logCli.Print(ctx, "Making ksctl executable...")
if err := os.Chmod(filepath.Join(tempDir, "ksctl"), 0550); err != nil {
return logCli.NewError(ctx, "Failed to make ksctl executable", "error", err)
}

logCli.Print(ctx, "Moving ksctl to /usr/local/bin (requires sudo)...")
cmd := exec.Command("sudo", "mv", "-v", filepath.Join(tempDir, "ksctl"), "/usr/local/bin/ksctl")
err = cmd.Run()
if err != nil {
return logCli.NewError(ctx, "Failed to move ksctl to /usr/local/bin", "error", err)
}

_, err = exec.LookPath("ksctl")
if err != nil {
return logCli.NewError(ctx, "Failed to find ksctl in PATH", "error", err)
}

return nil
}

var selfUpdate = &cobra.Command{
Use: "self-update",
Short: "update the ksctl cli",
Long: "setups up update for ksctl cli",
Run: func(cmd *cobra.Command, args []string) {

if Version == "dev" {
logCli.Error("Cannot update dev version", "msg", "Please use a stable version to update")
os.Exit(1)
}

logCli.Warn(ctx, "Currently no migrations are supported", "msg", "Please help us by creating a PR to support migrations. Thank you!")

vers, err := fetchLatestVersion()
if err != nil {
logCli.Error("Failed to fetch latest version", "error", err)
os.Exit(1)
}
vers = filterToUpgradeableVersions(vers)

logCli.Print(ctx, "Available versions to update")
selectedOption, _ := pterm.DefaultInteractiveSelect.WithOptions(vers).Show()

newVer := selectedOption

if err := update(newVer); err != nil {
logCli.Error("Failed to update ksctl cli", "error", err)
os.Exit(1)
}

logCli.Success(ctx, "Updated Ksctl cli", "previousVer", Version, "newVer", newVer)
logCli.Note(ctx, "Please restart your terminal to use the updated version")
},
}

func init() {
RootCmd.AddCommand(selfUpdate)
storageFlag(selfUpdate)

selfUpdate.Flags().BoolP("verbose", "v", true, "for verbose output")
}
13 changes: 1 addition & 12 deletions cli/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@ const (
| <\__ \ (__| |_| |
|_|\_\___/\___|\__|_|
`

v1_0Ksctl = `
__ __ .__
| | __ ______ ____ _/ |_ | |
| |/ / / ___/_/ ___\\ __\| |
| < \___ \ \ \___ | | | |__
|__|_ \/____ > \___ >|__| |____/
\/ \/ \/
`

v2_0Ksctl = `
Expand All @@ -48,9 +39,7 @@ var versionCmd = &cobra.Command{
Short: "Print the version number of ksctl",
Run: func(cmd *cobra.Command, args []string) {

fmt.Println(v0_1Ksctl)

color.HiGreen(v1_0Ksctl)
color.HiGreen(v0_1Ksctl)

x := strings.Split(v2_0Ksctl, "\n")

Expand Down
3 changes: 3 additions & 0 deletions logger/general_logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,10 @@ func (l *GeneralLog) Table(ctx context.Context, op consts.LogClusterDetail, data
)
}

println()
tbl.Print()
println()
println()
} else if op == consts.LoggingInfoCluster {
a, err := json.MarshalIndent(data[0], "", " ")
if err != nil {
Expand Down

0 comments on commit 833df8b

Please sign in to comment.