Skip to content

Commit

Permalink
Merge pull request #311 from kool-dev/logs
Browse files Browse the repository at this point in the history
Add kool deploy logs
  • Loading branch information
danielsuguimoto authored Apr 16, 2021
2 parents 9871e60 + d9444a6 commit b8cf420
Show file tree
Hide file tree
Showing 10 changed files with 578 additions and 125 deletions.
89 changes: 89 additions & 0 deletions cloud/k8s/kubectl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package k8s

import (
"errors"
"fmt"
"kool-dev/kool/api"
"kool-dev/kool/cmd/builder"
"kool-dev/kool/cmd/shell"
"os"
"path/filepath"
)

type K8S interface {
Authenticate(string, string) (string, error)
Kubectl(shell.PathChecker) (builder.Command, error)
Cleanup(shell.OutputWritter)
}

type DefaultK8S struct {
apiExec api.ExecCall
resp *api.ExecResponse
}

var authTempPath = "/tmp"

// NewDefaultK8S returns a new pointer for DefaultK8S with dependencies
func NewDefaultK8S() *DefaultK8S {
return &DefaultK8S{
apiExec: api.NewDefaultExecCall(),
}
}

func (k *DefaultK8S) Authenticate(domain, service string) (cloudService string, err error) {
k.apiExec.Body().Set("domain", domain)
k.apiExec.Body().Set("service", service)

if k.resp, err = k.apiExec.Call(); err != nil {
return
}

if k.resp.Token == "" {
err = fmt.Errorf("failed to generate access credentials to cloud deploy")
return
}

cloudService = k.resp.Path

err = os.WriteFile(k.getTempCAPath(), []byte(k.resp.CA), os.ModePerm)
return
}

func (k *DefaultK8S) Kubectl(looker shell.PathChecker) (kube builder.Command, err error) {
if k.resp == nil {
err = errors.New("calling kubectl but did not authenticate")
return
}

kube = builder.NewCommand("kubectl")

kube.AppendArgs("--server", k.resp.Server)
kube.AppendArgs("--token", k.resp.Token)
kube.AppendArgs("--namespace", k.resp.Namespace)
kube.AppendArgs("--certificate-authority", k.getTempCAPath())

if looker.LookPath(kube) != nil {
// we do not have 'kubectl' on current path... let's use a container!
kool := builder.NewCommand("kool")
kool.AppendArgs(
"docker", "--",
"-v", fmt.Sprintf("%s:%s", k.getTempCAPath(), k.getTempCAPath()),
"kooldev/toolkit:full",
kube.Cmd(),
)
kool.AppendArgs(kube.Args()...)
kube = kool
}

return
}

func (k *DefaultK8S) Cleanup(out shell.OutputWritter) {
if err := os.Remove(k.getTempCAPath()); err != nil {
out.Warning("failed to clear up temporary file; error:", err.Error())
}
}

func (k *DefaultK8S) getTempCAPath() string {
return filepath.Join(authTempPath, ".kool-cluster-CA")
}
156 changes: 156 additions & 0 deletions cloud/k8s/kubectl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package k8s

import (
"errors"
"kool-dev/kool/api"
"kool-dev/kool/cmd/shell"
"os"
"strings"
"testing"
)

// fake api.ExecCall
type fakeExecCall struct {
api.DefaultEndpoint

err error
resp *api.ExecResponse
}

func (d *fakeExecCall) Call() (*api.ExecResponse, error) {
return d.resp, d.err
}

func newFakeExecCall() *fakeExecCall {
return &fakeExecCall{
DefaultEndpoint: *api.NewDefaultEndpoint(""),
}
}

// fake shell.OutputWritter
type fakeOutputWritter struct {
warned []interface{}
}

func (*fakeOutputWritter) Println(args ...interface{}) {
}

func (*fakeOutputWritter) Printf(s string, args ...interface{}) {
}

func (f *fakeOutputWritter) Warning(args ...interface{}) {
f.warned = append(f.warned, args...)
}

func (*fakeOutputWritter) Success(args ...interface{}) {
}

func TestNewDefaultK8S(t *testing.T) {
k := NewDefaultK8S()
if _, ok := k.apiExec.(*api.DefaultExecCall); !ok {
t.Error("invalid type on apiExec")
}
}

func TestAuthenticate(t *testing.T) {
k := &DefaultK8S{
apiExec: newFakeExecCall(),
}

expectedErr := errors.New("call error")
k.apiExec.(*fakeExecCall).err = expectedErr

if _, err := k.Authenticate("foo", "bar"); !errors.Is(err, expectedErr) {
t.Error("unexpected error return from Authenticate")
}

k.apiExec.(*fakeExecCall).err = nil
k.apiExec.(*fakeExecCall).resp = &api.ExecResponse{
Server: "server",
Namespace: "ns",
Path: "path",
Token: "",
CA: "ca",
}

if _, err := k.Authenticate("foo", "bar"); !strings.Contains(err.Error(), "failed to generate access credentials") {
t.Errorf("unexpected error from DeployExec call: %v", err)
}

k.apiExec.(*fakeExecCall).resp.Token = "token"
authTempPath = t.TempDir()

if cloudService, err := k.Authenticate("foo", "bar"); err != nil {
t.Errorf("unexpected error from Authenticate call: %v", err)
} else if cloudService != "path" {
t.Errorf("unexpected cloudService return: %s", cloudService)
}
}

func TestTempCAPath(t *testing.T) {
k := NewDefaultK8S()

authTempPath = "fake-path"

if !strings.Contains(k.getTempCAPath(), authTempPath) {
t.Error("missing authTempPath from temp CA path")
}
}

func TestCleanup(t *testing.T) {
k := NewDefaultK8S()

authTempPath = t.TempDir()
if err := os.WriteFile(k.getTempCAPath(), []byte("ca"), os.ModePerm); err != nil {
t.Fatal(err)
}

fakeOut := &fakeOutputWritter{}

k.Cleanup(fakeOut)

if len(fakeOut.warned) != 0 {
t.Error("should not have warned on removing the file")
}

authTempPath = t.TempDir() + "test"
k.Cleanup(fakeOut)

if len(fakeOut.warned) != 2 {
t.Error("should have warned on removing the file once")
}
}

func TestKubectl(t *testing.T) {
authTempPath = t.TempDir()

k := &DefaultK8S{
apiExec: newFakeExecCall(),
}

k.apiExec.(*fakeExecCall).resp = &api.ExecResponse{
Server: "server",
Namespace: "ns",
Path: "path",
Token: "token",
CA: "ca",
}

fakeShell := &shell.FakeShell{}

if _, err := k.Kubectl(fakeShell); !strings.Contains(err.Error(), "but did not auth") {
t.Error("should get error before authenticating")
}

_, _ = k.Authenticate("foo", "bar")

if cmd, _ := k.Kubectl(fakeShell); cmd.Cmd() != "kubectl" {
t.Error("should use kubectl")
}

fakeShell.MockLookPath = errors.New("err")

if cmd, _ := k.Kubectl(fakeShell); cmd.Cmd() != "kool" {
t.Error("should use kool")
}
}
1 change: 1 addition & 0 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func AddKoolDeploy(root *cobra.Command) {
root.AddCommand(deployCmd)
deployCmd.AddCommand(NewDeployExecCommand(NewKoolDeployExec()))
deployCmd.AddCommand(NewDeployDestroyCommand(NewKoolDeployDestroy()))
deployCmd.AddCommand(NewDeployLogsCommand(NewKoolDeployLogs()))
}

// Execute runs the deploy logic.
Expand Down
Loading

0 comments on commit b8cf420

Please sign in to comment.