diff --git a/pkg/cmd/install_test.go b/pkg/cmd/install_test.go index c1d37d33..05ebc63e 100644 --- a/pkg/cmd/install_test.go +++ b/pkg/cmd/install_test.go @@ -112,3 +112,12 @@ func Test_RewriteKubeconfig(t *testing.T) { t.Errorf("Unexpected error, got: %q, want: %q.", len(match), len(expectedContextsToReplace)) } } + +func Test_getHelmURL(t *testing.T) { + got := getHelmURL("amd64", "darwin", "v2.14.3") + want := "https://get.helm.sh/helm-v2.14.3-darwin-amd64.tar.gz" + + if want != got { + t.Errorf("want %s, got %s", want, got) + } +} diff --git a/pkg/cmd/kubernetes_exec.go b/pkg/cmd/kubernetes_exec.go index 0025bf6f..5e32fd02 100644 --- a/pkg/cmd/kubernetes_exec.go +++ b/pkg/cmd/kubernetes_exec.go @@ -17,7 +17,8 @@ func fetchChart(path, chart string) error { } task := execute.ExecTask{ - Command: fmt.Sprintf("helm fetch %s --untar --untardir %s", chart, path), + Command: fmt.Sprintf("%s fetch %s --untar --untardir %s", localBinary("helm"), chart, path), + Env: os.Environ(), } res, err := task.Execute() @@ -48,8 +49,9 @@ func templateChart(basePath, chart, namespace, outputPath, values string) error chartRoot := path.Join(basePath, chart) task := execute.ExecTask{ - Command: fmt.Sprintf("helm template %s --output-dir %s --values %s --namespace %s", - chart, outputPath, path.Join(chartRoot, values), namespace), + Command: fmt.Sprintf("%s template %s --output-dir %s --values %s --namespace %s", + localBinary("helm"), chart, outputPath, path.Join(chartRoot, values), namespace), + Env: os.Environ(), Cwd: basePath, } @@ -65,9 +67,15 @@ func templateChart(basePath, chart, namespace, outputPath, values string) error return nil } +func localBinary(name string) string { + home := os.Getenv("HOME") + return path.Join(path.Join(home, ".k3sup/.bin/"), name) +} + func addHelmRepo(name, url string) error { task := execute.ExecTask{ - Command: fmt.Sprintf("helm repo add %s %s", name, url), + Command: fmt.Sprintf("%s repo add %s %s", localBinary("helm"), name, url), + Env: os.Environ(), } res, err := task.Execute() @@ -83,7 +91,26 @@ func addHelmRepo(name, url string) error { func updateHelmRepos() error { task := execute.ExecTask{ - Command: fmt.Sprintf("helm repo update"), + Command: fmt.Sprintf("%s repo update", localBinary("helm")), + Env: os.Environ(), + } + res, err := task.Execute() + + if err != nil { + return err + } + + if res.ExitCode != 0 { + return fmt.Errorf("exit code %d", res.ExitCode) + } + return nil +} + +func helmInit() error { + task := execute.ExecTask{ + Command: fmt.Sprintf("%s", localBinary("helm")), + Env: os.Environ(), + Args: []string{"init", "--client-only"}, } res, err := task.Execute() diff --git a/pkg/cmd/openfaas_app.go b/pkg/cmd/openfaas_app.go index e33f3e88..3d8c3a54 100644 --- a/pkg/cmd/openfaas_app.go +++ b/pkg/cmd/openfaas_app.go @@ -2,7 +2,10 @@ package cmd import ( "fmt" + "io/ioutil" "log" + "net/http" + "net/url" "os" "path" "strings" @@ -29,6 +32,18 @@ func makeInstallOpenFaaS() *cobra.Command { openfaas.Flags().StringP("namespace", "n", "openfaas", "Namespace for core services") openfaas.RunE = func(command *cobra.Command, args []string) error { + kubeConfigPath := path.Join(os.Getenv("HOME"), ".kube/config") + + if val, ok := os.LookupEnv("KUBECONFIG"); ok { + kubeConfigPath = val + } + + if command.Flags().Changed("kubeconfig") { + kubeConfigPath, _ = command.Flags().GetString("kubeconfig") + } + + fmt.Printf("Using context: %s\n", kubeConfigPath) + arch := getArchitecture() fmt.Printf("Node architecture: %s\n", arch) @@ -49,18 +64,17 @@ func makeInstallOpenFaaS() *cobra.Command { log.Printf("User dir established as: %s\n", userPath) - kubeConfigPath := path.Join(os.Getenv("HOME"), ".kube/config") + os.Setenv("HELM_HOME", path.Join(userPath, ".helm")) - if val, ok := os.LookupEnv("KUBECONFIG"); ok { - kubeConfigPath = val - } + if _, statErr := os.Stat(path.Join(path.Join(userPath, ".bin"), "helm")); statErr != nil { + downloadHelm(userPath, clientArch, clientOS) - if command.Flags().Changed("kubeconfig") { - kubeConfigPath, _ = command.Flags().GetString("kubeconfig") + err = helmInit() + if err != nil { + return err + } } - fmt.Printf("Using context: %s\n", kubeConfigPath) - // lb, _ := command.Flags().GetBool("loadbalancer") namespace, _ := command.Flags().GetString("namespace") @@ -109,7 +123,29 @@ func makeInstallOpenFaaS() *cobra.Command { err = kubectl("apply", "-R", "-f", outputPath) - return err + if err != nil { + return err + } + + fmt.Println(`======================================================================= += OpenFaaS has been installed. = +======================================================================= +If basic auth is enabled, you can now log into your gateway. + +# Get the faas-cli +curl -SLsf https://cli.openfaas.com | sudo sh + +# Forward the gateway to your machine +kubectl rollout status -n openfaas deploy/gateway +kubectl port-forward -n openfaas svc/gateway & + +# Get your password and log in +PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode; echo) +echo -n $PASSWORD | faas-cli login --username admin --password-stdin + +Thank you for using k3sup!`) + + return nil } return openfaas @@ -134,3 +170,36 @@ func getClientArch() (string, string) { return arch, os } + +func getHelmURL(arch, os, version string) string { + archSuffix := "amd64" + osSuffix := strings.ToLower(os) + + if strings.HasPrefix(arch, "armv7") { + archSuffix = "arm" + } else if strings.HasPrefix(arch, "aarch64") { + archSuffix = "arm64" + } + + return fmt.Sprintf("https://get.helm.sh/helm-%s-%s-%s.tar.gz", version, osSuffix, archSuffix) +} + +func downloadHelm(userPath, clientArch, clientOS string) error { + helmURL := getHelmURL(clientArch, clientOS, "v2.14.3") + fmt.Println(helmURL) + parsedURL, _ := url.Parse(helmURL) + + res, err := http.DefaultClient.Get(parsedURL.String()) + if err != nil { + return err + } + + defer res.Body.Close() + r := ioutil.NopCloser(res.Body) + untarErr := Untar(r, path.Join(userPath, ".bin")) + if untarErr != nil { + return untarErr + } + + return nil +} diff --git a/pkg/cmd/untar.go b/pkg/cmd/untar.go new file mode 100644 index 00000000..f74d63e6 --- /dev/null +++ b/pkg/cmd/untar.go @@ -0,0 +1,132 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Edited on 2019-10-11 to remove support for nested folders when un-taring +// so that all files are placed in the same target directory + +package cmd + +import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" + "log" + "os" + "path" + "path/filepath" + "strings" + "time" +) + +// TODO(bradfitz): this was copied from x/build/cmd/buildlet/buildlet.go +// but there were some buildlet-specific bits in there, so the code is +// forked for now. Unfork and add some opts arguments here, so the +// buildlet can use this code somehow. + +// Untar reads the gzip-compressed tar file from r and writes it into dir. +func Untar(r io.Reader, dir string) error { + return untar(r, dir) +} + +func untar(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) + } + baseFile := filepath.Base(f.Name) + abs := path.Join(dir, baseFile) + fmt.Println(abs, f.Name) + + fi := f.FileInfo() + mode := fi.Mode() + switch { + case mode.IsDir(): + + break + + case mode.IsRegular(): + + 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++ + default: + } + } + 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 +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 05363f02..21e0c4bd 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -3,6 +3,7 @@ package config import ( "fmt" "os" + "path" ) // K3sVersion default version @@ -10,16 +11,23 @@ const K3sVersion = "v0.8.1" func InitUserDir() (string, error) { home := os.Getenv("HOME") - fullPath := fmt.Sprintf("%s/k3sup/.bin/", home) + root := fmt.Sprintf("%s/.k3sup/", home) if len(home) == 0 { - return fullPath, fmt.Errorf("env-var HOME, not set") + return home, fmt.Errorf("env-var HOME, not set") } - err := os.MkdirAll(fullPath, 0700) + binPath := path.Join(root, "/.bin/") + err := os.MkdirAll(binPath, 0700) if err != nil { - return fullPath, err + return binPath, err } - return fullPath, nil + helmPath := path.Join(root, "/.helm/") + helmErr := os.MkdirAll(helmPath, 0700) + if helmErr != nil { + return helmPath, helmErr + } + + return root, nil }