Files
kubevpn/pkg/upgrade/upgrade.go
T
2023-02-18 15:15:33 +08:00

251 lines
5.8 KiB
Go

package upgrade
import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
goversion "github.com/hashicorp/go-version"
"github.com/schollz/progressbar/v3"
"github.com/wencaiwulue/kubevpn/pkg/util"
"k8s.io/apimachinery/pkg/util/sets"
)
// Main
// 1) get current binary version
// 2) get the latest version
// 3) compare two version decide needs to download or not
// 4) download newer version zip
// 5) unzip to temp file
// 6) check permission of putting new kubevpn back
// 7) chmod +x, move old to /temp, move new to CURRENT_FOLDER
func Main(current string, client *http.Client) error {
version, url, err := getManifest(client)
if err != nil {
return err
}
fmt.Printf("The latest version is: %s\n", version)
var cVersion, dVersion *goversion.Version
cVersion, err = goversion.NewVersion(current)
if err != nil {
return err
}
dVersion, err = goversion.NewVersion(version)
if err != nil {
return err
}
if cVersion.GreaterThanOrEqual(dVersion) {
fmt.Println("Already up to date, don't needs to upgrade")
return nil
}
var executable string
executable, err = os.Executable()
if err != nil {
return err
}
var tem *os.File
tem, err = os.Create(filepath.Join(filepath.Dir(executable), ".test"))
if tem != nil {
_ = tem.Close()
_ = os.Remove(tem.Name())
}
if os.IsPermission(err) {
util.RunWithElevated()
os.Exit(0)
} else if err != nil {
return err
}
fmt.Printf("Current version is: %s less than latest version: %s, needs to upgrade\n", cVersion, dVersion)
var temp *os.File
temp, err = os.CreateTemp("", "")
if err != nil {
return err
}
err = temp.Close()
if err != nil {
return err
}
err = download(client, url, temp.Name())
if err != nil {
return err
}
file, _ := os.CreateTemp("", "")
err = file.Close()
if err != nil {
return err
}
err = unzipKubeVPNIntoFile(temp.Name(), file.Name())
if err != nil {
return err
}
err = os.Chmod(file.Name(), 0755)
if err != nil {
return err
}
var curFolder string
curFolder, err = os.Executable()
if err != nil {
return err
}
var createTemp *os.File
createTemp, err = os.CreateTemp("", "")
if err != nil {
return err
}
err = createTemp.Close()
if err != nil {
return err
}
err = os.Remove(createTemp.Name())
if err != nil {
return err
}
err = os.Rename(curFolder, createTemp.Name())
if err != nil {
return err
}
err = os.Rename(file.Name(), curFolder)
return err
}
func getManifest(httpCli *http.Client) (version string, url string, err error) {
var resp *http.Response
resp, err = httpCli.Get("https://api.github.com/repos/wencaiwulue/kubevpn/releases/latest")
if err != nil {
err = fmt.Errorf("failed to call github api, err: %v", err)
return
}
var all []byte
all, err = io.ReadAll(resp.Body)
if err != nil {
err = fmt.Errorf("failed to read all reponse from github api, err: %v", err)
return
}
var m RootEntity
err = json.Unmarshal(all, &m)
if err != nil {
err = fmt.Errorf("failed to unmarshal reponse, err: %v", err)
return
}
version = m.TagName
for _, asset := range m.Assets {
if strings.Contains(asset.Name, runtime.GOARCH) && strings.Contains(asset.Name, runtime.GOOS) {
url = asset.BrowserDownloadUrl
break
}
}
if len(url) == 0 {
var found bool
// if os is not windows and darwin, default is linux
if !sets.New[string]("windows", "darwin").Has(runtime.GOOS) {
for _, asset := range m.Assets {
if strings.Contains(asset.Name, "linux") && strings.Contains(asset.Name, runtime.GOARCH) {
url = asset.BrowserDownloadUrl
found = true
break
}
}
}
if !found {
var link []string
for _, asset := range m.Assets {
link = append(link, asset.BrowserDownloadUrl)
}
err = fmt.Errorf("Can not found latest version url of KubeVPN, you can download it manually: \n%s", strings.Join(link, "\n"))
return
}
}
return
}
// https://api.github.com/repos/wencaiwulue/kubevpn/releases
// https://github.com/wencaiwulue/kubevpn/releases/download/v1.1.13/kubevpn-windows-arm64.exe
func download(client *http.Client, url string, filename string) error {
get, err := client.Get(url)
if err != nil {
return err
}
defer get.Body.Close()
total := float64(get.ContentLength) / 1024 / 1024
fmt.Printf("Length: 68276642 (%0.2fM)\n", total)
var f *os.File
f, err = os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return err
}
defer f.Close()
bar := progressbar.NewOptions(int(get.ContentLength),
progressbar.OptionSetWriter(os.Stdout),
progressbar.OptionEnableColorCodes(true),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(50),
progressbar.OptionOnCompletion(func() {
_, _ = fmt.Fprint(os.Stderr, "\n")
}),
progressbar.OptionSetRenderBlankState(true),
progressbar.OptionSetDescription("Writing temp file..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "=",
SaucerHead: ">",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}))
buf := make([]byte, 10<<(10*2)) // 10M
_, err = io.CopyBuffer(io.MultiWriter(f, bar), get.Body, buf)
return err
}
func unzipKubeVPNIntoFile(zipFile, filename string) error {
archive, err := zip.OpenReader(zipFile)
if err != nil {
return err
}
defer archive.Close()
var fi *zip.File
for _, f := range archive.File {
if strings.Contains(f.Name, "kubevpn") {
fi = f
break
}
}
if fi == nil {
return fmt.Errorf("can not found kubevpn")
}
err = os.MkdirAll(filepath.Dir(filename), os.ModePerm)
if err != nil {
return err
}
var dstFile *os.File
dstFile, err = os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
if err != nil {
return err
}
defer dstFile.Close()
var fileInArchive io.ReadCloser
fileInArchive, err = fi.Open()
if err != nil {
return err
}
defer fileInArchive.Close()
_, err = io.Copy(dstFile, fileInArchive)
return err
}