mirror of https://github.com/knative/client.git
320 lines
9.3 KiB
Go
320 lines
9.3 KiB
Go
// Copyright 2020 The Knative Authors
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os/exec"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type kn struct {
|
|
namespace string
|
|
}
|
|
|
|
const (
|
|
seperatorHeavy = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
seperatorLight = "╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍"
|
|
)
|
|
|
|
// Run the 'kn' CLI with args and opts
|
|
func (k kn) Run(args ...string) KnRunResult {
|
|
return RunKn(k.namespace, args)
|
|
}
|
|
|
|
// Helper methods for calling out to the test cluster
|
|
type kubectl struct {
|
|
namespace string
|
|
}
|
|
|
|
// Run the 'kubectl' CLI with args and opts
|
|
func (k kubectl) Run(args ...string) (string, error) {
|
|
return RunKubectl(k.namespace, args...)
|
|
}
|
|
|
|
// Collector for results
|
|
type KnRunResultCollector struct {
|
|
results []KnRunResult
|
|
extraDumps []string
|
|
t *testing.T
|
|
}
|
|
|
|
func NewKnRunResultCollector(t *testing.T) *KnRunResultCollector {
|
|
return &KnRunResultCollector{
|
|
results: []KnRunResult{},
|
|
t: t,
|
|
extraDumps: []string{},
|
|
}
|
|
}
|
|
|
|
func (c *KnRunResultCollector) AssertNoError(result KnRunResult) {
|
|
c.results = append(c.results, result)
|
|
if result.Error != nil {
|
|
c.t.Logf("ERROR: %v", result.Stderr)
|
|
c.t.FailNow()
|
|
}
|
|
}
|
|
|
|
func (c *KnRunResultCollector) AssertError(result KnRunResult) {
|
|
c.results = append(c.results, result)
|
|
if result.Error == nil {
|
|
c.t.Log("ERROR: Error expected but no error happened")
|
|
c.t.FailNow()
|
|
}
|
|
}
|
|
|
|
// AddDump adds extra dump information to the collector which is printed
|
|
// out if an error occurs
|
|
func (c *KnRunResultCollector) AddDump(kind string, name string, namespace string) {
|
|
dumpInfo := extractDumpInfoWithName(kind, name, namespace)
|
|
if dumpInfo != "" {
|
|
c.extraDumps = append(c.extraDumps, dumpInfo)
|
|
}
|
|
}
|
|
|
|
func (c *KnRunResultCollector) DumpIfFailed() {
|
|
if c.t.Failed() {
|
|
c.t.Log(c.errorDetails())
|
|
}
|
|
}
|
|
|
|
func (c *KnRunResultCollector) errorDetails() string {
|
|
var out = bytes.Buffer{}
|
|
fmt.Fprintln(&out, "=== FAIL: =======================[[ERROR]]========================")
|
|
c.printCommands(&out)
|
|
var dumpInfos []string
|
|
if len(c.results) > 0 {
|
|
dumpInfo := c.results[len(c.results)-1].DumpInfo
|
|
if dumpInfo != "" {
|
|
dumpInfos = append(dumpInfos, dumpInfo)
|
|
}
|
|
}
|
|
dumpInfos = append(dumpInfos, c.extraDumps...)
|
|
for _, d := range dumpInfos {
|
|
fmt.Fprintln(&out, "--------------------------[[DUMP]]-------------------------------")
|
|
fmt.Fprintf(&out, d)
|
|
}
|
|
|
|
fmt.Fprintln(&out, "=================================================================")
|
|
return out.String()
|
|
}
|
|
|
|
func (c *KnRunResultCollector) printCommands(out io.Writer) {
|
|
for i, result := range c.results {
|
|
c.printCommand(out, result)
|
|
if i < len(c.results)-1 {
|
|
fmt.Fprintf(out, "┣━%s\n", seperatorHeavy)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *KnRunResultCollector) printCommand(out io.Writer, result KnRunResult) {
|
|
fmt.Fprintf(out, "🦆 %s\n", result.CmdLine)
|
|
for _, l := range strings.Split(result.Stdout, "\n") {
|
|
fmt.Fprintf(out, "┃ %s\n", l)
|
|
}
|
|
if result.Stderr != "" {
|
|
errorPrefix := "🔥"
|
|
if result.ErrorExpected {
|
|
errorPrefix = "︙"
|
|
}
|
|
for _, l := range strings.Split(result.Stderr, "\n") {
|
|
fmt.Fprintf(out, "%s %s\n", errorPrefix, l)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================================
|
|
// Functions:
|
|
|
|
// Result of a "kn" call
|
|
type KnRunResult struct {
|
|
// Command line called
|
|
CmdLine string
|
|
// Standard output of command
|
|
Stdout string
|
|
// Standard error of command
|
|
Stderr string
|
|
// And extra dump informations in case of an unexpected error
|
|
DumpInfo string
|
|
// Error occurred during execution
|
|
Error error
|
|
// Was an error expected ?
|
|
ErrorExpected bool
|
|
}
|
|
|
|
// RunKubectl runs "kk" in a given namespace
|
|
func RunKn(namespace string, args []string) KnRunResult {
|
|
if namespace != "" {
|
|
args = append(args, "--namespace", namespace)
|
|
}
|
|
stdout, stderr, err := runCli("kn", args)
|
|
result := KnRunResult{
|
|
CmdLine: cmdCLIDesc("kn", args),
|
|
Stdout: stdout,
|
|
Stderr: stderr,
|
|
Error: err,
|
|
}
|
|
if err != nil {
|
|
command := args[0]
|
|
if command == "source" && len(args) > 1 {
|
|
command = "source " + args[1]
|
|
args = args[1:]
|
|
}
|
|
result.DumpInfo = extractDumpInfo(command, args, namespace)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// RunKubectl runs "kubectl" in a given namespace
|
|
func RunKubectl(namespace string, args ...string) (string, error) {
|
|
if namespace != "" {
|
|
args = append(args, "--namespace", namespace)
|
|
}
|
|
stdout, stderr, err := runCli("kubectl", args)
|
|
if err != nil {
|
|
return stdout, errors.Wrap(err, fmt.Sprintf("stderr: %s", stderr))
|
|
}
|
|
return stdout, nil
|
|
}
|
|
|
|
func runCli(cli string, args []string) (string, string, error) {
|
|
var stderr bytes.Buffer
|
|
var stdout bytes.Buffer
|
|
|
|
cmd := exec.Command(cli, args...)
|
|
cmd.Stderr = &stderr
|
|
cmd.Stdout = &stdout
|
|
cmd.Stdin = nil
|
|
|
|
err := cmd.Run()
|
|
return stdout.String(), stderr.String(), err
|
|
}
|
|
|
|
type dumpFunc func(namespace string, args []string) string
|
|
|
|
// Dump handler for specific commands ("service", "revision") which should add extra infos
|
|
// Relies on that argv[1] is the command and argv[3] is the name of the object
|
|
var dumpHandlers = map[string]dumpFunc{
|
|
"service": dumpService,
|
|
"revision": dumpRevision,
|
|
"route": dumpRoute,
|
|
"trigger": dumpTrigger,
|
|
"source apiserver": dumpApiServerSource,
|
|
}
|
|
|
|
func extractDumpInfoWithName(command string, name string, namespace string) string {
|
|
return extractDumpInfo(command, []string{command, "", name}, namespace)
|
|
}
|
|
|
|
func extractDumpInfo(command string, args []string, namespace string) string {
|
|
dumpHandler := dumpHandlers[command]
|
|
if dumpHandler != nil {
|
|
return dumpHandler(namespace, args)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func dumpService(namespace string, args []string) string {
|
|
// For list like operation we don't have a name
|
|
if len(args) < 3 || args[2] == "" {
|
|
return ""
|
|
}
|
|
name := args[2]
|
|
var buffer bytes.Buffer
|
|
|
|
// Service info
|
|
appendResourceInfo(&buffer, "ksvc", name, namespace)
|
|
fmt.Fprintf(&buffer, "%s\n", seperatorHeavy)
|
|
// Service's configuration
|
|
appendResourceInfo(&buffer, "configuration", name, namespace)
|
|
fmt.Fprintf(&buffer, "%s\n", seperatorHeavy)
|
|
// Get all revisions for this service
|
|
appendResourceInfoWithNameSelector(&buffer, "revision", name, namespace, "serving.knative.dev/service")
|
|
// Get all routes for this service
|
|
appendResourceInfoWithNameSelector(&buffer, "route", name, namespace, "serving.knative.dev/service")
|
|
return buffer.String()
|
|
}
|
|
|
|
func dumpRevision(namespace string, args []string) string {
|
|
return simpleDump("revision", args, namespace)
|
|
}
|
|
|
|
func dumpRoute(namespace string, args []string) string {
|
|
return simpleDump("route", args, namespace)
|
|
}
|
|
|
|
func dumpApiServerSource(namespace string, args []string) string {
|
|
return simpleDump("apiserversource", args, namespace)
|
|
}
|
|
|
|
func dumpTrigger(namespace string, args []string) string {
|
|
return simpleDump("trigger", args, namespace)
|
|
}
|
|
|
|
func simpleDump(kind string, args []string, namespace string) string {
|
|
if len(args) < 3 || args[2] == "" {
|
|
return ""
|
|
}
|
|
|
|
var buffer bytes.Buffer
|
|
appendResourceInfo(&buffer, kind, args[2], namespace)
|
|
return buffer.String()
|
|
}
|
|
|
|
func appendResourceInfo(buffer *bytes.Buffer, kind string, name string, namespace string) {
|
|
appendResourceInfoWithNameSelector(buffer, kind, name, namespace, "")
|
|
}
|
|
|
|
func appendResourceInfoWithNameSelector(buffer *bytes.Buffer, kind string, name string, namespace string, selector string) {
|
|
var extra string
|
|
argsDescribe := []string{"describe", kind}
|
|
argsGet := []string{"get", "-oyaml", kind}
|
|
|
|
if selector != "" {
|
|
labelArg := fmt.Sprintf("%s=%s", selector, name)
|
|
argsDescribe = append(argsDescribe, "--selector", labelArg)
|
|
argsGet = append(argsGet, "--selector", labelArg)
|
|
extra = fmt.Sprintf(" --selector %s", labelArg)
|
|
} else {
|
|
argsDescribe = append(argsDescribe, name)
|
|
argsGet = append(argsGet, name)
|
|
extra = ""
|
|
}
|
|
|
|
out, err := RunKubectl(namespace, argsDescribe...)
|
|
appendCLIOutput(buffer, fmt.Sprintf("kubectl describe %s %s --namespace %s%s", kind, name, namespace, extra), out, err)
|
|
fmt.Fprintf(buffer, "%s\n", seperatorLight)
|
|
out, err = RunKubectl(namespace, argsGet...)
|
|
appendCLIOutput(buffer, fmt.Sprintf("kubectl get %s %s --namespace %s -oyaml%s", kind, name, namespace, extra), out, err)
|
|
}
|
|
|
|
func appendCLIOutput(buffer *bytes.Buffer, desc string, out string, err error) {
|
|
buffer.WriteString(fmt.Sprintf("==== %s\n", desc))
|
|
if err != nil {
|
|
buffer.WriteString(fmt.Sprintf("%s: %v\n", "!!!! ERROR", err))
|
|
}
|
|
buffer.WriteString(out)
|
|
}
|
|
|
|
func cmdCLIDesc(cli string, args []string) string {
|
|
return fmt.Sprintf("%s %s", cli, strings.Join(args, " "))
|
|
}
|