From edb0ddb6f6f7ed3075f783a5e9e2c9ce6f880758 Mon Sep 17 00:00:00 2001 From: Christian Simon Date: Wed, 12 Sep 2018 17:16:10 +0100 Subject: [PATCH 01/14] Add some basic e2e tests Signed-off-by: Christian Simon --- cmd/tarmak/e2e/e2e_cluster_test.go | 120 +++++++ cmd/tarmak/e2e/tarmak_instance_test.go | 293 ++++++++++++++++++ .../centos-puppet-agent-latest-kernel.json | 2 +- packer/amazon/centos-puppet-agent.json | 2 +- 4 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 cmd/tarmak/e2e/e2e_cluster_test.go create mode 100644 cmd/tarmak/e2e/tarmak_instance_test.go diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go new file mode 100644 index 0000000000..08d4da0a73 --- /dev/null +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -0,0 +1,120 @@ +// Copyright Jetstack Ltd. See LICENSE for details. +package e2e_test + +import ( + "fmt" + "os" + "testing" +) + +func TestAWSSingleCluster(t *testing.T) { + t.Parallel() + skipE2ETests(t) + + ti := NewTarmakInstance(t) + ti.singleCluster = true + ti.singleZone = true + + t.Log("initialise config for single cluster") + if err := ti.Init(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + + t.Log("build tarmak image") + c := ti.Command("cluster", "image", "build") + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + + defer func() { + t.Log("run cluster destroy command") + c = ti.Command("cluster", "destroy") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + }() + t.Log("run cluster apply command") + c = ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("get component status") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } +} + +func TestAWSMultiCluster(t *testing.T) { + t.Parallel() + skipE2ETests(t) + + ti := NewTarmakInstance(t) + ti.singleCluster = false + ti.singleZone = false + + t.Log("initialise config for single cluster") + if err := ti.Init(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("build tarmak image") + c := ti.Command("cluster", "image", "build") + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + defer func() { + t.Log("run hub destroy command") + c = ti.Command("--current-cluster", fmt.Sprintf("%s-hub", ti.environmentName), "cluster", "destroy") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + }() + t.Log("run hub apply command") + c = ti.Command("--current-cluster", fmt.Sprintf("%s-hub", ti.environmentName), "cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + defer func() { + t.Log("run cluster destroy command") + c = ti.Command("cluster", "destroy") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + }() + t.Log("run cluster apply command") + c = ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("get component status") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + +} diff --git a/cmd/tarmak/e2e/tarmak_instance_test.go b/cmd/tarmak/e2e/tarmak_instance_test.go new file mode 100644 index 0000000000..2bf175f4b1 --- /dev/null +++ b/cmd/tarmak/e2e/tarmak_instance_test.go @@ -0,0 +1,293 @@ +// Copyright Jetstack Ltd. See LICENSE for details. +package e2e_test + +import ( + "context" + "flag" + "fmt" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "runtime" + "syscall" + "testing" + "time" + + "github.com/google/goexpect" +) + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") +var e2eEnable bool + +const tarmakInitPrompt = "> " +const tarmakInitYesNo = " \\[Y\\/n\\] " + +func init() { + // add custom e2e flag + flag.BoolVar(&e2eEnable, "e2e", false, "Enable E2E tests") + flag.Parse() + + // seed random + rand.Seed(time.Now().UnixNano()) +} + +func skipE2ETests(t *testing.T) { + goTestCmd := "go test -v -timeout 1h github.com/jetstack/tarmak/cmd/tarmak/e2e -e2e" + + if !e2eEnable { + t.Skipf("E2E tests are disabled. Run tests with '%s'", goTestCmd) + } + if timeoutFlag := flag.Lookup("test.timeout"); timeoutFlag != nil { + t.Logf("flag=%+v", timeoutFlag.Value.String()) + if timeout, err := time.ParseDuration(timeoutFlag.Value.String()); err != nil { + t.Fatal("Unparseable timeout: ", err) + } else { + if timeout < time.Hour { + t.Skipf("E2E tests are disabled, as timeout is set to short. Run tests with '%s'", goTestCmd) + } + } + } +} + +func randStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +type TarmakInstance struct { + t *testing.T + configPath string // config path + binPath string // bin path to tarmak binary + region string // region of the cluster + singleCluster bool // single or multi cluster + singleZone bool // single or multi zone + environmentName string // name of the tarmak environment + awsBucketDynamoPrefix string // aws bucket and dynamodb prefix + awsSTSVaultPath string // vault auth path +} + +func NewTarmakInstance(t *testing.T) *TarmakInstance { + ti := &TarmakInstance{ + t: t, + binPath: fmt.Sprintf("../../../tarmak_%s_%s", runtime.GOOS, runtime.GOARCH), + region: "eu-central-1", + singleCluster: true, + singleZone: true, + environmentName: fmt.Sprintf("e2e%s", randStringRunes(6)), + awsBucketDynamoPrefix: fmt.Sprintf("jetstack-e2e-%s", randStringRunes(6)), + awsSTSVaultPath: "jetstack/aws/jetstack-dev/sts/admin", + } + + if _, err := os.Stat(ti.binPath); os.IsNotExist(err) { + t.Fatal("tarmak binary not existing: ", ti.binPath) + } else if err != nil { + t.Fatal("error finding tarmak binary: ", ti.binPath) + } + + if dir, err := ioutil.TempDir("", "tarmak-config"); err != nil { + t.Fatal("error creating temporary config directory: ", err) + } else { + t.Logf("created temp config directory in %s", dir) + ti.configPath = dir + } + + return ti +} + +func (ti *TarmakInstance) Command(args ...string) *exec.Cmd { + c := exec.CommandContext( + context.Background(), + ti.binPath, + args..., + ) + + c.Env = append(os.Environ(), fmt.Sprintf("TARMAK_CONFIG=%s", ti.configPath)) + + return c +} + +func (ti *TarmakInstance) Init() error { + e, wait, err := ti.Expect("init") + if err != nil { + return fmt.Errorf("error init expect: %s", err) + } + defer e.Close() + + if err := ti.initProvider(e); err != nil { + return fmt.Errorf("error initialsing provider config: %s", err) + } + if err := ti.initEnvironment(e); err != nil { + return fmt.Errorf("error initialsing environment config: %s", err) + } + if err := ti.initCluster(e); err != nil { + return fmt.Errorf("error initialsing cluster config: %s", err) + } + + if err := wait(); err != nil { + return fmt.Errorf("error waiting for tarmak: %s", err) + } + + return nil + +} + +// This returns a GExpect. It needs to be closed if it's no longer used +func (ti *TarmakInstance) Expect(args ...string) (*expect.GExpect, func() error, error) { + c := ti.Command(args...) + + // write error out to my stdout + c.Stderr = os.Stderr + + stdIn, err := c.StdinPipe() + if err != nil { + return nil, nil, fmt.Errorf("error creating pipe: %s", err) + } + + stdOut, err := c.StdoutPipe() + if err != nil { + return nil, nil, fmt.Errorf("error creating pipe: %s", err) + } + + if err := c.Start(); err != nil { + return nil, nil, fmt.Errorf("Unexcepted error starting tarmak: %+v", err) + } + + waitCh := make(chan error, 1) + + e, _, err := expect.SpawnGeneric( + &expect.GenOptions{ + In: stdIn, + Out: stdOut, + Wait: func() error { + err := c.Wait() + waitCh <- err + return err + }, + Close: c.Process.Kill, + Check: func() bool { + if c.Process == nil { + return false + } + // Sending Signal 0 to a process returns nil if process can take a signal , something else if not. + return c.Process.Signal(syscall.Signal(0)) == nil + }, + }, + time.Second, + ) + if err != nil { + return nil, nil, fmt.Errorf("error creating expect: %s", err) + } + + wait := func() error { + err := <-waitCh + return err + } + + return e, wait, nil +} + +func (ti *TarmakInstance) initProvider(e *expect.GExpect) error { + ti.t.Log("setting up provider") + res, err := e.ExpectBatch([]expect.Batcher{ + &expect.BExp{R: tarmakInitPrompt}, + &expect.BSnd{S: "jetstack-dev\n"}, // enter provider name + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: "1\n"}, // select AWS + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: "2\n"}, // vault auth + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: fmt.Sprintf("%s\n", ti.awsSTSVaultPath)}, // path for vault endpoint + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: fmt.Sprintf("%s\n", ti.awsBucketDynamoPrefix)}, // bucket / dynamodb backend + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: "develop.tarmak.org\n"}, // public route 53 + &expect.BExp{R: tarmakInitYesNo}, // + &expect.BSnd{S: "Y\n"}, // save provider + }, 30*time.Second) + if err != nil { + return fmt.Errorf("unexpected expect flow for init provider res=%+v error: %+v", res, err) + } + return nil +} + +func (ti *TarmakInstance) initEnvironment(e *expect.GExpect) error { + ti.t.Logf("setting up environment %s", ti.environmentName) + res, err := e.ExpectBatch([]expect.Batcher{ + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: fmt.Sprintf("%s\n", ti.environmentName)}, // environment + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: fmt.Sprintf("%s\n", ti.environmentName)}, // enter project name + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: "tech+e2e@jetstack.io\n"}, // enter contact mail + &expect.BExp{R: tarmakInitPrompt}, // + &expect.BSnd{S: "7\n"}, // select eu-central-1 // TODO: this will break with AWS adding more regions + &expect.BExp{R: tarmakInitYesNo}, // + &expect.BSnd{S: "Y\n"}, // save environment + }, 30*time.Second) + if err != nil { + return fmt.Errorf("unexpected expect flow for init environment res=%+v error: %+v", res, err) + } + return nil +} + +func (ti *TarmakInstance) initCluster(e *expect.GExpect) error { + + batches := []expect.Batcher{ + &expect.BExp{R: tarmakInitPrompt}, // + } + + // single vs. multi cluster + if ti.singleCluster { + batches = append(batches, &expect.BSnd{S: "1\n"}) + } else { + batches = append(batches, &expect.BSnd{S: "2\n"}) + } + + // single vs. multi zone + if !ti.singleZone { + batches = append( + batches, + &expect.BExp{R: tarmakInitPrompt}, + &expect.BSnd{S: "2\n"}, // enable second zone + &expect.BExp{R: tarmakInitPrompt}, + &expect.BSnd{S: "3\n"}, // enable third zone + ) + } + + // continue zone selection + batches = append( + batches, + &expect.BExp{R: tarmakInitPrompt}, + &expect.BSnd{S: "4\n"}, // continue + ) + + // set cluster name for multi cluster + if !ti.singleCluster { + batches = append( + batches, + &expect.BExp{R: tarmakInitPrompt}, + &expect.BSnd{S: "greeen\n"}, + ) + } + + // save cluster + batches = append( + batches, + &expect.BExp{R: tarmakInitYesNo}, // + &expect.BSnd{S: "Y\n"}, // save cluster + ) + + ti.t.Logf("setting up cluster in environment %s", ti.environmentName) + res, err := e.ExpectBatch( + batches, + 30*time.Second, + ) + if err != nil { + return fmt.Errorf("unexpected expect flow for init cluster res=%+v error: %+v", res, err) + } + return nil +} diff --git a/packer/amazon/centos-puppet-agent-latest-kernel.json b/packer/amazon/centos-puppet-agent-latest-kernel.json index 2c852a3b5f..126c1d642c 100644 --- a/packer/amazon/centos-puppet-agent-latest-kernel.json +++ b/packer/amazon/centos-puppet-agent-latest-kernel.json @@ -26,7 +26,7 @@ "ssh_username": "centos", "ssh_pty": "true", "encrypt_boot": "{{user `ebs_volume_encrypted`}}", - "ami_name": "Tarmak CentOS 7 x86_64 with puppet-agent and latest upstream kernel{{isotime \"2006-01-02_030405\"}}", + "ami_name": "{{user `tarmak_environment`}} - Tarmak CentOS 7 x86_64 with puppet-agent and latest upstream kernel{{isotime \"2006-01-02_030405\"}}", "tags": { "Name": "tarmak_{{user `tarmak_environment`}}_{{user `tarmak_base_image_name`}}", "tarmak_environment": "{{user `tarmak_environment`}}", diff --git a/packer/amazon/centos-puppet-agent.json b/packer/amazon/centos-puppet-agent.json index aa1304a777..6a5418e729 100644 --- a/packer/amazon/centos-puppet-agent.json +++ b/packer/amazon/centos-puppet-agent.json @@ -26,7 +26,7 @@ "ssh_username": "centos", "ssh_pty": "true", "encrypt_boot": "{{user `ebs_volume_encrypted`}}", - "ami_name": "Tarmak CentOS 7 x86_64 with puppet-agent {{isotime \"2006-01-02_030405\"}}", + "ami_name": "{{user `tarmak_environment`}} - Tarmak CentOS 7 x86_64 with puppet-agent {{isotime \"2006-01-02_030405\"}}", "tags": { "Name": "tarmak_{{user `tarmak_environment`}}_{{user `tarmak_base_image_name`}}", "tarmak_environment": "{{user `tarmak_environment`}}", From 76bae18b95c3bc522fe1cadabee71356508b72df Mon Sep 17 00:00:00 2001 From: Christian Simon Date: Wed, 12 Sep 2018 17:23:30 +0100 Subject: [PATCH 02/14] Add vendoring Signed-off-by: Christian Simon --- Gopkg.lock | 80 +- vendor/github.com/google/goexpect/AUTHORS | 3 + vendor/github.com/google/goexpect/LICENSE | 28 + vendor/github.com/google/goexpect/expect.go | 1181 +++++++++++++++++ vendor/github.com/google/goterm/AUTHORS | 3 + vendor/github.com/google/goterm/LICENSE | 28 + vendor/github.com/google/goterm/term/color.go | 677 ++++++++++ vendor/github.com/google/goterm/term/ssh.go | 203 +++ .../github.com/google/goterm/term/termios.go | 392 ++++++ 9 files changed, 2558 insertions(+), 37 deletions(-) create mode 100644 vendor/github.com/google/goexpect/AUTHORS create mode 100644 vendor/github.com/google/goexpect/LICENSE create mode 100644 vendor/github.com/google/goexpect/expect.go create mode 100644 vendor/github.com/google/goterm/AUTHORS create mode 100644 vendor/github.com/google/goterm/LICENSE create mode 100644 vendor/github.com/google/goterm/term/color.go create mode 100644 vendor/github.com/google/goterm/term/ssh.go create mode 100644 vendor/github.com/google/goterm/term/termios.go diff --git a/Gopkg.lock b/Gopkg.lock index bb895973e0..6fdec69dbb 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -550,25 +550,6 @@ revision = "358ee7663966325963d4e8b2e1fbd570c5195153" version = "v1.38.1" -[[projects]] - digest = "1:da40b9e5973892e2bd37a72c36464b8252a4034522925d920983edaabda03693" - name = "github.com/go-openapi/analysis" - packages = [ - ".", - "internal", - ] - pruneopts = "NUT" - revision = "7c1bef8f6d9fa6148ce0d8a0ebf5339a084a6639" - version = "0.16.0" - -[[projects]] - digest = "1:747665117585f3d9e2f6c7635aac0410570289139f9a52b3a514f0e8b93ea517" - name = "github.com/go-openapi/errors" - packages = ["."] - pruneopts = "NUT" - revision = "b2b2befaf267d082d779bcef52d682a47c779517" - version = "0.16.0" - [[projects]] digest = "1:260f7ebefc63024c8dfe2c9f1a2935a89fa4213637a1f522f592f80c001cc441" name = "github.com/go-openapi/jsonpointer" @@ -585,14 +566,6 @@ revision = "3fb327e6747da3043567ee86abd02bb6376b6be2" version = "0.15.0" -[[projects]] - digest = "1:cc4186672d13bce6e14f7b39c6f51b2f8c5126532a020ced03841e7175886651" - name = "github.com/go-openapi/loads" - packages = ["."] - pruneopts = "NUT" - revision = "2a2b323bab96e6b1fdee110e57d959322446e9c9" - version = "0.16.0" - [[projects]] digest = "1:87216dc20d985010c4cbe3301b4f9d4c81eaea407a289bd6338d2ac66d61b5a4" name = "github.com/go-openapi/spec" @@ -601,14 +574,6 @@ revision = "bce47c9386f9ecd6b86f450478a80103c3fe1402" version = "0.15.0" -[[projects]] - digest = "1:d9091d7447932ed64fe3584eabda47ad19753760f28b58bc6262e38155a5c1d9" - name = "github.com/go-openapi/strfmt" - packages = ["."] - pruneopts = "NUT" - revision = "913ee058e387ac83a67e2d9f13acecdcd5769fc6" - version = "0.16.0" - [[projects]] digest = "1:d6dca21942524b423bf8d3e273c61874f7cd5511c2797e67685c33f4ab971f57" name = "github.com/go-openapi/swag" @@ -685,11 +650,11 @@ [[projects]] branch = "master" - digest = "1:05f95ffdfcf651bdb0f05b40b69e7f5663047f8da75c72d58728acb59b5cc107" + digest = "1:245bd4eb633039cd66106a5d340ae826d87f4e36a8602fcc940e14176fd26ea7" name = "github.com/google/btree" packages = ["."] pruneopts = "NUT" - revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306" + revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4" [[projects]] branch = "master" @@ -699,6 +664,14 @@ pruneopts = "NUT" revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" +[[projects]] + branch = "master" + digest = "1:c0c871240b85ea14304b4f6113c390b4390020cf4914c7ba78f63e9f54ef7927" + name = "github.com/google/goexpect" + packages = ["."] + pruneopts = "NUT" + revision = "88b7cf98484152f464f685e94dc4334205362983" + [[projects]] branch = "master" digest = "1:52c5834e2bebac9030c97cc0798ac11c3aa8a39f098aeb419f142533da6cd3cc" @@ -707,6 +680,14 @@ pruneopts = "NUT" revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" +[[projects]] + branch = "master" + digest = "1:5b66c432b11dc4996e583c298c03a3120c9f2e1c68d05b72adceff13595892c3" + name = "github.com/google/goterm" + packages = ["term"] + pruneopts = "NUT" + revision = "70e1c263818522aa1ecbdd34d7e143639d447ced" + [[projects]] digest = "1:1bb197a3b5db4e06e00b7560f8e89836c486627f2a0338332ed37daa003d259e" name = "github.com/google/uuid" @@ -2077,6 +2058,7 @@ version = "v0.9.1" [[projects]] +<<<<<<< HEAD branch = "v2" digest = "1:2642fd0b6900c77247d61d80cf2eb59a374ef4ffc2d25a1b95b87dc355b2894e" name = "gopkg.in/mgo.v2" @@ -2088,6 +2070,8 @@ revision = "9856a29383ce1c59f308dd1cf0363a79b5bef6b5" [[projects]] +======= +>>>>>>> Add vendoring digest = "1:80c34337e8a734e190f2d1b716cae774cca74db98315166f92074434e9af0227" name = "gopkg.in/natefinch/lumberjack.v2" packages = ["."] @@ -2540,7 +2524,11 @@ [[projects]] branch = "master" +<<<<<<< HEAD digest = "1:c48a795cd7048bb1888273bc604b6e69b22f9b8089c3df65f77cc527757b515c" +======= + digest = "1:a8a0fdd623b2a37c433d137bddfab6b155e988efbc7cd68c18f9923dce6c8742" +>>>>>>> Add vendoring name = "k8s.io/kube-openapi" packages = [ "cmd/openapi-gen", @@ -2552,9 +2540,14 @@ "pkg/handler", "pkg/util", "pkg/util/proto", +<<<<<<< HEAD "pkg/util/sets", ] pruneopts = "NT" +======= + ] + pruneopts = "NUT" +>>>>>>> Add vendoring revision = "d8ea2fe547a448256204cfc68dfee7b26c720acb" [solve-meta] @@ -2640,12 +2633,19 @@ "github.com/cenkalti/backoff", "github.com/davecgh/go-spew/spew", "github.com/docker/docker/pkg/archive", +<<<<<<< HEAD "github.com/go-openapi/spec", +======= +>>>>>>> Add vendoring "github.com/go-ozzo/ozzo-validation", "github.com/go-ozzo/ozzo-validation/is", "github.com/golang/glog", "github.com/golang/mock/gomock", "github.com/golang/mock/mockgen", +<<<<<<< HEAD +======= + "github.com/google/goexpect", +>>>>>>> Add vendoring "github.com/google/uuid", "github.com/hashicorp/errwrap", "github.com/hashicorp/go-cleanhttp", @@ -2675,7 +2675,10 @@ "github.com/jetstack/vault-unsealer/pkg/vault", "github.com/jteeuwen/go-bindata/go-bindata", "github.com/kardianos/osext", +<<<<<<< HEAD "github.com/kubernetes-incubator/reference-docs/gen-apidocs", +======= +>>>>>>> Add vendoring "github.com/mitchellh/cli", "github.com/mitchellh/go-homedir", "github.com/sirupsen/logrus", @@ -2739,8 +2742,11 @@ "k8s.io/code-generator/cmd/defaulter-gen", "k8s.io/code-generator/cmd/informer-gen", "k8s.io/code-generator/cmd/lister-gen", +<<<<<<< HEAD "k8s.io/kube-openapi/cmd/openapi-gen", "k8s.io/kube-openapi/pkg/common", +======= +>>>>>>> Add vendoring ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/google/goexpect/AUTHORS b/vendor/github.com/google/goexpect/AUTHORS new file mode 100644 index 0000000000..15167cd746 --- /dev/null +++ b/vendor/github.com/google/goexpect/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/github.com/google/goexpect/LICENSE b/vendor/github.com/google/goexpect/LICENSE new file mode 100644 index 0000000000..931520b99e --- /dev/null +++ b/vendor/github.com/google/goexpect/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/google/goexpect/expect.go b/vendor/github.com/google/goexpect/expect.go new file mode 100644 index 0000000000..de164d43d4 --- /dev/null +++ b/vendor/github.com/google/goexpect/expect.go @@ -0,0 +1,1181 @@ +// Copyright 2016 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. + +// Package expect is a Go version of the classic TCL Expect. +package expect + +import ( + "bytes" + "errors" + "fmt" + "io" + "log" + "os/exec" + "regexp" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "golang.org/x/crypto/ssh" + "google.golang.org/grpc/codes" + + "github.com/google/goterm/term" +) + +// DefaultTimeout is the default Expect timeout. +const DefaultTimeout = 60 * time.Second + +const ( + bufferSize = 8192 // bufferSize sets the size of the io buffers. + checkDuration = 2 * time.Second // checkDuration how often to check for new output. +) + +// Status contains an errormessage and a status code. +type Status struct { + code codes.Code + msg string +} + +// NewStatus creates a Status with the provided code and message. +func NewStatus(code codes.Code, msg string) *Status { + return &Status{code, msg} +} + +// NewStatusf returns a Status with the providead code and a formatted message. +func NewStatusf(code codes.Code, format string, a ...interface{}) *Status { + return NewStatus(code, fmt.Sprintf(fmt.Sprintf(format, a...))) +} + +// Err is a helper to handle errors. +func (s *Status) Err() error { + if s == nil || s.code == codes.OK { + return nil + } + return s +} + +// Error is here to adhere to the error interface. +func (s *Status) Error() string { + return s.msg +} + +// Option represents one Expecter option. +type Option func(*GExpect) Option + +// CheckDuration changes the default duration checking for new incoming data. +func CheckDuration(d time.Duration) Option { + return func(e *GExpect) Option { + prev := e.chkDuration + e.chkDuration = d + return CheckDuration(prev) + } +} + +// Verbose enables/disables verbose logging of matches and sends. +func Verbose(v bool) Option { + return func(e *GExpect) Option { + prev := e.verbose + e.verbose = v + return Verbose(prev) + } +} + +// VerboseWriter sets an alternate destination for verbose logs. +func VerboseWriter(w io.Writer) Option { + return func(e *GExpect) Option { + prev := e.verboseWriter + e.verboseWriter = w + return VerboseWriter(prev) + } +} + +// NoCheck turns off the Expect alive checks. +func NoCheck() Option { + return changeChk(func(*GExpect) bool { + return true + }) +} + +// DebugCheck adds logging to the check function. +// The check function for the spawners are called at creation/timeouts and I/O so can +// be usable for printing current state during debugging. +func DebugCheck(l *log.Logger) Option { + lg := log.Printf + if l != nil { + lg = l.Printf + } + return func(e *GExpect) Option { + prev := e.chk + e.chkMu.Lock() + e.chk = func(ge *GExpect) bool { + res := prev(ge) + ge.mu.Lock() + lg("chk: %t, ge: %v", res, ge) + ge.mu.Unlock() + return res + } + e.chkMu.Unlock() + return changeChk(prev) + } +} + +// ChangeCheck changes the Expect check function. +func ChangeCheck(f func() bool) Option { + return changeChk(func(*GExpect) bool { + return f() + }) +} + +func changeChk(f func(*GExpect) bool) Option { + return func(e *GExpect) Option { + prev := e.chk + e.chkMu.Lock() + e.chk = f + e.chkMu.Unlock() + return changeChk(prev) + } +} + +// SetEnv sets the environmental variables of the spawned process. +func SetEnv(env []string) Option { + return func(e *GExpect) Option { + prev := e.cmd.Env + e.cmd.Env = env + return SetEnv(prev) + } +} + +// BatchCommands. +const ( + // BatchSend for invoking Send in a batch + BatchSend = iota + // BatchExpect for invoking Expect in a batch + BatchExpect + // BatchSwitchCase for invoking ExpectSwitchCase in a batch + BatchSwitchCase +) + +// TimeoutError is the error returned by all Expect functions upon timer expiry. +type TimeoutError int + +// Error implements the Error interface. +func (t TimeoutError) Error() string { + return fmt.Sprintf("expect: timer expired after %d seconds", time.Duration(t)/time.Second) +} + +// BatchRes returned from ExpectBatch for every Expect command executed. +type BatchRes struct { + // Idx is used to match the result with the []Batcher commands sent in. + Idx int + // Out output buffer for the expect command at Batcher[Idx]. + Output string + // Match regexp matches for expect command at Batcher[Idx]. + Match []string +} + +// Batcher interface is used to make it more straightforward and readable to create +// batches of Expects. +// +// var batch = []Batcher{ +// &BExpT{"password",8}, +// &BSnd{"password\n"}, +// &BExp{"olakar@router>"}, +// &BSnd{ "show interface description\n"}, +// &BExp{ "olakar@router>"}, +// } +// +// var batchSwCaseReplace = []Batcher{ +// &BCasT{[]Caser{ +// &BCase{`([0-9]) -- .*\(MASTER\)`, `\1` + "\n"}}, 1}, +// &BExp{`prompt/>`}, +// } +type Batcher interface { + // cmd returns the Batch command. + Cmd() int + // Arg returns the command argument. + Arg() string + // Timeout returns the timeout duration for the command , <0 gives default value. + Timeout() time.Duration + // Cases returns the Caser structure for SwitchCase commands. + Cases() []Caser +} + +// BExp implements the Batcher interface for Expect commands using the default timeout. +type BExp struct { + // R contains the Expect command regular expression. + R string +} + +// Cmd returns the Expect command (BatchExpect). +func (be *BExp) Cmd() int { + return BatchExpect +} + +// Arg returns the Expect regular expression. +func (be *BExp) Arg() string { + return be.R +} + +// Timeout always returns -1 which sets it to the value used to call the ExpectBatch function. +func (be *BExp) Timeout() time.Duration { + return -1 +} + +// Cases always returns nil for the Expect command. +func (be *BExp) Cases() []Caser { + return nil +} + +// BExpT implements the Batcher interface for Expect commands adding a timeout option to the BExp +// type. +type BExpT struct { + // R contains the Expect command regular expression. + R string + // T holds the Expect command timeout in seconds. + T int +} + +// Cmd returns the Expect command (BatchExpect). +func (bt *BExpT) Cmd() int { + return BatchExpect +} + +// Timeout returns the timeout in seconds. +func (bt *BExpT) Timeout() time.Duration { + return time.Duration(bt.T) * time.Second +} + +// Arg returns the Expect regular expression. +func (bt *BExpT) Arg() string { + return bt.R +} + +// Cases always return nil for the Expect command. +func (bt *BExpT) Cases() []Caser { + return nil +} + +// BSnd implements the Batcher interface for Send commands. +type BSnd struct { + S string +} + +// Cmd returns the Send command(BatchSend). +func (bs *BSnd) Cmd() int { + return BatchSend +} + +// Arg returns the data to be sent. +func (bs *BSnd) Arg() string { + return bs.S +} + +// Timeout always returns 0 , Send doesn't have a timeout. +func (bs *BSnd) Timeout() time.Duration { + return 0 +} + +// Cases always returns nil , not used for Send commands. +func (bs *BSnd) Cases() []Caser { + return nil +} + +// BCas implements the Batcher interface for SwitchCase commands. +type BCas struct { + // C holds the Caser array for the SwitchCase command. + C []Caser +} + +// Cmd returns the SwitchCase command(BatchSwitchCase). +func (bc *BCas) Cmd() int { + return BatchSwitchCase +} + +// Arg returns an empty string , not used for SwitchCase. +func (bc *BCas) Arg() string { + return "" +} + +// Timeout returns -1 , setting it to the default value. +func (bc *BCas) Timeout() time.Duration { + return -1 +} + +// Cases returns the Caser structure. +func (bc *BCas) Cases() []Caser { + return bc.C +} + +// BCasT implements the Batcher interfacs for SwitchCase commands, adding a timeout option +// to the BCas type. +type BCasT struct { + // Cs holds the Caser array for the SwitchCase command. + C []Caser + // Tout holds the SwitchCase timeout in seconds. + T int +} + +// Timeout returns the timeout in seconds. +func (bct *BCasT) Timeout() time.Duration { + return time.Duration(bct.T) * time.Second +} + +// Cmd returns the SwitchCase command(BatchSwitchCase). +func (bct *BCasT) Cmd() int { + return BatchSwitchCase +} + +// Arg returns an empty string , not used for SwitchCase. +func (bct *BCasT) Arg() string { + return "" +} + +// Cases returns the Caser structure. +func (bct *BCasT) Cases() []Caser { + return bct.C +} + +// Tag represents the state for a Caser. +type Tag int32 + +const ( + // OKTag marks the desired state was reached. + OKTag = Tag(iota) + // FailTag means reaching this state will fail the Switch/Case. + FailTag + // ContinueTag will recheck for matches. + ContinueTag + // NextTag skips match and continues to the next one. + NextTag + // NoTag signals no tag was set for this case. + NoTag +) + +// OK returns the OK Tag and status. +func OK() func() (Tag, *Status) { + return func() (Tag, *Status) { + return OKTag, NewStatus(codes.OK, "state reached") + } +} + +// Fail returns Fail Tag and status. +func Fail(s *Status) func() (Tag, *Status) { + return func() (Tag, *Status) { + return FailTag, s + } +} + +// Continue returns the Continue Tag and status. +func Continue(s *Status) func() (Tag, *Status) { + return func() (Tag, *Status) { + return ContinueTag, s + } +} + +// Next returns the Next Tag and status. +func Next() func() (Tag, *Status) { + return func() (Tag, *Status) { + return NextTag, NewStatus(codes.Unimplemented, "Next returns not implemented") + } +} + +// LogContinue logs the message and returns the Continue Tag and status. +func LogContinue(msg string, s *Status) func() (Tag, *Status) { + return func() (Tag, *Status) { + log.Print(msg) + return ContinueTag, s + } +} + +// Caser is an interface for ExpectSwitchCase and Batch to be able to handle +// both the Case struct and the more script friendly BCase struct. +type Caser interface { + // RE returns a compiled regexp + RE() (*regexp.Regexp, error) + // Send returns the send string + String() string + // Tag returns the Tag. + Tag() (Tag, *Status) + // Retry returns true if there are retries left. + Retry() bool +} + +// Case used by the ExpectSwitchCase to take different Cases. +// Implements the Caser interface. +type Case struct { + // R is the compiled regexp to match. + R *regexp.Regexp + // S is the string to send if Regexp matches. + S string + // T is the Tag for this Case. + T func() (Tag, *Status) + // Rt specifies number of times to retry, only used for cases tagged with Continue. + Rt int +} + +// Tag returns the tag for this case. +func (c *Case) Tag() (Tag, *Status) { + if c.T == nil { + return NoTag, NewStatus(codes.OK, "no Tag set") + } + return c.T() +} + +// RE returns the compiled regular expression. +func (c *Case) RE() (*regexp.Regexp, error) { + return c.R, nil +} + +// Retry decrements the Retry counter and checks if there are any retries left. +func (c *Case) Retry() bool { + defer func() { c.Rt-- }() + return c.Rt > 0 +} + +// Send returns the string to send if regexp matches +func (c *Case) String() string { + return c.S +} + +// BCase with just a string is a bit more friendly to scripting. +// Implements the Caser interface. +type BCase struct { + // R contains the string regular expression. + R string + // S contains the string to be sent if R matches. + S string + // T contains the Tag. + T func() (Tag, *Status) + // Rt contains the number of retries. + Rt int +} + +// RE returns the compiled regular expression. +func (b *BCase) RE() (*regexp.Regexp, error) { + if b.R == "" { + return nil, nil + } + return regexp.Compile(b.R) +} + +// Send returns the string to send. +func (b *BCase) String() string { + return b.S +} + +// Tag returns the BCase Tag. +func (b *BCase) Tag() (Tag, *Status) { + if b.T == nil { + return NoTag, NewStatus(codes.OK, "no Tag set") + } + return b.T() +} + +// Retry decrements the Retry counter and checks if there are any retries left. +func (b *BCase) Retry() bool { + b.Rt-- + return b.Rt > -1 +} + +// Expecter interface primarily to make testing easier. +type Expecter interface { + // Expect reads output from a spawned session and tries matching it with the provided regular expression. + // It returns all output found until match. + Expect(*regexp.Regexp, time.Duration) (string, []string, error) + // ExpectBatch takes an array of BatchEntries and runs through them in order. For every Expect + // command a BatchRes entry is created with output buffer and sub matches. + // Failure of any of the batch commands will stop the execution, returning the results up to the + // failure. + ExpectBatch([]Batcher, time.Duration) ([]BatchRes, error) + // ExpectSwitchCase makes it possible to Expect with multiple regular expressions and actions. Returns the + // full output and submatches of the commands together with an index for the matching Case. + ExpectSwitchCase([]Caser, time.Duration) (string, []string, int, error) + // Send sends data into the spawned session. + Send(string) error + // Close closes the spawned session and files. + Close() error +} + +// GExpect implements the Expecter interface. +type GExpect struct { + // pty holds the virtual terminal used to interact with the spawned commands. + pty *term.PTY + // cmd contains the cmd information for the spawned process. + cmd *exec.Cmd + ssh *ssh.Session + // snd is the channel used by the Send command to send data into the spawned command. + snd chan string + // rcv is used to signal the Expect commands that new data arrived. + rcv chan struct{} + // chkMu lock protecting the check function. + chkMu sync.RWMutex + // chk contains the function to check if the spawned command is alive. + chk func(*GExpect) bool + // cls contains the function to close spawned command. + cls func(*GExpect) error + // timeout contains the default timeout for a spawned command. + timeout time.Duration + // chkDuration contains the duration between checks for new incoming data. + chkDuration time.Duration + // verbose enables verbose logging. + verbose bool + // verboseWriter if set specifies where to write verbose information. + verboseWriter io.Writer + + // mu protects the output buffer. It must be held for any operations on out. + mu sync.Mutex + out bytes.Buffer +} + +// String implements the stringer interface. +func (e *GExpect) String() string { + res := fmt.Sprintf("%p: ", e) + if e.pty != nil { + _, name := e.pty.PTSName() + res += fmt.Sprintf("pty: %s ", name) + } + switch { + case e.cmd != nil: + res += fmt.Sprintf("cmd: %s(%d) ", e.cmd.Path, e.cmd.Process.Pid) + case e.ssh != nil: + res += fmt.Sprint("ssh session ") + } + res += fmt.Sprintf("buf: %q", e.out.String()) + return res +} + +// ExpectBatch takes an array of BatchEntry and executes them in order filling in the BatchRes +// array for any Expect command executed. +func (e *GExpect) ExpectBatch(batch []Batcher, timeout time.Duration) ([]BatchRes, error) { + res := []BatchRes{} + for i, b := range batch { + switch b.Cmd() { + case BatchExpect: + re, err := regexp.Compile(b.Arg()) + if err != nil { + return res, err + } + to := b.Timeout() + if to < 0 { + to = timeout + } + out, match, err := e.Expect(re, to) + res = append(res, BatchRes{i, out, match}) + if err != nil { + return res, err + } + case BatchSend: + if err := e.Send(b.Arg()); err != nil { + return res, err + } + case BatchSwitchCase: + to := b.Timeout() + if to < 0 { + to = timeout + } + out, match, _, err := e.ExpectSwitchCase(b.Cases(), to) + res = append(res, BatchRes{i, out, match}) + if err != nil { + return res, err + } + default: + return res, errors.New("unknown command:" + strconv.Itoa(b.Cmd())) + } + } + return res, nil +} + +func (e *GExpect) check() bool { + e.chkMu.RLock() + defer e.chkMu.RUnlock() + return e.chk(e) +} + +// ExpectSwitchCase checks each Case against the accumulated out buffer, sending specified +// string back. Leaving Send empty will Send nothing to the process. +// Substring expansion can be used eg. +// Case{`vf[0-9]{2}.[a-z]{3}[0-9]{2}\.net).*UP`,`show arp \1`} +// Given: vf11.hnd01.net UP 35 (4) 34 (4) CONNECTED 0 0/0 +// Would send: show arp vf11.hnd01.net +func (e *GExpect) ExpectSwitchCase(cs []Caser, timeout time.Duration) (string, []string, int, error) { + // Compile all regexps + rs := make([]*regexp.Regexp, 0, len(cs)) + for _, c := range cs { + re, err := c.RE() + if err != nil { + return "", []string{""}, -1, err + } + rs = append(rs, re) + } + // Setup timeouts + // timeout == 0 => Just dump the buffer and exit. + // timeout < 0 => Set default value. + if timeout < 0 { + timeout = e.timeout + } + timer := time.NewTimer(timeout) + check := e.chkDuration + // Check if any new data arrived every checkDuration interval. + // If timeout/4 is less than the checkout interval we set the checkout to + // timeout/4. If timeout ends up being 0 we bump it to one to keep the Ticker from + // panicking. + // All this b/c of the unreliable channel send setup in the read function,making it + // possible for Expect* functions to miss the rcv signal. + // + // from read(): + // // Ping Expect function + // select { + // case e.rcv <- struct{}{}: + // default: + // } + // + // A signal is only sent if any Expect function is running. Expect could miss it + // while playing around with buffers and matching regular expressions. + if timeout>>2 < check { + check = timeout >> 2 + if check <= 0 { + check = 1 + } + } + chTicker := time.NewTicker(check) + defer chTicker.Stop() + // Read in current data and start actively check for matches. + var tbuf bytes.Buffer + if _, err := io.Copy(&tbuf, e); err != nil { + return tbuf.String(), nil, -1, fmt.Errorf("io.Copy failed: %v", err) + } + for { + L1: + for i, c := range cs { + if rs[i] == nil { + continue + } + match := rs[i].FindStringSubmatch(tbuf.String()) + if match == nil { + continue + } + + t, s := c.Tag() + if t == NextTag && !c.Retry() { + continue + } + + if e.verbose { + if e.verboseWriter != nil { + vStr := fmt.Sprintln(term.Green("Match for RE:").String() + fmt.Sprintf(" %q found: %q Buffer: %s", rs[i].String(), match, tbuf.String())) + for n, bytesRead, err := 0, 0, error(nil); bytesRead < len(vStr); bytesRead += n { + n, err = e.verboseWriter.Write([]byte(vStr)[n:]) + if err != nil { + log.Printf("Write to Verbose Writer failed: %v", err) + break + } + } + } else { + log.Printf("Match for RE: %q found: %q Buffer: %q", rs[i].String(), match, tbuf.String()) + } + } + + // Clear the buffer directly after match. + o := tbuf.String() + tbuf.Reset() + + st := c.String() + // Replace the submatches \[0-9]+ in the send string. + if len(match) > 1 && len(st) > 0 { + for i := 1; i < len(match); i++ { + // \(submatch) will be expanded in the Send string. + // To escape use \\(number). + si := strconv.Itoa(i) + r := strings.NewReplacer(`\\`+si, `\`+si, `\`+si, `\\`+si) + st = r.Replace(st) + st = strings.Replace(st, `\\`+si, match[i], -1) + } + } + // Don't send anything if string is empty. + if st != "" { + if err := e.Send(st); err != nil { + return o, match, i, fmt.Errorf("failed to send: %q err: %v", st, err) + } + } + // Tag handling. + switch t { + case OKTag, FailTag, NoTag: + return o, match, i, s.Err() + case ContinueTag: + if !c.Retry() { + return o, match, i, s.Err() + } + break L1 + case NextTag: + break L1 + default: + s = NewStatusf(codes.Unknown, "Tag: %d unknown, err: %v", t, s) + } + return o, match, i, s.Err() + } + if !e.check() { + nr, err := io.Copy(&tbuf, e) + if err != nil { + return tbuf.String(), nil, -1, fmt.Errorf("io.Copy failed: %v", err) + } + if nr == 0 { + return tbuf.String(), nil, -1, errors.New("expect: Process not running") + } + } + select { + case <-timer.C: + // Expect timeout. + nr, err := io.Copy(&tbuf, e) + if err != nil { + return tbuf.String(), nil, -1, fmt.Errorf("io.Copy failed: %v", err) + } + // If we got no new data we return otherwise give it another chance to match. + if nr == 0 { + return tbuf.String(), nil, -1, TimeoutError(timeout) + } + timer = time.NewTimer(timeout) + case <-chTicker.C: + // Periodical timer to make sure data is handled in case the <-e.rcv channel + // was missed. + if _, err := io.Copy(&tbuf, e); err != nil { + return tbuf.String(), nil, -1, fmt.Errorf("io.Copy failed: %v", err) + } + case <-e.rcv: + // Data to fetch. + if _, err := io.Copy(&tbuf, e); err != nil { + return tbuf.String(), nil, -1, fmt.Errorf("io.Copy failed: %v", err) + } + } + } +} + +// GenOptions contains the options needed to set up a generic Spawner. +type GenOptions struct { + // In is where Expect Send messages will be written. + In io.WriteCloser + // Out will be read and matched by the expecter. + Out io.Reader + // Wait is used by expect to know when the session is over and cleanup of io Go routines should happen. + Wait func() error + // Close will be called as part of the expect Close, should normally include a Close of the In WriteCloser. + Close func() error + // Check is called everytime a Send or Expect function is called to makes sure the session is still running. + Check func() bool +} + +// SpawnGeneric is used to write generic Spawners. It returns an Expecter. The returned channel will give the return +// status of the spawned session, in practice this means the return value of the provided Wait function. +func SpawnGeneric(opt *GenOptions, timeout time.Duration, opts ...Option) (*GExpect, <-chan error, error) { + switch { + case opt == nil: + return nil, nil, errors.New("GenOptions is ") + case opt.In == nil: + return nil, nil, errors.New("In can't be ") + case opt.Out == nil: + return nil, nil, errors.New("Out can't be ") + case opt.Wait == nil: + return nil, nil, errors.New("Wait can't be ") + case opt.Close == nil: + return nil, nil, errors.New("Close can't be ") + case opt.Check == nil: + return nil, nil, errors.New("Check can't be ") + } + if timeout < 1 { + timeout = DefaultTimeout + } + e := &GExpect{ + rcv: make(chan struct{}), + snd: make(chan string), + timeout: timeout, + chkDuration: checkDuration, + cls: func(e *GExpect) error { + return opt.Close() + }, + chk: func(e *GExpect) bool { + return opt.Check() + }, + } + for _, o := range opts { + o(e) + } + errCh := make(chan error, 1) + go e.waitForSession(errCh, opt.Wait, opt.In, opt.Out, nil) + return e, errCh, nil +} + +// SpawnFake spawns an expect.Batcher. +func SpawnFake(b []Batcher, timeout time.Duration, opt ...Option) (*GExpect, <-chan error, error) { + rr, rw := io.Pipe() + wr, ww := io.Pipe() + done := make(chan struct{}) + srv, _, err := SpawnGeneric(&GenOptions{ + In: ww, + Out: rr, + Wait: func() error { + <-done + return nil + }, + Close: func() error { + return ww.Close() + }, + Check: func() bool { return true }, + }, timeout, opt...) + if err != nil { + return nil, nil, err + } + + go func() { + res, err := srv.ExpectBatch(b, timeout) + if err != nil { + log.Printf("ExpectBatch(%v,%v) failed: %v, out: %v", b, timeout, err, res) + } + close(done) + }() + + return SpawnGeneric(&GenOptions{ + In: rw, + Out: wr, + Close: func() error { + return rw.Close() + }, + Check: func() bool { return true }, + Wait: func() error { + <-done + return nil + }, + }, timeout, opt...) +} + +// SpawnWithArgs starts a new process and collects the output. The error +// channel returns the result of the command Spawned when it finishes. +// Arguments may contain spaces. +func SpawnWithArgs(command []string, timeout time.Duration, opts ...Option) (*GExpect, <-chan error, error) { + pty, err := term.OpenPTY() + if err != nil { + return nil, nil, err + } + var t term.Termios + t.Raw() + t.Set(pty.Slave) + + if timeout < 1 { + timeout = DefaultTimeout + } + // Get the command up and running + cmd := exec.Command(command[0], command[1:]...) + // This ties the commands Stdin,Stdout & Stderr to the virtual terminal we created + cmd.Stdin, cmd.Stdout, cmd.Stderr = pty.Slave, pty.Slave, pty.Slave + // New process needs to be the process leader and control of a tty + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setsid: true, + Setctty: true} + e := &GExpect{ + rcv: make(chan struct{}), + snd: make(chan string), + cmd: cmd, + timeout: timeout, + chkDuration: checkDuration, + pty: pty, + cls: func(e *GExpect) error { + if e.cmd != nil { + return e.cmd.Process.Kill() + } + return nil + }, + chk: func(e *GExpect) bool { + if e.cmd.Process == nil { + return false + } + // Sending Signal 0 to a process returns nil if process can take a signal , something else if not. + return e.cmd.Process.Signal(syscall.Signal(0)) == nil + }, + } + for _, o := range opts { + o(e) + } + + res := make(chan error, 1) + go e.runcmd(res) + // Wait until command started + return e, res, <-res +} + +// Spawn starts a new process and collects the output. The error channel +// returns the result of the command Spawned when it finishes. Arguments may +// not contain spaces. +func Spawn(command string, timeout time.Duration, opts ...Option) (*GExpect, <-chan error, error) { + return SpawnWithArgs(strings.Fields(command), timeout, opts...) +} + +// SpawnSSH starts an interactive SSH session,ties it to a PTY and collects the output. The returned channel sends the +// state of the SSH session after it finishes. +func SpawnSSH(sshClient *ssh.Client, timeout time.Duration, opts ...Option) (*GExpect, <-chan error, error) { + tios := term.Termios{} + tios.Raw() + tios.Wz.WsCol, tios.Wz.WsRow = sshTermWidth, sshTermHeight + return SpawnSSHPTY(sshClient, timeout, tios, opts...) +} + +const ( + sshTerm = "xterm" + sshTermWidth = 132 + sshTermHeight = 43 +) + +// SpawnSSHPTY starts an interactive SSH session and ties it to a local PTY, optionally requests a remote PTY. +func SpawnSSHPTY(sshClient *ssh.Client, timeout time.Duration, term term.Termios, opts ...Option) (*GExpect, <-chan error, error) { + if sshClient == nil { + return nil, nil, errors.New("*ssh.Client is nil") + } + if timeout < 1 { + timeout = DefaultTimeout + } + // Setup interactive session + session, err := sshClient.NewSession() + if err != nil { + return nil, nil, err + } + e := &GExpect{ + rcv: make(chan struct{}), + snd: make(chan string), + chk: func(e *GExpect) bool { + if e.ssh == nil { + return false + } + _, err := e.ssh.SendRequest("dummy", false, nil) + return err == nil + }, + cls: func(e *GExpect) error { + if e.ssh != nil { + return e.ssh.Close() + } + return nil + }, + ssh: session, + timeout: timeout, + chkDuration: checkDuration, + } + for _, o := range opts { + o(e) + } + if term.Wz.WsCol == 0 { + term.Wz.WsCol = sshTermWidth + } + if term.Wz.WsRow == 0 { + term.Wz.WsRow = sshTermHeight + } + if err := session.RequestPty(sshTerm, int(term.Wz.WsRow), int(term.Wz.WsCol), term.ToSSH()); err != nil { + return nil, nil, err + } + inPipe, err := session.StdinPipe() + if err != nil { + return nil, nil, err + } + outPipe, err := session.StdoutPipe() + if err != nil { + return nil, nil, err + } + errPipe, err := session.StderrPipe() + if err != nil { + return nil, nil, err + } + if err := session.Shell(); err != nil { + return nil, nil, err + } + // Shell started. + errCh := make(chan error, 1) + go e.waitForSession(errCh, session.Wait, inPipe, outPipe, errPipe) + return e, errCh, nil +} + +func (e *GExpect) waitForSession(r chan error, wait func() error, sIn io.WriteCloser, sOut io.Reader, sErr io.Reader) { + chDone := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-chDone: + return + case sstr, ok := <-e.snd: + if !ok { + log.Printf("Send channel closed") + return + } + if _, err := sIn.Write([]byte(sstr)); err != nil || !e.check() { + log.Printf("Write failed: %v", err) + return + } + } + } + }() + rdr := func(out io.Reader) { + defer wg.Done() + buf := make([]byte, bufferSize) + for { + nr, err := out.Read(buf) + if err != nil || !e.check() { + if err == io.EOF { + if e.verbose { + log.Printf("read closing down: %v", err) + } + return + } + return + } + e.mu.Lock() + e.out.Write(buf[:nr]) + e.mu.Unlock() + // Inform Expect (if it's currently running) that there's some new data to look through. + select { + case e.rcv <- struct{}{}: + default: + } + } + } + wg.Add(1) + go rdr(sOut) + if sErr != nil { + wg.Add(1) + go rdr(sErr) + } + err := wait() + close(chDone) + wg.Wait() + r <- err +} + +// Close closes the Spawned session. +func (e *GExpect) Close() error { + return e.cls(e) +} + +// Read implements the reader interface for the out buffer. +func (e *GExpect) Read(p []byte) (nr int, err error) { + e.mu.Lock() + defer e.mu.Unlock() + return e.out.Read(p) +} + +// Send sends a string to spawned process. +func (e *GExpect) Send(in string) error { + if !e.check() { + return errors.New("expect: Process not running") + } + e.snd <- in + if e.verbose { + if e.verboseWriter != nil { + vStr := fmt.Sprintln(term.Blue("Sent:").String() + fmt.Sprintf(" %q", in)) + for n, bytesRead, err := 0, 0, error(nil); bytesRead < len(vStr); bytesRead += n { + n, err = e.verboseWriter.Write([]byte(vStr)[n:]) + if err != nil { + log.Printf("Write to Verbose Writer failed: %v", err) + break + } + return nil + } + } + log.Printf("Sent: %q", in) + } + return nil +} + +// runcmd executes the command and Wait for the return value. +func (e *GExpect) runcmd(res chan error) { + if err := e.cmd.Start(); err != nil { + res <- err + return + } + // Moving the go read/write functions here makes sure the command is started before first checking if it's running. + clean := make(chan struct{}) + chDone := e.goIO(clean) + // Signal command started + res <- nil + cErr := e.cmd.Wait() + close(chDone) + e.pty.Slave.Close() + // make sure the read/send routines are done before closing the pty. + <-clean + res <- cErr +} + +// goIO starts the io handlers. +func (e *GExpect) goIO(clean chan struct{}) (done chan struct{}) { + done = make(chan struct{}) + var ptySync sync.WaitGroup + ptySync.Add(2) + go e.read(done, &ptySync) + go e.send(done, &ptySync) + go func() { + ptySync.Wait() + e.pty.Master.Close() + close(clean) + }() + return done +} + +// Expect reads spawned processes output looking for input regular expression. +// Timeout set to 0 makes Expect return the current buffer. +// Negative timeout value sets it to Default timeout. +func (e *GExpect) Expect(re *regexp.Regexp, timeout time.Duration) (string, []string, error) { + out, match, _, err := e.ExpectSwitchCase([]Caser{&Case{re, "", nil, 0}}, timeout) + return out, match, err +} + +// Options sets the specified options. +func (e *GExpect) Options(opts ...Option) (prev Option) { + for _, o := range opts { + prev = o(e) + } + return prev +} + +// read reads from the PTY master and forwards to active Expect function. +func (e *GExpect) read(done chan struct{}, ptySync *sync.WaitGroup) { + defer ptySync.Done() + buf := make([]byte, bufferSize) + for { + nr, err := e.pty.Master.Read(buf) + if err != nil || !e.check() { + if err == io.EOF { + if e.verbose { + log.Printf("read closing down: %v", err) + } + return + } + return + } + // Add to buffer + e.mu.Lock() + e.out.Write(buf[:nr]) + e.mu.Unlock() + // Ping Expect function + select { + case e.rcv <- struct{}{}: + default: + } + } +} + +// send writes to the PTY master. +func (e *GExpect) send(done chan struct{}, ptySync *sync.WaitGroup) { + defer ptySync.Done() + for { + select { + case <-done: + return + case sstr, ok := <-e.snd: + if !ok { + return + } + if _, err := e.pty.Master.Write([]byte(sstr)); err != nil || !e.check() { + log.Printf("send failed: %v", err) + break + } + } + } +} diff --git a/vendor/github.com/google/goterm/AUTHORS b/vendor/github.com/google/goterm/AUTHORS new file mode 100644 index 0000000000..15167cd746 --- /dev/null +++ b/vendor/github.com/google/goterm/AUTHORS @@ -0,0 +1,3 @@ +# This source code refers to The Go Authors for copyright purposes. +# The master list of authors is in the main Go distribution, +# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/github.com/google/goterm/LICENSE b/vendor/github.com/google/goterm/LICENSE new file mode 100644 index 0000000000..931520b99e --- /dev/null +++ b/vendor/github.com/google/goterm/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/google/goterm/term/color.go b/vendor/github.com/google/goterm/term/color.go new file mode 100644 index 0000000000..4bba716073 --- /dev/null +++ b/vendor/github.com/google/goterm/term/color.go @@ -0,0 +1,677 @@ +// Copyright 2015 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. + +package term + +/* +Some simple functions to add colors and attributes to terminals. + +The base colors are types implementing the Stringer interface, this makes +it very simple to give a color to arbitrary strings. Also handy to have the raw string still +available for comparisons and such. + + g := Green("Green world") + fmt.Println("Hello",g) + fmt.Println(Red("Warning!")) + + var col fmt.Stringer + switch { + case atk == 0: + col = Blue("5 FADE OUT") + case atk < 4: + col = Green("4 DOUBLE TAKE") + case atk <10: + col = Yellow("3 ROUND HOUSE") + case atk <50: + col = Red("2 FAST PACE") + case atk >= 50: + col = Blinking("1 COCKED PISTOL") + } + fmt.Println("Defcon: ",col) +*/ + +import ( + "errors" + "fmt" + "math/rand" + "strconv" +) + +type stringer interface { + String() string +} + +// colorEnable toggles colors on/off. +var colorEnable = true + +// ColorEnable activates the terminal colors , this is the default. +func ColorEnable() { + colorEnable = true +} + +// ColorDisable disables the terminal colors. +func ColorDisable() { + colorEnable = false +} + +// Terminal Color and modifier codes +const ( + CSI = "\033[" + FgBlack = "30" + FgRed = "31" + FgGreen = "32" + FgYellow = "33" + FgBlue = "34" + FgMagenta = "35" + FgCyan = "36" + FgWhite = "37" + FgDefault = "39" + F256 = "38" + BgBlack = "40" + BgRed = "41" + BgGreen = "42" + BgYellow = "43" + BgBlue = "44" + BgMagenta = "45" + BgCyan = "46" + BgWhite = "47" + BgDefault = "49" + Bg256 = "48" + Blink = "5" + Ital = "3" + Underln = "4" + Faint = "2" + Bld = "1" + NoMode = "0" +) + +// Standard colors +// Foreground + +// Green implements the Stringer interface to print string foreground in Green color. +type Green string + +// Blue implements the Stringer interface to print string foreground in Blue color. +type Blue string + +// Red implements the Stringer interface to print string foreground in Red color. +type Red string + +// Yellow implements the Stringer interface to print string foreground in Yellow color. +type Yellow string + +// Magenta implements the Stringer interface to print string foreground in Magenta color. +type Magenta string + +// Cyan implements the Stringer interface to print string foreground in Cyan color. +type Cyan string + +// White implements the Stringer interface to print string foreground in White color. +type White string + +// Black implements the Stringer interface to print string foreground in Black color. +type Black string + +// Random implements the Stringer interface to print string foreground in Random color. +type Random string + +// Background + +// BGreen implements the Stringer interface to print string background in Green color. +type BGreen string + +// BBlue implements the Stringer interface to print string background in Blue color. +type BBlue string + +// BRed implements the Stringer interface to print string background in Red color. +type BRed string + +// BYellow implements the Stringer interface to print string background in Yellow color. +type BYellow string + +// BRandom implements the Stringer interface to print string background in Random color. +type BRandom string + +// BMagenta implements the Stringer interface to print string background in Magenta color. +type BMagenta string + +// BCyan implements the Stringer interface to print string background in Cyan color. +type BCyan string + +// BWhite implements the Stringer interface to print string background in White color. +type BWhite string + +// BBlack implements the Stringer interface to print string background in Black color. +type BBlack string + +// Set color + +// Color is the type returned by the colour setters to print any terminal colour. +type Color string + +// ColorRandom implements the Stringer interface to print string Random color. +type ColorRandom string + +// Color256Random implements the Stringer interface to print string random 256 color Term style. +type Color256Random string + +// Some modifiers + +// Blinking implements the Stringer interface to print string in Blinking mode. +type Blinking string + +// Underline implements the Stringer interface to print string in Underline mode. +type Underline string + +// Bold implements the Stringer interface to print string in Bold mode. +type Bold string + +//type Bright string -- Doesn't seem to work well + +// Italic implements the Stringer interface to print string foreground in Italic color. +type Italic string + +// colType takes all the base color types and generates proper modifiers. +func colType(col stringer) string { + nMode := FgDefault + var mode, res string + switch c := col.(type) { + case Black: + mode = FgBlack + res = string(c) + case Red: + mode = FgRed + res = string(c) + case Green: + mode = FgGreen + res = string(c) + case Yellow: + mode = FgYellow + res = string(c) + case Blue: + mode = FgBlue + res = string(c) + case Magenta: + mode = FgMagenta + res = string(c) + case Cyan: + mode = FgCyan + res = string(c) + case White: + mode = FgWhite + res = string(c) + case BBlack: + mode = BgBlack + res = string(c) + case BRed: + mode = BgRed + nMode = BgDefault + res = string(c) + case BGreen: + mode = BgGreen + nMode = BgDefault + res = string(c) + case BYellow: + mode = BgYellow + nMode = BgDefault + res = string(c) + case BBlue: + mode = BgBlue + nMode = BgDefault + res = string(c) + case BMagenta: + mode = BgMagenta + nMode = BgDefault + res = string(c) + case BCyan: + mode = BgCyan + nMode = BgDefault + res = string(c) + case BWhite: + mode = BgWhite + nMode = BgDefault + res = string(c) + case Blinking: + mode = Blink + nMode = NoMode + res = string(c) + case Italic: + mode = Ital + nMode = NoMode + res = string(c) + case Underline: + mode = Underln + nMode = NoMode + res = string(c) + case Bold: + nMode = NoMode + mode = Bld + res = string(c) + default: + return "unsupported type" + } + if !colorEnable { + return res + } + return CSI + mode + "m" + res + CSI + nMode + "m" +} + +// Stringers for all the base colors , just fill it in with something and print it +// Foreground + +// String implements the Stringer interface for type Green. +func (c Green) String() string { + return colType(c) +} + +// Greenf returns a Green formatted string. +func Greenf(format string, a ...interface{}) string { + return colType(Green(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type Blue. +func (c Blue) String() string { + return colType(c) +} + +// Bluef returns a Blue formatted string. +func Bluef(format string, a ...interface{}) string { + return colType(Blue(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type Red. +func (c Red) String() string { + return colType(c) +} + +// Redf returns a Red formatted string. +func Redf(format string, a ...interface{}) string { + return colType(Red(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type Yellow. +func (c Yellow) String() string { + return colType(c) +} + +// Yellowf returns a Yellow formatted string. +func Yellowf(format string, a ...interface{}) string { + return colType(Yellow(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type Magenta. +func (c Magenta) String() string { + return colType(c) +} + +// Magentaf returns a Magenta formatted string. +func Magentaf(format string, a ...interface{}) string { + return colType(Magenta(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type White. +func (c White) String() string { + return colType(c) +} + +// Whitef returns a White formatted string. +func Whitef(format string, a ...interface{}) string { + return colType(White(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type Black. +func (c Black) String() string { + return colType(c) +} + +// Blackf returns a Black formatted string. +func Blackf(format string, a ...interface{}) string { + return colType(Black(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type Cyan. +func (c Cyan) String() string { + return colType(c) +} + +// Cyanf returns a Cyan formatted string. +func Cyanf(format string, a ...interface{}) string { + return colType(Cyan(fmt.Sprintf(format, a...))) +} + +// Background + +// String implements the Stringer interface for type BGreen. +func (c BGreen) String() string { + return colType(c) +} + +// BGreenf returns a BGreen formatted string. +func BGreenf(format string, a ...interface{}) string { + return colType(BGreen(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BBlue. +func (c BBlue) String() string { + return colType(c) +} + +// BBluef returns a BBlue formatted string. +func BBluef(format string, a ...interface{}) string { + return colType(BBlue(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BRed. +func (c BRed) String() string { + return colType(c) +} + +// BRedf returns a BRed formatted string. +func BRedf(format string, a ...interface{}) string { + return colType(BRed(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BYellow. +func (c BYellow) String() string { + return colType(c) +} + +// BYellowf returns a BYellow formatted string. +func BYellowf(format string, a ...interface{}) string { + return colType(BYellow(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BMagenta. +func (c BMagenta) String() string { + return colType(c) +} + +// BMagentaf returns a BMagenta formatted string. +func BMagentaf(format string, a ...interface{}) string { + return colType(BMagenta(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BWhite. +func (c BWhite) String() string { + return colType(c) +} + +// BWhitef returns a BWhite formatted string. +func BWhitef(format string, a ...interface{}) string { + return colType(BWhite(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BBlack. +func (c BBlack) String() string { + return colType(c) +} + +// BBlackf returns a BBlack formatted string. +func BBlackf(format string, a ...interface{}) string { + return colType(BBlack(fmt.Sprintf(format, a...))) +} + +// String implements the Stringer interface for type BCyan. +func (c BCyan) String() string { + return colType(c) +} + +// BCyanf returns a BCyan formatted string. +func BCyanf(format string, a ...interface{}) string { + return colType(BCyan(fmt.Sprintf(format, a...))) +} + +// Modifier codes + +// String implements the Stringer interface for type Blinking. +func (c Blinking) String() string { + return colType(c) +} + +// String implements the Stringer interface for type Underline. +func (c Underline) String() string { + return colType(c) +} + +// String implements the Stringer interface for type Bold. +func (c Bold) String() string { + return colType(c) +} + +// String implements the Stringer interface for type Italic. +func (c Italic) String() string { + return colType(c) +} + +// NewColor gives a type Color back with specified fg/bg colors set that can +// be printed with anything using the Stringer iface. +func NewColor(str string, fg string, bg string) (Color, error) { + if fg != "" { + ifg, err := strconv.Atoi(fg) + if err != nil { + return Color(""), err + } + if ifg < 30 && ifg > 37 { + return Color(""), errors.New("fg: " + fg + "not a valid color 30-37") + } + } else { + fg = FgDefault + } + if bg != "" { + ibg, err := strconv.Atoi(bg) + if err != nil { + return Color(""), err + } + if ibg < 40 && ibg > 47 { + return Color(""), errors.New("fg: " + fg + "not a valid color 40-47") + } + } else { + bg = BgDefault + } + return Color(CSI + fg + ";" + bg + "m" + str + CSI + FgDefault + ";" + BgDefault + "m"), nil +} + +// String the stringer interface for all base color types. +func (c Color) String() string { + if !colorEnable { + clean := make([]byte, 0, len(c)) + src := []byte(c) + L1: + for i := 0; i < len(src); i++ { + // Shortest possible mod. + if len(src) < i+4 { + clean = append(clean, src[i:]...) + return string(clean) + } + if string(src[i:i+2]) == CSI { + // Save current index incase this is not a term mod code. + s := i + // skip forward to end of mod + for i += 2; i < len(src); i++ { + switch src[i] { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';': + // Legal characters in a term mod code. + continue + case 'm': + // End of the term mod code. + continue L1 + default: + // Not a term mod code. + i = s + break + } + } + } + clean = append(clean, src[i]) + } + return string(clean) + } + return string(c) +} + +// NewColor256 gives a type Color back using Term 256 color that can be printed with anything using the Stringer iface. +func NewColor256(str string, fg string, bg string) (Color, error) { + if fg != "" { + ifg, err := strconv.Atoi(fg) + if err != nil { + return Color(""), err + } + if ifg < 0 && ifg > 256 { + return Color(""), errors.New("fg: " + fg + " not a valid color 0-256") + } + } + if bg != "" { + ibg, err := strconv.Atoi(bg) + if err != nil { + return Color(""), err + } + if ibg < 0 && ibg > 256 { + return Color(""), errors.New("bg: " + bg + " not a valid color 0-256") + } + } + tstr := CSI + F256 + ";5;" + fg + ";" + Bg256 + ";5;" + bg + "m" + tstr += str + return Color(tstr + CSI + FgDefault + ";5;" + BgDefault + ";5;" + "m"), nil +} + +// NewColorRGB takes R G B and returns a ColorRGB type that can be printed by anything using the Stringer iface. +// Only Konsole to my knowledge that supports 24bit color +func NewColorRGB(str string, red uint8, green uint8, blue uint8) Color { + ired := strconv.Itoa(int(red)) + igreen := strconv.Itoa(int(green)) + iblue := strconv.Itoa(int(blue)) + tstr := CSI + F256 + ";2;" + ired + ";" + igreen + ";" + iblue + "m" + tstr += str + return Color(tstr + CSI + FgDefault + ";5;" + BgDefault + ";5;" + "m") +} + +// String is a random color stringer. +func (c ColorRandom) String() string { + if !colorEnable { + return string(c) + } + ifg := rand.Int()%8 + 30 + ibg := rand.Int()%8 + 40 + res := CSI + strconv.Itoa(ifg) + ";" + strconv.Itoa(ibg) + "m" + res += string(c) + res += CSI + strconv.Itoa(ifg) + ";" + strconv.Itoa(ibg) + "m" + return res +} + +// String gives a random fg color everytime it's printed. +func (c Random) String() string { + if !colorEnable { + return string(c) + } + ifg := int(rand.Int()%8 + 30) + res := CSI + strconv.Itoa(ifg) + "m" + res += string(c) + strconv.Itoa(int(ifg)) + res += CSI + FgDefault + "m" + return res +} + +// String gives a random bg color everytime it's printed. +func (c BRandom) String() string { + if !colorEnable { + return string(c) + } + ibg := rand.Int()%8 + 40 + res := CSI + strconv.Itoa(ibg) + "m" + res += string(c) + strconv.Itoa(ibg) + res += CSI + BgDefault + "m" + return res +} + +// NewCombo Takes a combination of modes and return a string with them all combined. +func NewCombo(s string, mods ...string) Color { + var col, bcol, mod bool + modstr := CSI + tracking := make(map[string]bool) + for _, m := range mods { + switch m { + case FgBlack, FgRed, FgGreen, FgYellow, FgBlue, FgMagenta, FgCyan, FgWhite: + if col { + continue + } + col = true + case BgBlack, BgRed, BgGreen, BgYellow, BgBlue, BgMagenta, BgCyan, BgWhite: + if bcol { + continue + } + bcol = true + case Bld, Faint, Ital, Underln, Blink: + if tracking[m] { + continue + } + tracking[m] = true + mod = true + default: + continue + } + modstr += m + ";" + } + end := CSI + if col { + end += FgDefault + } + if bcol { + if col { + end += ";" + } + end += BgDefault + } + if mod { + if col || bcol { + end += ";" + } + end += NoMode + } + end += "m" + modstr = modstr[:len(modstr)-1] + "m" + modstr += s + modstr += end + return Color(modstr) +} + +// TestTerm tries out most of the functions in this package and return +// a colourful string. Could be used to check what your terminal supports. +func TestTerm() string { + res := "Standard 8:\n" + res += "Fg:\t" + for c := 30; c < 38; c++ { + tres, _ := NewColor("#", strconv.Itoa(c), "") + res += tres.String() + } + res += "\nBg:\t" + for c := 40; c < 48; c++ { + tres, _ := NewColor(" ", "", strconv.Itoa(c)) + res += tres.String() + } + res += "\nStandard 16:\t" + for c := 0; c < 16; c++ { + tcol, _ := NewColor256(" ", "", strconv.Itoa(c)) + res += tcol.String() + } + res += "\n" + res += "256 Col:\n" + // 6x6x6 cubes are trendy + for row, base := 1, 0; row <= 6; row++ { + base = (row * 6) + 9 // Step over the first 16 base colors + for cubes := 1; cubes <= 6; cubes++ { + for column := 1; column <= 6; column++ { + tcol, _ := NewColor256(" ", "", strconv.Itoa(base+column)) + res += tcol.String() + } + base += 36 // 6 * 6 + } + res += "\n" + } + // Grayscale left. + res += "Grayscales:\n" + for c := 232; c <= 255; c++ { + tcol, _ := NewColor256(" ", "", strconv.Itoa(c)) + res += tcol.String() + } + return res +} diff --git a/vendor/github.com/google/goterm/term/ssh.go b/vendor/github.com/google/goterm/term/ssh.go new file mode 100644 index 0000000000..2e7619cec6 --- /dev/null +++ b/vendor/github.com/google/goterm/term/ssh.go @@ -0,0 +1,203 @@ +// Copyright 2015 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. + + +package term + +const ( + // Terminal attribute types. + sshIflag = iota + sshOflag + sshCflag + sshLflag + sshCchar + sshTspeed + sshNOP + + // SSH terminal attributes. + sshTTYOPEND = 0 + sshVINTR = 1 + sshVQUIT = 2 + sshVERASE = 3 + sshVKILL = 4 + sshVEOF = 5 + sshVEOL = 6 + sshVEOL2 = 7 + sshVSTART = 8 + sshVSTOP = 9 + sshVSUSP = 10 + sshVDSUSP = 11 + sshVREPRINT = 12 + sshVWERASE = 13 + sshVLNEXT = 14 + sshVFLUSH = 15 + sshVSWTCH = 16 + sshVSTATUS = 17 + sshVDISCARD = 18 + sshIGNPAR = 30 + sshPARMRK = 31 + sshINPCK = 32 + sshISTRIP = 33 + sshINLCR = 34 + sshIGNCR = 35 + sshICRNL = 36 + sshIUCLC = 37 + sshIXON = 38 + sshIXANY = 39 + sshIXOFF = 40 + sshIMAXBEL = 41 + sshISIG = 50 + sshICANON = 51 + sshXCASE = 52 + sshECHO = 53 + sshECHOE = 54 + sshECHOK = 55 + sshECHONL = 56 + sshNOFLSH = 57 + sshTOSTOP = 58 + sshIEXTEN = 59 + sshECHOCTL = 60 + sshECHOKE = 61 + sshPENDIN = 62 + sshOPOST = 70 + sshOLCUC = 71 + sshONLCR = 72 + sshOCRNL = 73 + sshONOCR = 74 + sshONLRET = 75 + sshCS7 = 90 + sshCS8 = 91 + sshPARENB = 92 + sshPARODD = 93 + sshTTYOPISPEED = 128 + sshTTYOPOSPEED = 129 +) + +var convertSSH = map[uint8]struct { + tType uint + native uint32 +}{ + sshTTYOPEND: {tType: sshNOP}, + sshVINTR: {tType: sshCchar, native: VINTR}, + sshVQUIT: {tType: sshCchar, native: VQUIT}, + sshVERASE: {tType: sshCchar, native: VERASE}, + sshVKILL: {tType: sshCchar, native: VKILL}, + sshVEOF: {tType: sshCchar, native: VEOF}, + sshVEOL: {tType: sshCchar, native: VEOL}, + sshVEOL2: {tType: sshCchar, native: VEOL2}, + sshVSTART: {tType: sshCchar, native: VSTART}, + sshVSTOP: {tType: sshCchar, native: VSTOP}, + sshVSUSP: {tType: sshCchar, native: VSUSP}, + sshVDSUSP: {tType: sshCchar, native: sshNOP}, + sshVREPRINT: {tType: sshCchar, native: VREPRINT}, + sshVWERASE: {tType: sshCchar, native: VWERASE}, + sshVLNEXT: {tType: sshCchar, native: VLNEXT}, + sshVFLUSH: {tType: sshNOP}, + sshVSWTCH: {tType: sshCchar, native: VSWTC}, + sshVSTATUS: {tType: sshNOP}, + sshVDISCARD: {tType: sshCchar, native: VDISCARD}, + sshIGNPAR: {tType: sshIflag, native: IGNPAR}, + sshPARMRK: {tType: sshIflag, native: PARMRK}, + sshINPCK: {tType: sshIflag, native: INPCK}, + sshISTRIP: {tType: sshIflag, native: ISTRIP}, + sshINLCR: {tType: sshIflag, native: INLCR}, + sshIGNCR: {tType: sshIflag, native: IGNCR}, + sshICRNL: {tType: sshIflag, native: ICRNL}, + sshIUCLC: {tType: sshIflag, native: IUCLC}, + sshIXON: {tType: sshIflag, native: IXON}, + sshIXANY: {tType: sshIflag, native: IXANY}, + sshIXOFF: {tType: sshIflag, native: IXOFF}, + sshIMAXBEL: {tType: sshIflag, native: IMAXBEL}, + sshISIG: {tType: sshLflag, native: ISIG}, + sshICANON: {tType: sshLflag, native: ICANON}, + sshXCASE: {tType: sshLflag, native: XCASE}, + sshECHO: {tType: sshLflag, native: ECHO}, + sshECHOE: {tType: sshLflag, native: ECHOE}, + sshECHOK: {tType: sshLflag, native: ECHOK}, + sshECHONL: {tType: sshLflag, native: ECHONL}, + sshNOFLSH: {tType: sshLflag, native: NOFLSH}, + sshTOSTOP: {tType: sshLflag, native: TOSTOP}, + sshIEXTEN: {tType: sshLflag, native: IEXTEN}, + sshECHOCTL: {tType: sshLflag, native: ECHOCTL}, + sshECHOKE: {tType: sshLflag, native: ECHOKE}, + sshPENDIN: {tType: sshNOP}, + sshOPOST: {tType: sshOflag, native: OPOST}, + sshOLCUC: {tType: sshOflag, native: OLCUC}, + sshONLCR: {tType: sshOflag, native: ONLCR}, + sshOCRNL: {tType: sshOflag, native: OCRNL}, + sshONOCR: {tType: sshOflag, native: ONOCR}, + sshONLRET: {tType: sshOflag, native: ONLRET}, + sshCS7: {tType: sshCflag, native: CS7}, + sshCS8: {tType: sshCflag, native: CS8}, + sshPARENB: {tType: sshCflag, native: PARENB}, + sshPARODD: {tType: sshCflag, native: PARODD}, + sshTTYOPISPEED: {tType: sshTspeed}, + sshTTYOPOSPEED: {tType: sshTspeed}, +} + +// ToSSH converts the Termios attributes to SSH attributes usable as ssh.TerminalModes. +func (t *Termios) ToSSH() map[uint8]uint32 { + sshModes := make(map[uint8]uint32, len(convertSSH)) + var flags uint32 + for sshID, tios := range convertSSH { + switch tios.tType { + case sshIflag: + flags = t.Iflag + case sshOflag: + flags = t.Oflag + case sshLflag: + flags = t.Lflag + case sshCflag: + flags = t.Cflag + case sshCchar: + sshModes[sshID] = uint32(t.Cc[tios.native]) + continue + case sshTspeed: + sshModes[sshTTYOPISPEED], sshModes[sshTTYOPOSPEED] = t.Ispeed, t.Ospeed + continue + default: + continue + } + var onOff uint32 + if tios.native&flags > 0 { + onOff = 1 + } + sshModes[sshID] = onOff + } + return sshModes +} + +// FromSSH converts SSH attributes to Termios attributes. +func (t *Termios) FromSSH(termModes map[uint8]uint32) { + var flags *uint32 + for sshID, val := range termModes { + switch convertSSH[sshID].tType { + case sshIflag: + flags = &t.Iflag + case sshOflag: + flags = &t.Oflag + case sshLflag: + flags = &t.Lflag + case sshCflag: + flags = &t.Cflag + case sshCchar: + t.Cc[convertSSH[sshID].native] = byte(val) + continue + case sshTspeed: + if sshID == sshTTYOPISPEED { + t.Ispeed = val + } else { + t.Ospeed = val + } + continue + default: + continue + } + if val > 0 { + *flags |= convertSSH[sshID].native + } else { + *flags &^= convertSSH[sshID].native + } + } +} diff --git a/vendor/github.com/google/goterm/term/termios.go b/vendor/github.com/google/goterm/term/termios.go new file mode 100644 index 0000000000..e1aee994f0 --- /dev/null +++ b/vendor/github.com/google/goterm/term/termios.go @@ -0,0 +1,392 @@ +// Copyright 2015 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. + + +/* +Package term implements a subset of the C termios library to interface with Terminals. + +This package allows the caller to get and set most Terminal capabilites +and sizes as well as create PTYs to enable writing things like script, +screen, tmux, and expect. + +The Termios type is used for setting/getting Terminal capabilities while +the PTY type is used for handling virtual terminals. + +Currently this part of this lib is Linux specific. + +Also implements a simple version of readline in pure Go and some Stringers +for terminal colors and attributes. +*/ +package term + +import ( + "errors" + "os" + "strconv" + "strings" + "syscall" + "unsafe" +) + +// IOCTL terminal stuff. +const ( + TCGETS = 0x5401 // TCGETS get terminal attributes + TCSETS = 0x5402 // TCSETS set terminal attributes + TIOCGWINSZ = 0x5413 // TIOCGWINSZ used to get the terminal window size + TIOCSWINSZ = 0x5414 // TIOCSWINSZ used to set the terminal window size + TIOCGPTN = 0x80045430 // TIOCGPTN IOCTL used to get the PTY number + TIOCSPTLCK = 0x40045431 // TIOCSPTLCK IOCT used to lock/unlock PTY + CBAUD = 0010017 // CBAUD Serial speed settings + CBAUDEX = 0010000 // CBAUDX Serial speed settings +) + +// INPUT handling terminal flags +// see 'man stty' for further info about most of the constants +const ( + IGNBRK = 0000001 // IGNBRK ignore break characters + BRKINT = 0000002 // BRKINT Break genereates an interrupt signal + IGNPAR = 0000004 // IGNPAR Ignore characters with parity errors + PARMRK = 0000010 // PARMRK Mark parity errors byte{ff,0} + INPCK = 0000020 // INPCK enable parity checking + ISTRIP = 0000040 // ISTRIP Clear 8th bit of input characters + INLCR = 0000100 // INLCR Translate LF => CR + IGNCR = 0000200 // IGNCR Ignore Carriage Return + ICRNL = 0000400 // ICRNL Translate CR => NL + IUCLC = 0001000 // IUCLC Translate uppercase to lowercase + IXON = 0002000 // IXON Enable flow control + IXANY = 0004000 // IXANY let any char restart input + IXOFF = 0010000 // IXOFF start sending start/stop chars + IMAXBEL = 0020000 // IMAXBEL Sound the bell and skip flushing input buffer + IUTF8 = 0040000 // IUTF8 assume input being utf-8 +) + +// OUTPUT treatment terminal flags +const ( + OPOST = 0000001 // OPOST post process output + OLCUC = 0000002 // OLCUC translate lower case to upper case + ONLCR = 0000004 // ONLCR Map NL -> CR-NL + OCRNL = 0000010 // OCRNL Map CR -> NL + ONOCR = 0000020 // ONOCR No CR at col 0 + ONLRET = 0000040 // ONLRET NL also do CR + OFILL = 0000100 // OFILL Fillchar for delay + OFDEL = 0000200 // OFDEL use delete instead of null +) + +// TERM control modes. +const ( + CSIZE = 0000060 // CSIZE used as mask when setting character size + CS5 = 0000000 // CS5 char size 5bits + CS6 = 0000020 // CS6 char size 6bits + CS7 = 0000040 // CS7 char size 7bits + CS8 = 0000060 // CS8 char size 8bits + CSTOPB = 0000100 // CSTOPB two stop bits + CREAD = 0000200 // CREAD enable input + PARENB = 0000400 // PARENB generate and expect parity bit + PARODD = 0001000 // PARODD set odd parity + HUPCL = 0002000 // HUPCL send HUP when last process closes term + CLOCAL = 0004000 // CLOCAL no modem control signals +) + +// TERM modes +const ( + ISIG = 0000001 // ISIG enable Interrupt,quit and suspend chars + ICANON = 0000002 // ICANON enable erase,kill ,werase and rprnt chars + XCASE = 0000004 // XCASE preceedes all uppercase chars with '\' + ECHO = 0000010 // ECHO echo input characters + ECHOE = 0000020 // ECHOE erase => BS - SPACE - BS + ECHOK = 0000040 // ECHOK add newline after kill char + ECHONL = 0000100 // ECHONL echo NL even without other characters + NOFLSH = 0000200 // NOFLSH no flush after interrupt and kill characters + TOSTOP = 0000400 // TOSTOP stop BG jobs trying to write to term + ECHOCTL = 0001000 // ECHOCTL will echo control characters as ^c + ECHOPRT = 0002000 // ECHOPRT will print erased characters between \ / + ECHOKE = 0004000 // ECHOKE kill all line considering ECHOPRT and ECHOE flags + IEXTEN = 0100000 // IEXTEN enable non POSIX special characters +) + +// Control characters +const ( + VINTR = 0 // VINTR char will send an interrupt signal + VQUIT = 1 // VQUIT char will send a quit signal + VERASE = 2 // VEREASE char will erase last typed char + VKILL = 3 // VKILL char will erase current line + VEOF = 4 // VEOF char will send EOF + VTIME = 5 // VTIME set read timeout in tenths of seconds + VMIN = 6 // VMIN set min characters for a complete read + VSWTC = 7 // VSWTC char will switch to a different shell layer + VSTART = 8 // VSTART char will restart output after stopping it + VSTOP = 9 // VSTOP char will stop output + VSUSP = 10 // VSUSP char will send a stop signal + VEOL = 11 // VEOL char will end the line + VREPRINT = 12 // VREPRINT will redraw the current line + VDISCARD = 13 // VDISCARD + VWERASE = 14 // VWERASE char will erase last word typed + VLNEXT = 15 // VLNEXT char will enter the next char quoted + VEOL2 = 16 // VEOL2 char alternate to end line + tNCCS = 32 // tNCCS Termios CC size +) + +// Termios merge of the C Terminal and Kernel termios structs. +type Termios struct { + Iflag uint32 // Iflag Handles the different Input modes + Oflag uint32 // Oflag For the different Output modes + Cflag uint32 // Cflag Control modes + Lflag uint32 // Lflag Local modes + Line byte // Line + Cc [tNCCS]byte // Cc Control characters. How to handle special Characters eg. Backspace being ^H or ^? and so on + Ispeed uint32 // Ispeed Hardly ever used speed of terminal + Ospeed uint32 // Ospeed " + Wz Winsize // Wz Terminal size information. +} + +// Winsize handle the terminal window size. +type Winsize struct { + WsRow uint16 // WsRow Terminal number of rows + WsCol uint16 // WsCol Terminal number of columns + WsXpixel uint16 // WsXpixel Terminal width in pixels + WsYpixel uint16 // WsYpixel Terminal height in pixels +} + +// PTY the PTY Master/Slave are always bundled together so makes sense to bundle here too. +// +// Slave - implements the virtual terminal functionality and the place you connect client applications +// Master - Things written to the Master are forwarded to the Slave terminal and the other way around. +// This gives reading from Master would give you nice line-by-line with no strange characters in +// Cooked() Mode and every char in Raw() mode. +// +// Since Slave is a virtual terminal it depends on the terminal settings ( in this lib the Termios ) what +// and when data is forwarded through the terminal. +// +// See 'man pty' for further info +type PTY struct { + Master *os.File // Master The Master part of the PTY + Slave *os.File // Slave The Slave part of the PTY +} + +// Raw Sets terminal t to raw mode. +// This gives that the terminal will do the absolut minimal of processing, pretty much send everything through. +// This is normally what Shells and such want since they have their own readline and movement code. +func (t *Termios) Raw() { + t.Iflag &^= IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON + // t.Iflag &^= BRKINT | ISTRIP | ICRNL | IXON // Stevens RAW + t.Oflag &^= OPOST + t.Lflag &^= ECHO | ECHONL | ICANON | ISIG | IEXTEN + t.Cflag &^= CSIZE | PARENB + t.Cflag |= CS8 + t.Cc[VMIN] = 1 + t.Cc[VTIME] = 0 +} + +// Cook Set the Terminal to Cooked mode. +// In this mode the Terminal process the information before sending it on to the application. +func (t *Termios) Cook() { + t.Iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON + t.Oflag |= OPOST + t.Lflag |= ISIG | ICANON +} + +// Sane reset Term to sane values. +// Should be pretty much what the shell command "reset" does to the terminal. +func (t *Termios) Sane() { + t.Iflag &^= IGNBRK | INLCR | IGNCR | IUTF8 | IXOFF | IUCLC | IXANY + t.Iflag |= BRKINT | ICRNL | IMAXBEL + t.Oflag |= OPOST | ONLCR + t.Oflag &^= OLCUC | OCRNL | ONOCR | ONLRET + t.Cflag |= CREAD +} + +// Set Sets terminal t attributes on file. +func (t *Termios) Set(file *os.File) error { + fd := file.Fd() + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(TCSETS), uintptr(unsafe.Pointer(t))) + if errno != 0 { + return errno + } + return nil +} + +// Attr Gets (terminal related) attributes from file. +func Attr(file *os.File) (Termios, error) { + var t Termios + fd := file.Fd() + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(TCGETS), uintptr(unsafe.Pointer(&t))) + if errno != 0 { + return t, errno + } + t.Ispeed &= CBAUD | CBAUDEX + t.Ospeed &= CBAUD | CBAUDEX + return t, nil +} + +// Isatty returns true if file is a tty. +func Isatty(file *os.File) bool { + _, err := Attr(file) + return err == nil +} + +// GetPass reads password from a TTY with no echo. +func GetPass(prompt string, f *os.File, pbuf []byte) ([]byte, error) { + t, err := Attr(f) + if err != nil { + return nil, err + } + defer t.Set(f) + noecho := t + noecho.Lflag = noecho.Lflag &^ ECHO + if err := noecho.Set(f); err != nil { + return nil, err + } + b := make([]byte, 1, 1) + i := 0 + if _, err := f.Write([]byte(prompt)); err != nil { + return nil, err + } + for ; i < len(pbuf); i++ { + if _, err := f.Read(b); err != nil { + b[0] = 0 + clearbuf(pbuf[:i+1]) + } + if b[0] == '\n' || b[0] == '\r' { + return pbuf[:i], nil + } + pbuf[i] = b[0] + b[0] = 0 + } + clearbuf(pbuf[:i+1]) + return nil, errors.New("ran out of bufferspace") +} + +// clearbuf clears out the buffer incase we couldn't read the full password. +func clearbuf(b []byte) { + for i := range b { + b[i] = 0 + } +} + +// GetChar reads a single byte. +func GetChar(f *os.File) (b byte, err error) { + bs := make([]byte, 1, 1) + if _, err = f.Read(bs); err != nil { + return 0, err + } + return bs[0], err +} + +// PTSName return the name of the pty. +func (p *PTY) PTSName() (string, error) { + n, err := p.PTSNumber() + if err != nil { + return "", err + } + return "/dev/pts/" + strconv.Itoa(int(n)), nil +} + +// PTSNumber return the pty number. +func (p *PTY) PTSNumber() (uint, error) { + var ptyno uint + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(p.Master.Fd()), uintptr(TIOCGPTN), uintptr(unsafe.Pointer(&ptyno))) + if errno != 0 { + return 0, errno + } + return ptyno, nil +} + +// Winsz Fetches the current terminal windowsize. +// example handling changing window sizes with PTYs: +// +// import "os" +// import "os/signal" +// +// var sig = make(chan os.Signal,2) // Channel to listen for UNIX SIGNALS on +// signal.Notify(sig, syscall.SIGWINCH) // That'd be the window changing +// +// for { +// <-sig +// term.Winsz(os.Stdin) // We got signaled our terminal changed size so we read in the new value +// term.Setwinsz(pty.Slave) // Copy it to our virtual Terminal +// } +func (t *Termios) Winsz(file *os.File) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(file.Fd()), uintptr(TIOCGWINSZ), uintptr(unsafe.Pointer(&t.Wz))) + if errno != 0 { + return errno + } + return nil +} + +// Setwinsz Sets the terminal window size. +func (t *Termios) Setwinsz(file *os.File) error { + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(file.Fd()), uintptr(TIOCSWINSZ), uintptr(unsafe.Pointer(&t.Wz))) + if errno != 0 { + return errno + } + return nil +} + +// OpenPTY Creates a new Master/Slave PTY pair. +func OpenPTY() (*PTY, error) { + // Opening ptmx gives you the FD of a brand new PTY + master, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) + if err != nil { + return nil, err + } + + // unlock pty slave + var unlock int // 0 => Unlock + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(master.Fd()), uintptr(TIOCSPTLCK), uintptr(unsafe.Pointer(&unlock))); errno != 0 { + master.Close() + return nil, errno + } + + // get path of pts slave + pty := &PTY{Master: master} + slaveStr, err := pty.PTSName() + if err != nil { + master.Close() + return nil, err + } + + // open pty slave + pty.Slave, err = os.OpenFile(slaveStr, os.O_RDWR|syscall.O_NOCTTY, 0) + if err != nil { + master.Close() + return nil, err + } + + return pty, nil +} + +// Close closes the PTYs that OpenPTY created. +func (p *PTY) Close() error { + slaveErr := errors.New("Slave FD nil") + if p.Slave != nil { + slaveErr = p.Slave.Close() + } + masterErr := errors.New("Master FD nil") + if p.Master != nil { + masterErr = p.Master.Close() + } + if slaveErr != nil || masterErr != nil { + var errs []string + if slaveErr != nil { + errs = append(errs, "Slave: "+slaveErr.Error()) + } + if masterErr != nil { + errs = append(errs, "Master: "+masterErr.Error()) + } + return errors.New(strings.Join(errs, " ")) + } + return nil +} + +// ReadByte implements the io.ByteReader interface to read single char from the PTY. +func (p *PTY) ReadByte() (byte, error) { + bs := make([]byte, 1, 1) + _, err := p.Master.Read(bs) + return bs[0], err +} + +// GetChar fine old getchar() for a PTY. +func (p *PTY) GetChar() (byte, error) { + return p.ReadByte() +} From d7de69028913049a5d020c29ae2e25ebd02b877b Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Tue, 25 Sep 2018 16:42:19 +0100 Subject: [PATCH 03/14] Use environment destroy Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/e2e_cluster_test.go | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index 08d4da0a73..a39bfaf890 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -28,8 +28,8 @@ func TestAWSSingleCluster(t *testing.T) { } defer func() { - t.Log("run cluster destroy command") - c = ti.Command("cluster", "destroy") + t.Log("run environment destroy command") + c = ti.Command("environment", "destroy", "--name", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -75,8 +75,8 @@ func TestAWSMultiCluster(t *testing.T) { } defer func() { - t.Log("run hub destroy command") - c = ti.Command("--current-cluster", fmt.Sprintf("%s-hub", ti.environmentName), "cluster", "destroy") + t.Log("run environment destroy command") + c = ti.Command("environment", "destroy", "--name", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -91,15 +91,6 @@ func TestAWSMultiCluster(t *testing.T) { t.Fatalf("unexpected error: %+v", err) } - defer func() { - t.Log("run cluster destroy command") - c = ti.Command("cluster", "destroy") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Errorf("unexpected error: %+v", err) - } - }() t.Log("run cluster apply command") c = ti.Command("cluster", "apply") // write error out to my stdout From e632349773883cb19d35c135f0065407a644d97f Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Fri, 5 Oct 2018 15:57:00 +0100 Subject: [PATCH 04/14] Fixing vendor packages Signed-off-by: Mattias Gees --- Gopkg.lock | 59 +++++++++++++++---------- vendor/github.com/google/btree/btree.go | 21 +++------ 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 6fdec69dbb..22f847a515 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -550,6 +550,25 @@ revision = "358ee7663966325963d4e8b2e1fbd570c5195153" version = "v1.38.1" +[[projects]] + digest = "1:da40b9e5973892e2bd37a72c36464b8252a4034522925d920983edaabda03693" + name = "github.com/go-openapi/analysis" + packages = [ + ".", + "internal", + ] + pruneopts = "NUT" + revision = "7c1bef8f6d9fa6148ce0d8a0ebf5339a084a6639" + version = "0.16.0" + +[[projects]] + digest = "1:747665117585f3d9e2f6c7635aac0410570289139f9a52b3a514f0e8b93ea517" + name = "github.com/go-openapi/errors" + packages = ["."] + pruneopts = "NUT" + revision = "b2b2befaf267d082d779bcef52d682a47c779517" + version = "0.16.0" + [[projects]] digest = "1:260f7ebefc63024c8dfe2c9f1a2935a89fa4213637a1f522f592f80c001cc441" name = "github.com/go-openapi/jsonpointer" @@ -566,6 +585,14 @@ revision = "3fb327e6747da3043567ee86abd02bb6376b6be2" version = "0.15.0" +[[projects]] + digest = "1:cc4186672d13bce6e14f7b39c6f51b2f8c5126532a020ced03841e7175886651" + name = "github.com/go-openapi/loads" + packages = ["."] + pruneopts = "NUT" + revision = "2a2b323bab96e6b1fdee110e57d959322446e9c9" + version = "0.16.0" + [[projects]] digest = "1:87216dc20d985010c4cbe3301b4f9d4c81eaea407a289bd6338d2ac66d61b5a4" name = "github.com/go-openapi/spec" @@ -574,6 +601,14 @@ revision = "bce47c9386f9ecd6b86f450478a80103c3fe1402" version = "0.15.0" +[[projects]] + digest = "1:d9091d7447932ed64fe3584eabda47ad19753760f28b58bc6262e38155a5c1d9" + name = "github.com/go-openapi/strfmt" + packages = ["."] + pruneopts = "NUT" + revision = "913ee058e387ac83a67e2d9f13acecdcd5769fc6" + version = "0.16.0" + [[projects]] digest = "1:d6dca21942524b423bf8d3e273c61874f7cd5511c2797e67685c33f4ab971f57" name = "github.com/go-openapi/swag" @@ -2058,7 +2093,6 @@ version = "v0.9.1" [[projects]] -<<<<<<< HEAD branch = "v2" digest = "1:2642fd0b6900c77247d61d80cf2eb59a374ef4ffc2d25a1b95b87dc355b2894e" name = "gopkg.in/mgo.v2" @@ -2070,8 +2104,6 @@ revision = "9856a29383ce1c59f308dd1cf0363a79b5bef6b5" [[projects]] -======= ->>>>>>> Add vendoring digest = "1:80c34337e8a734e190f2d1b716cae774cca74db98315166f92074434e9af0227" name = "gopkg.in/natefinch/lumberjack.v2" packages = ["."] @@ -2524,11 +2556,7 @@ [[projects]] branch = "master" -<<<<<<< HEAD digest = "1:c48a795cd7048bb1888273bc604b6e69b22f9b8089c3df65f77cc527757b515c" -======= - digest = "1:a8a0fdd623b2a37c433d137bddfab6b155e988efbc7cd68c18f9923dce6c8742" ->>>>>>> Add vendoring name = "k8s.io/kube-openapi" packages = [ "cmd/openapi-gen", @@ -2540,14 +2568,9 @@ "pkg/handler", "pkg/util", "pkg/util/proto", -<<<<<<< HEAD "pkg/util/sets", ] pruneopts = "NT" -======= - ] - pruneopts = "NUT" ->>>>>>> Add vendoring revision = "d8ea2fe547a448256204cfc68dfee7b26c720acb" [solve-meta] @@ -2633,19 +2656,13 @@ "github.com/cenkalti/backoff", "github.com/davecgh/go-spew/spew", "github.com/docker/docker/pkg/archive", -<<<<<<< HEAD "github.com/go-openapi/spec", -======= ->>>>>>> Add vendoring "github.com/go-ozzo/ozzo-validation", "github.com/go-ozzo/ozzo-validation/is", "github.com/golang/glog", "github.com/golang/mock/gomock", "github.com/golang/mock/mockgen", -<<<<<<< HEAD -======= "github.com/google/goexpect", ->>>>>>> Add vendoring "github.com/google/uuid", "github.com/hashicorp/errwrap", "github.com/hashicorp/go-cleanhttp", @@ -2675,10 +2692,7 @@ "github.com/jetstack/vault-unsealer/pkg/vault", "github.com/jteeuwen/go-bindata/go-bindata", "github.com/kardianos/osext", -<<<<<<< HEAD "github.com/kubernetes-incubator/reference-docs/gen-apidocs", -======= ->>>>>>> Add vendoring "github.com/mitchellh/cli", "github.com/mitchellh/go-homedir", "github.com/sirupsen/logrus", @@ -2742,11 +2756,8 @@ "k8s.io/code-generator/cmd/defaulter-gen", "k8s.io/code-generator/cmd/informer-gen", "k8s.io/code-generator/cmd/lister-gen", -<<<<<<< HEAD "k8s.io/kube-openapi/cmd/openapi-gen", "k8s.io/kube-openapi/pkg/common", -======= ->>>>>>> Add vendoring ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/google/btree/btree.go b/vendor/github.com/google/btree/btree.go index 6ff062f9bb..7e4551d73b 100644 --- a/vendor/github.com/google/btree/btree.go +++ b/vendor/github.com/google/btree/btree.go @@ -500,14 +500,13 @@ const ( // thus creating a "greaterOrEqual" or "lessThanEqual" rather than just a // "greaterThan" or "lessThan" queries. func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit bool, iter ItemIterator) (bool, bool) { - var ok, found bool - var index int + var ok bool switch dir { case ascend: - if start != nil { - index, _ = n.items.find(start) - } - for i := index; i < len(n.items); i++ { + for i := 0; i < len(n.items); i++ { + if start != nil && n.items[i].Less(start) { + continue + } if len(n.children) > 0 { if hit, ok = n.children[i].iterate(dir, start, stop, includeStart, hit, iter); !ok { return hit, false @@ -531,15 +530,7 @@ func (n *node) iterate(dir direction, start, stop Item, includeStart bool, hit b } } case descend: - if start != nil { - index, found = n.items.find(start) - if !found { - index = index - 1 - } - } else { - index = len(n.items) - 1 - } - for i := index; i >= 0; i-- { + for i := len(n.items) - 1; i >= 0; i-- { if start != nil && !n.items[i].Less(start) { if !includeStart || hit || start.Less(n.items[i]) { continue From 8775ba245edce6ca2124da3746f5d2432c36c104 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Tue, 18 Dec 2018 15:17:40 +0100 Subject: [PATCH 05/14] Add e2e test command to makefile Signed-off-by: Mattias Gees --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index a89944be4e..129f4e1d12 100644 --- a/Makefile +++ b/Makefile @@ -252,3 +252,6 @@ docker_%: local_build: go_generate go build -o tarmak_local_build ./cmd/tarmak + +e2e-test: build + go test -v -timeout 1h github.com/jetstack/tarmak/cmd/tarmak/e2e -e2e \ No newline at end of file From 7acb64a3d75e1b27b3638fc3d4401bf58435f585 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Tue, 18 Dec 2018 16:17:26 +0100 Subject: [PATCH 06/14] Fix environment destroy Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/e2e_cluster_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index a39bfaf890..c62486a550 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -29,7 +29,7 @@ func TestAWSSingleCluster(t *testing.T) { defer func() { t.Log("run environment destroy command") - c = ti.Command("environment", "destroy", "--name", ti.environmentName, "--auto-approve") + c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -76,7 +76,7 @@ func TestAWSMultiCluster(t *testing.T) { defer func() { t.Log("run environment destroy command") - c = ti.Command("environment", "destroy", "--name", ti.environmentName, "--auto-approve") + c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { From 92b6427b1e11300b82406714be666e0be5613d70 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Wed, 19 Dec 2018 17:12:23 +0100 Subject: [PATCH 07/14] Add tarmak upgrade test Signed-off-by: Mattias Gees --- Makefile | 9 +++- cmd/tarmak/e2e/e2e_cluster_test.go | 70 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 129f4e1d12..e6c43b2249 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ PACKAGE_NAME ?= github.com/jetstack/tarmak CONTAINER_DIR := /go/src/$(PACKAGE_NAME) GO_VERSION := 1.10.4 +TARMAK_VERSION ?= 0.5.2 BINDIR ?= $(CURDIR)/bin PATH := $(BINDIR):$(PATH) @@ -253,5 +254,9 @@ docker_%: local_build: go_generate go build -o tarmak_local_build ./cmd/tarmak -e2e-test: build - go test -v -timeout 1h github.com/jetstack/tarmak/cmd/tarmak/e2e -e2e \ No newline at end of file +e2e-test: download build + go test -v -timeout 1h github.com/jetstack/tarmak/cmd/tarmak/e2e -e2e + +download: + wget https://github.com/jetstack/tarmak/releases/download/$(TARMAK_VERSION)/tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 + chmod +x tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 \ No newline at end of file diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index c62486a550..e60e62f40c 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -4,6 +4,7 @@ package e2e_test import ( "fmt" "os" + "runtime" "testing" ) @@ -109,3 +110,72 @@ func TestAWSMultiCluster(t *testing.T) { } } + +func TestAWSUpgradeTarmak(t *testing.T) { + t.Parallel() + skipE2ETests(t) + + ti := NewTarmakInstance(t) + ti.singleCluster = true + ti.singleZone = true + + + t.Log("initialise config for single cluster") + if err := ti.Init(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + + ti.binPath = fmt.Sprintf("../../../tarmak_0.5.2_%s_%s", runtime.GOOS, runtime.GOARCH) + + t.Log("build tarmak image") + c := ti.Command("cluster", "image", "build") + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + + defer func() { + t.Log("run environment destroy command") + c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + }() + t.Log("run cluster apply command") + c = ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("get component status") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + ti.binPath = fmt.Sprintf("../../../tarmak_%s_%s", runtime.GOOS, runtime.GOARCH) + + t.Log("run cluster apply command for Tarmak upgrade") + c = ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("get component status for Tarmak upgrade") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } +} \ No newline at end of file From 67cc9b209b891ee201bc25fda5f5610896c59c3b Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Wed, 19 Dec 2018 17:17:48 +0100 Subject: [PATCH 08/14] Fix go fmt Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/e2e_cluster_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index e60e62f40c..a77675cd47 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -119,7 +119,6 @@ func TestAWSUpgradeTarmak(t *testing.T) { ti.singleCluster = true ti.singleZone = true - t.Log("initialise config for single cluster") if err := ti.Init(); err != nil { t.Errorf("unexpected error: %+v", err) @@ -178,4 +177,4 @@ func TestAWSUpgradeTarmak(t *testing.T) { if err := c.Run(); err != nil { t.Fatalf("unexpected error: %+v", err) } -} \ No newline at end of file +} From a9a50336ca7af25e8f08cfe92015e5320c9855d6 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Thu, 20 Dec 2018 09:03:05 +0100 Subject: [PATCH 09/14] Fix makefile Signed-off-by: Mattias Gees --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e6c43b2249..1f31b01a2c 100644 --- a/Makefile +++ b/Makefile @@ -258,5 +258,5 @@ e2e-test: download build go test -v -timeout 1h github.com/jetstack/tarmak/cmd/tarmak/e2e -e2e download: - wget https://github.com/jetstack/tarmak/releases/download/$(TARMAK_VERSION)/tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 + curl -sL -o tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 https://github.com/jetstack/tarmak/releases/download/$(TARMAK_VERSION)/tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 chmod +x tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 \ No newline at end of file From efc3eebbb50423384c7f12efe736c661c2deefcf Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Thu, 20 Dec 2018 13:31:59 +0100 Subject: [PATCH 10/14] Add kubernetes upgrade test Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/e2e_cluster_test.go | 66 ++++++++++++++++++++++++++ cmd/tarmak/e2e/tarmak_instance_test.go | 19 +++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index a77675cd47..4c0fa3e34d 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -178,3 +178,69 @@ func TestAWSUpgradeTarmak(t *testing.T) { t.Fatalf("unexpected error: %+v", err) } } + +func TestAWSUpgradeKubernetes(t *testing.T) { + t.Parallel() + skipE2ETests(t) + + ti := NewTarmakInstance(t) + ti.singleCluster = true + ti.singleZone = true + + t.Log("initialise config for single cluster") + if err := ti.Init(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + + t.Log("build tarmak image") + c := ti.Command("cluster", "image", "build") + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + + defer func() { + t.Log("run environment destroy command") + c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Errorf("unexpected error: %+v", err) + } + }() + t.Log("run cluster apply command") + c = ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("get component status") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + ti.UpdateKubernetesVersion() + + t.Log("run cluster apply command for Kubernetes upgrade") + c = ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } + + t.Log("get component status for Kubernetes upgrade") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + t.Fatalf("unexpected error: %+v", err) + } +} diff --git a/cmd/tarmak/e2e/tarmak_instance_test.go b/cmd/tarmak/e2e/tarmak_instance_test.go index 2bf175f4b1..5801540dd3 100644 --- a/cmd/tarmak/e2e/tarmak_instance_test.go +++ b/cmd/tarmak/e2e/tarmak_instance_test.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "runtime" + "strings" "syscall" "testing" "time" @@ -206,7 +207,7 @@ func (ti *TarmakInstance) initProvider(e *expect.GExpect) error { &expect.BExp{R: tarmakInitPrompt}, // &expect.BSnd{S: "develop.tarmak.org\n"}, // public route 53 &expect.BExp{R: tarmakInitYesNo}, // - &expect.BSnd{S: "Y\n"}, // save provider + &expect.BSnd{S: "Y\n"}, // save provider }, 30*time.Second) if err != nil { return fmt.Errorf("unexpected expect flow for init provider res=%+v error: %+v", res, err) @@ -291,3 +292,19 @@ func (ti *TarmakInstance) initCluster(e *expect.GExpect) error { } return nil } + +func (ti *TarmakInstance) UpdateKubernetesVersion() error { + + config, err := ioutil.ReadFile(fmt.Sprintf("%v/tarmak.yaml", ti.configPath)) + if err != nil { + fmt.Errorf("Error reading config file: %+v", err) + } + output := strings.Replace(string(config), "version: 1.11.5", "version: 1.12.4", 1) + + d1 := []byte(output) + err = ioutil.WriteFile(fmt.Sprintf("%v/tarmak.yaml", ti.configPath), d1, 0644) + if err != nil { + fmt.Errorf("Error writing config file: %+v", err) + } + return nil +} \ No newline at end of file From 7b2b5b9e7ab71c120ef432295dfd30e3a316a426 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Thu, 20 Dec 2018 14:43:49 +0100 Subject: [PATCH 11/14] Refactor code a bit Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/e2e_cluster_test.go | 100 +++---------------------- cmd/tarmak/e2e/tarmak_instance_test.go | 20 +++++ 2 files changed, 32 insertions(+), 88 deletions(-) diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index 4c0fa3e34d..64b123123d 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -37,21 +37,9 @@ func TestAWSSingleCluster(t *testing.T) { t.Errorf("unexpected error: %+v", err) } }() - t.Log("run cluster apply command") - c = ti.Command("cluster", "apply") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - t.Log("get component status") - c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") - // write error out to my stdout - c.Stderr = os.Stderr - c.Stdout = os.Stdout - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.RunAndVerify(); err != nil { + t.Fatal(err) } } @@ -92,23 +80,9 @@ func TestAWSMultiCluster(t *testing.T) { t.Fatalf("unexpected error: %+v", err) } - t.Log("run cluster apply command") - c = ti.Command("cluster", "apply") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.RunAndVerify(); err != nil { + t.Fatal(err) } - - t.Log("get component status") - c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") - // write error out to my stdout - c.Stderr = os.Stderr - c.Stdout = os.Stdout - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - } func TestAWSUpgradeTarmak(t *testing.T) { @@ -142,40 +116,15 @@ func TestAWSUpgradeTarmak(t *testing.T) { t.Errorf("unexpected error: %+v", err) } }() - t.Log("run cluster apply command") - c = ti.Command("cluster", "apply") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - t.Log("get component status") - c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") - // write error out to my stdout - c.Stderr = os.Stderr - c.Stdout = os.Stdout - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.RunAndVerify(); err != nil { + t.Fatal(err) } ti.binPath = fmt.Sprintf("../../../tarmak_%s_%s", runtime.GOOS, runtime.GOARCH) - t.Log("run cluster apply command for Tarmak upgrade") - c = ti.Command("cluster", "apply") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - - t.Log("get component status for Tarmak upgrade") - c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") - // write error out to my stdout - c.Stderr = os.Stderr - c.Stdout = os.Stdout - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.RunAndVerify(); err != nil { + t.Fatal(err) } } @@ -208,39 +157,14 @@ func TestAWSUpgradeKubernetes(t *testing.T) { t.Errorf("unexpected error: %+v", err) } }() - t.Log("run cluster apply command") - c = ti.Command("cluster", "apply") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - t.Log("get component status") - c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") - // write error out to my stdout - c.Stderr = os.Stderr - c.Stdout = os.Stdout - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.RunAndVerify(); err != nil { + t.Fatal(err) } ti.UpdateKubernetesVersion() - t.Log("run cluster apply command for Kubernetes upgrade") - c = ti.Command("cluster", "apply") - // write error out to my stdout - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - - t.Log("get component status for Kubernetes upgrade") - c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") - // write error out to my stdout - c.Stderr = os.Stderr - c.Stdout = os.Stdout - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.RunAndVerify(); err != nil { + t.Fatal(err) } } diff --git a/cmd/tarmak/e2e/tarmak_instance_test.go b/cmd/tarmak/e2e/tarmak_instance_test.go index 5801540dd3..afeb22bc29 100644 --- a/cmd/tarmak/e2e/tarmak_instance_test.go +++ b/cmd/tarmak/e2e/tarmak_instance_test.go @@ -307,4 +307,24 @@ func (ti *TarmakInstance) UpdateKubernetesVersion() error { fmt.Errorf("Error writing config file: %+v", err) } return nil +} + +func (ti *TarmakInstance) RunAndVerify() error { + ti.t.Log("run cluster apply command") + c := ti.Command("cluster", "apply") + // write error out to my stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + return fmt.Errorf("unexpected error: %+v", err) + } + + ti.t.Log("get component status") + c = ti.Command("cluster", "kubectl", "get", "cs", "-o", "yaml") + // write error out to my stdout + c.Stderr = os.Stderr + c.Stdout = os.Stdout + if err := c.Run(); err != nil { + return fmt.Errorf("unexpected error: %+v", err) + } + return nil } \ No newline at end of file From 2be5c8074e19369e4e1ae2492e3c3b5a2a6f6aef Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Thu, 20 Dec 2018 14:53:15 +0100 Subject: [PATCH 12/14] Fix go fmt Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/tarmak_instance_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/tarmak/e2e/tarmak_instance_test.go b/cmd/tarmak/e2e/tarmak_instance_test.go index afeb22bc29..f48f0eafe1 100644 --- a/cmd/tarmak/e2e/tarmak_instance_test.go +++ b/cmd/tarmak/e2e/tarmak_instance_test.go @@ -207,7 +207,7 @@ func (ti *TarmakInstance) initProvider(e *expect.GExpect) error { &expect.BExp{R: tarmakInitPrompt}, // &expect.BSnd{S: "develop.tarmak.org\n"}, // public route 53 &expect.BExp{R: tarmakInitYesNo}, // - &expect.BSnd{S: "Y\n"}, // save provider + &expect.BSnd{S: "Y\n"}, // save provider }, 30*time.Second) if err != nil { return fmt.Errorf("unexpected expect flow for init provider res=%+v error: %+v", res, err) @@ -327,4 +327,4 @@ func (ti *TarmakInstance) RunAndVerify() error { return fmt.Errorf("unexpected error: %+v", err) } return nil -} \ No newline at end of file +} From 6bf365f4d3a645001a8b7eaeca37149aa54b0cf6 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Thu, 20 Dec 2018 15:09:48 +0100 Subject: [PATCH 13/14] Fix return statement Signed-off-by: Mattias Gees --- cmd/tarmak/e2e/tarmak_instance_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/tarmak/e2e/tarmak_instance_test.go b/cmd/tarmak/e2e/tarmak_instance_test.go index f48f0eafe1..00d74e9989 100644 --- a/cmd/tarmak/e2e/tarmak_instance_test.go +++ b/cmd/tarmak/e2e/tarmak_instance_test.go @@ -297,14 +297,14 @@ func (ti *TarmakInstance) UpdateKubernetesVersion() error { config, err := ioutil.ReadFile(fmt.Sprintf("%v/tarmak.yaml", ti.configPath)) if err != nil { - fmt.Errorf("Error reading config file: %+v", err) + return fmt.Errorf("Error reading config file: %+v", err) } output := strings.Replace(string(config), "version: 1.11.5", "version: 1.12.4", 1) d1 := []byte(output) err = ioutil.WriteFile(fmt.Sprintf("%v/tarmak.yaml", ti.configPath), d1, 0644) if err != nil { - fmt.Errorf("Error writing config file: %+v", err) + return fmt.Errorf("Error writing config file: %+v", err) } return nil } From 055ef883cb07877766208ab9e1fbe95ef34c01c3 Mon Sep 17 00:00:00 2001 From: Mattias Gees Date: Thu, 20 Dec 2018 16:20:39 +0100 Subject: [PATCH 14/14] Refactor duplicate code Signed-off-by: Mattias Gees --- Makefile | 2 +- cmd/tarmak/e2e/e2e_cluster_test.go | 58 ++++++-------------------- cmd/tarmak/e2e/tarmak_instance_test.go | 15 +++++++ 3 files changed, 29 insertions(+), 46 deletions(-) diff --git a/Makefile b/Makefile index 1f31b01a2c..fb821ccf11 100644 --- a/Makefile +++ b/Makefile @@ -259,4 +259,4 @@ e2e-test: download build download: curl -sL -o tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 https://github.com/jetstack/tarmak/releases/download/$(TARMAK_VERSION)/tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 - chmod +x tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 \ No newline at end of file + chmod +x tarmak_$(TARMAK_VERSION)_$(UNAME_S)_amd64 diff --git a/cmd/tarmak/e2e/e2e_cluster_test.go b/cmd/tarmak/e2e/e2e_cluster_test.go index 64b123123d..b24779661a 100644 --- a/cmd/tarmak/e2e/e2e_cluster_test.go +++ b/cmd/tarmak/e2e/e2e_cluster_test.go @@ -16,21 +16,13 @@ func TestAWSSingleCluster(t *testing.T) { ti.singleCluster = true ti.singleZone = true - t.Log("initialise config for single cluster") - if err := ti.Init(); err != nil { - t.Errorf("unexpected error: %+v", err) - } - - t.Log("build tarmak image") - c := ti.Command("cluster", "image", "build") - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Errorf("unexpected error: %+v", err) + if err := ti.GenerateAndBuild(); err != nil { + t.Fatal(err) } defer func() { t.Log("run environment destroy command") - c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") + c := ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -51,21 +43,13 @@ func TestAWSMultiCluster(t *testing.T) { ti.singleCluster = false ti.singleZone = false - t.Log("initialise config for single cluster") - if err := ti.Init(); err != nil { - t.Fatalf("unexpected error: %+v", err) - } - - t.Log("build tarmak image") - c := ti.Command("cluster", "image", "build") - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Fatalf("unexpected error: %+v", err) + if err := ti.GenerateAndBuild(); err != nil { + t.Fatal(err) } defer func() { t.Log("run environment destroy command") - c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") + c := ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -73,7 +57,7 @@ func TestAWSMultiCluster(t *testing.T) { } }() t.Log("run hub apply command") - c = ti.Command("--current-cluster", fmt.Sprintf("%s-hub", ti.environmentName), "cluster", "apply") + c := ti.Command("--current-cluster", fmt.Sprintf("%s-hub", ti.environmentName), "cluster", "apply") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -93,23 +77,15 @@ func TestAWSUpgradeTarmak(t *testing.T) { ti.singleCluster = true ti.singleZone = true - t.Log("initialise config for single cluster") - if err := ti.Init(); err != nil { - t.Errorf("unexpected error: %+v", err) - } - ti.binPath = fmt.Sprintf("../../../tarmak_0.5.2_%s_%s", runtime.GOOS, runtime.GOARCH) - t.Log("build tarmak image") - c := ti.Command("cluster", "image", "build") - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Errorf("unexpected error: %+v", err) + if err := ti.GenerateAndBuild(); err != nil { + t.Fatal(err) } defer func() { t.Log("run environment destroy command") - c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") + c := ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { @@ -136,21 +112,13 @@ func TestAWSUpgradeKubernetes(t *testing.T) { ti.singleCluster = true ti.singleZone = true - t.Log("initialise config for single cluster") - if err := ti.Init(); err != nil { - t.Errorf("unexpected error: %+v", err) - } - - t.Log("build tarmak image") - c := ti.Command("cluster", "image", "build") - c.Stderr = os.Stderr - if err := c.Run(); err != nil { - t.Errorf("unexpected error: %+v", err) + if err := ti.GenerateAndBuild(); err != nil { + t.Fatal(err) } defer func() { t.Log("run environment destroy command") - c = ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") + c := ti.Command("environment", "destroy", ti.environmentName, "--auto-approve") // write error out to my stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { diff --git a/cmd/tarmak/e2e/tarmak_instance_test.go b/cmd/tarmak/e2e/tarmak_instance_test.go index 00d74e9989..aa5056474b 100644 --- a/cmd/tarmak/e2e/tarmak_instance_test.go +++ b/cmd/tarmak/e2e/tarmak_instance_test.go @@ -328,3 +328,18 @@ func (ti *TarmakInstance) RunAndVerify() error { } return nil } + +func (ti *TarmakInstance) GenerateAndBuild() error { + ti.t.Log("initialise config for cluster") + if err := ti.Init(); err != nil { + return fmt.Errorf("unexpected error: %+v", err) + } + + ti.t.Log("build tarmak image") + c := ti.Command("cluster", "image", "build") + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + return fmt.Errorf("unexpected error: %+v", err) + } + return nil +}