-
-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add system apps top-level command and Go package
Part of #654, tested on an Intel x86_64 machine. Signed-off-by: Alex Ellis (OpenFaaS Ltd) <[email protected]>
- Loading branch information
Showing
7 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
// Copyright (c) arkade author(s) 2022. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
package system | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"path" | ||
"strings" | ||
|
||
"github.com/alexellis/arkade/pkg/archive" | ||
"github.com/alexellis/arkade/pkg/env" | ||
"github.com/alexellis/arkade/pkg/get" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func MakeInstallGo() *cobra.Command { | ||
|
||
command := &cobra.Command{ | ||
Use: "go", | ||
Short: "Install Go", | ||
Long: `Install Go programming language and SDK.`, | ||
Example: ` arkade system install go | ||
arkade system install go --version v1.18.1`, | ||
SilenceUsage: true, | ||
} | ||
|
||
command.Flags().StringP("version", "v", "", "The version for Go, or leave blank for pinned version") | ||
command.Flags().String("path", "/usr/local/", "Installation path, where a go subfolder will be created") | ||
command.Flags().Bool("progress", true, "Show download progress") | ||
|
||
command.PreRunE = func(cmd *cobra.Command, args []string) error { | ||
|
||
return nil | ||
} | ||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error { | ||
installPath, _ := cmd.Flags().GetString("path") | ||
version, _ := cmd.Flags().GetString("version") | ||
fmt.Printf("Installing Go to %s\n", installPath) | ||
|
||
if err := os.MkdirAll(installPath, 0755); err != nil && !os.IsExist(err) { | ||
fmt.Printf("Error creating directory %s, error: %s\n", installPath, err.Error()) | ||
} | ||
|
||
arch, osVer := env.GetClientArch() | ||
|
||
if strings.ToLower(osVer) != "linux" { | ||
return fmt.Errorf("this app only supports Linux") | ||
} | ||
|
||
dlArch := arch | ||
if arch == "x86_64" { | ||
dlArch = "amd64" | ||
} else if arch == "aarch64" { | ||
dlArch = "arm64" | ||
} else if arch == "armv7" || arch == "armv7l" { | ||
dlArch = "armv6l" | ||
} | ||
|
||
if len(version) == 0 { | ||
v, err := getGoVersion() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
version = v | ||
} else if !strings.HasPrefix(version, "go") { | ||
version = "go" + version | ||
} | ||
|
||
fmt.Printf("Installing version: %s for: %s\n", version, dlArch) | ||
|
||
dlURL := fmt.Sprintf("https://go.dev/dl/%s.%s-%s.tar.gz", version, strings.ToLower(osVer), dlArch) | ||
fmt.Printf("Downloading from: %s\n", dlURL) | ||
|
||
progress := true | ||
outPath, err := get.DownloadFileP(dlURL, progress) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Printf("Downloaded to: %s\n", outPath) | ||
|
||
f, err := os.OpenFile(outPath, os.O_RDONLY, 0644) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
|
||
fmt.Printf("Unpacking Go to: %s\n", path.Join(installPath, "go")) | ||
|
||
if err := archive.UntarNested(f, installPath); err != nil { | ||
return err | ||
} | ||
|
||
fmt.Printf("\nexport PATH=$PATH:%s:$HOME/go/bin\n"+ | ||
"export GOPATH=$HOME/go/\n", path.Join(installPath, "go", "bin")) | ||
|
||
return nil | ||
} | ||
|
||
return command | ||
} | ||
|
||
func getGoVersion() (string, error) { | ||
req, err := http.NewRequest(http.MethodGet, "https://go.dev/VERSION?m=text", nil) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if res.StatusCode != http.StatusOK { | ||
return "", fmt.Errorf("unexpected status code: %d", res.StatusCode) | ||
} | ||
if res.Body == nil { | ||
return "", fmt.Errorf("unexpected empty body") | ||
} | ||
|
||
defer res.Body.Close() | ||
body, _ := io.ReadAll(res.Body) | ||
|
||
return strings.TrimSpace(string(body)), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// Copyright (c) arkade author(s) 2022. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
package system | ||
|
||
import "github.com/spf13/cobra" | ||
|
||
func MakeInstall() *cobra.Command { | ||
|
||
command := &cobra.Command{ | ||
Use: "install", | ||
Short: "Install system apps", | ||
Long: `Install system apps for Linux hosts`, | ||
Aliases: []string{"i"}, | ||
Example: ` arkade system install [APP] | ||
arkade system install --help`, | ||
SilenceUsage: true, | ||
} | ||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error { | ||
return cmd.Usage() | ||
} | ||
|
||
command.AddCommand(MakeInstallGo()) | ||
|
||
return command | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Copyright (c) arkade author(s) 2020. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
// system contains a suite of Sponsored Apps for arkade | ||
package system | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func MakeSystem() *cobra.Command { | ||
|
||
command := &cobra.Command{ | ||
Use: "system", | ||
Short: "System apps", | ||
Long: `Apps for systems.`, | ||
Aliases: []string{"s"}, | ||
Example: ` arkade system install [APP] | ||
arkade s i [APP]`, | ||
SilenceUsage: true, | ||
} | ||
|
||
command.RunE = func(cmd *cobra.Command, args []string) error { | ||
return cmd.Usage() | ||
} | ||
|
||
command.AddCommand(MakeInstall()) | ||
|
||
return command | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package archive | ||
|
||
import ( | ||
"archive/tar" | ||
"compress/gzip" | ||
"fmt" | ||
"io" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"time" | ||
) | ||
|
||
// Untar reads the gzip-compressed tar file from r and writes it into dir. | ||
func UntarNested(r io.Reader, dir string) error { | ||
return untarNested(r, dir) | ||
} | ||
|
||
func untarNested(r io.Reader, dir string) (err error) { | ||
t0 := time.Now() | ||
nFiles := 0 | ||
madeDir := map[string]bool{} | ||
defer func() { | ||
td := time.Since(t0) | ||
if err == nil { | ||
log.Printf("extracted tarball into %s: %d files, %d dirs (%v)", dir, nFiles, len(madeDir), td) | ||
} else { | ||
log.Printf("error extracting tarball into %s after %d files, %d dirs, %v: %v", dir, nFiles, len(madeDir), td, err) | ||
} | ||
}() | ||
zr, err := gzip.NewReader(r) | ||
if err != nil { | ||
return fmt.Errorf("requires gzip-compressed body: %v", err) | ||
} | ||
tr := tar.NewReader(zr) | ||
loggedChtimesError := false | ||
for { | ||
f, err := tr.Next() | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
log.Printf("tar reading error: %v", err) | ||
return fmt.Errorf("tar error: %v", err) | ||
} | ||
if !validRelPath(f.Name) { | ||
return fmt.Errorf("tar contained invalid name error %q", f.Name) | ||
} | ||
rel := filepath.FromSlash(f.Name) | ||
abs := filepath.Join(dir, rel) | ||
|
||
fi := f.FileInfo() | ||
mode := fi.Mode() | ||
switch { | ||
case mode.IsRegular(): | ||
// Make the directory. This is redundant because it should | ||
// already be made by a directory entry in the tar | ||
// beforehand. Thus, don't check for errors; the next | ||
// write will fail with the same error. | ||
dir := filepath.Dir(abs) | ||
if !madeDir[dir] { | ||
if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil { | ||
return err | ||
} | ||
madeDir[dir] = true | ||
} | ||
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm()) | ||
if err != nil { | ||
return err | ||
} | ||
n, err := io.Copy(wf, tr) | ||
if closeErr := wf.Close(); closeErr != nil && err == nil { | ||
err = closeErr | ||
} | ||
if err != nil { | ||
return fmt.Errorf("error writing to %s: %v", abs, err) | ||
} | ||
if n != f.Size { | ||
return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size) | ||
} | ||
modTime := f.ModTime | ||
if modTime.After(t0) { | ||
// Clamp modtimes at system time. See | ||
// golang.org/issue/19062 when clock on | ||
// buildlet was behind the gitmirror server | ||
// doing the git-archive. | ||
modTime = t0 | ||
} | ||
if !modTime.IsZero() { | ||
if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError { | ||
// benign error. Gerrit doesn't even set the | ||
// modtime in these, and we don't end up relying | ||
// on it anywhere (the gomote push command relies | ||
// on digests only), so this is a little pointless | ||
// for now. | ||
log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err) | ||
loggedChtimesError = true // once is enough | ||
} | ||
} | ||
nFiles++ | ||
case mode.IsDir(): | ||
if err := os.MkdirAll(abs, 0755); err != nil { | ||
return err | ||
} | ||
madeDir[abs] = true | ||
default: | ||
return fmt.Errorf("tar file entry %s contained unsupported file type %v", f.Name, mode) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// func validRelativeDir(dir string) bool { | ||
// if strings.Contains(dir, `\`) || path.IsAbs(dir) { | ||
// return false | ||
// } | ||
// dir = path.Clean(dir) | ||
// if strings.HasPrefix(dir, "../") || strings.HasSuffix(dir, "/..") || dir == ".." { | ||
// return false | ||
// } | ||
// return true | ||
// } | ||
|
||
// func validRelPath(p string) bool { | ||
// if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") { | ||
// return false | ||
// } | ||
// return true | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters