Skip to content

Commit

Permalink
Update check and inject output
Browse files Browse the repository at this point in the history
The outputs of the `check` and inject commands did not vary much between
successful and failed executions, and were a bit verbose and visually
challenging to parse.

Reorganize output of check and inject commands, to provide more info
when errors occur, and less output when successful. Specific changes:

`linkerd check`
- visually group checks by category
- introduce `hintURL`'s, to provide doc links when checks fail
- add spinners when retrying, remove addtional retry lines
- colored emojis to indicate success/warning/failure

`linkerd inject`
- modify default output to mirror `kubectl apply`
- only output non-successful inject reports
- support `--verbose` flag to output all inject reports

Fixes #1471, #1653, #1656, #1739

Signed-off-by: Andrew Seigner <siggy@buoyant.io>
  • Loading branch information
siggy committed Jan 15, 2019
1 parent dacd881 commit 4f042de
Show file tree
Hide file tree
Showing 51 changed files with 649 additions and 430 deletions.
33 changes: 33 additions & 0 deletions Gopkg.lock
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@
pruneopts = ""
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"

[[projects]]
digest = "1:87a8cd3894b69f36d93f71074b921d6446483cc9465b36415991ecc02851ec08"
name = "github.com/briandowns/spinner"
packages = ["."]
pruneopts = ""
revision = "dd69c579ff204842e8962816987f838e2c2c18d8"
version = "1.4"

[[projects]]
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
name = "github.com/davecgh/go-spew"
Expand All @@ -71,6 +79,14 @@
pruneopts = ""
revision = "449fdfce4d962303d702fec724ef0ad181c92528"

[[projects]]
digest = "1:e988ed0ca0d81f4d28772760c02ee95084961311291bdfefc1b04617c178b722"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = ""
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"

[[projects]]
digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22"
name = "github.com/ghodss/yaml"
Expand Down Expand Up @@ -290,6 +306,22 @@
pruneopts = ""
revision = "60711f1a8329503b04e1c88535f419d0bb440bff"

[[projects]]
digest = "1:9ea83adf8e96d6304f394d40436f2eb44c1dc3250d223b74088cc253a6cd0a1c"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = ""
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"

[[projects]]
digest = "1:3140e04675a6a91d2a20ea9d10bdadf6072085502e6def6768361260aee4b967"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = ""
revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c"
version = "v0.0.4"

[[projects]]
digest = "1:81e673df85e765593a863f67cba4544cf40e8919590f04d67664940786c2b61a"
name = "github.com/mattn/go-runewidth"
Expand Down Expand Up @@ -941,6 +973,7 @@
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/briandowns/spinner",
"github.com/ghodss/yaml",
"github.com/go-openapi/spec",
"github.com/golang/protobuf/jsonpb",
Expand Down
4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ required = [
name = "k8s.io/apimachinery"
version = "kubernetes-1.11.1"

[[constraint]]
name = "github.com/briandowns/spinner"
version = "1.4"

[[constraint]]
name = "github.com/sirupsen/logrus"
version = "v1.0.3"
Expand Down
2 changes: 1 addition & 1 deletion cli/Dockerfile-bin
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## compile binaries
FROM gcr.io/linkerd-io/go-deps:f95a60fe as golang
FROM gcr.io/linkerd-io/go-deps:87d183c6 as golang
WORKDIR /go/src/github.com/linkerd/linkerd2
COPY cli cli
COPY controller/k8s controller/k8s
Expand Down
72 changes: 38 additions & 34 deletions cli/cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,11 @@ import (
"os"
"time"

"github.com/briandowns/spinner"
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/spf13/cobra"
)

const (
retryStatus = "[retry]"
failStatus = "[FAIL]"
warningStatus = "[warning]"
)

type checkOptions struct {
versionOverride string
preInstallOnly bool
Expand Down Expand Up @@ -82,6 +77,7 @@ func configureAndRunChecks(options *checkOptions) error {
checks := []healthcheck.CategoryID{
healthcheck.KubernetesAPIChecks,
healthcheck.KubernetesVersionChecks,
healthcheck.LinkerdVersionChecks,
}

if options.preInstallOnly {
Expand All @@ -91,30 +87,19 @@ func configureAndRunChecks(options *checkOptions) error {
checks = append(checks, healthcheck.LinkerdPreInstallClusterChecks)
}
checks = append(checks, healthcheck.LinkerdPreInstallChecks)
} else if options.dataPlaneOnly {
checks = append(checks, healthcheck.LinkerdControlPlaneExistenceChecks)
checks = append(checks, healthcheck.LinkerdAPIChecks)
if !options.singleNamespace {
checks = append(checks, healthcheck.LinkerdServiceProfileChecks)
}
if options.namespace != "" {
checks = append(checks, healthcheck.LinkerdDataPlaneExistenceChecks)
}
checks = append(checks, healthcheck.LinkerdDataPlaneChecks)
} else {
checks = append(checks, healthcheck.LinkerdControlPlaneExistenceChecks)
checks = append(checks, healthcheck.LinkerdAPIChecks)

if !options.singleNamespace {
checks = append(checks, healthcheck.LinkerdServiceProfileChecks)
}
}

checks = append(checks, healthcheck.LinkerdVersionChecks)
if !(options.preInstallOnly || options.dataPlaneOnly) {
checks = append(checks, healthcheck.LinkerdControlPlaneVersionChecks)
}
if options.dataPlaneOnly {
checks = append(checks, healthcheck.LinkerdDataPlaneVersionChecks)
if options.dataPlaneOnly {
checks = append(checks, healthcheck.LinkerdDataPlaneChecks)
} else {
checks = append(checks, healthcheck.LinkerdControlPlaneVersionChecks)
}
}

hc := healthcheck.NewHealthChecker(checks, &healthcheck.Options{
Expand Down Expand Up @@ -150,30 +135,49 @@ func (o *checkOptions) validate() error {
}

func runChecks(w io.Writer, hc *healthcheck.HealthChecker) bool {
var lastCategory healthcheck.CategoryID
spin := spinner.New(spinner.CharSets[0], 100*time.Millisecond)
spin.Writer = w

prettyPrintResults := func(result *healthcheck.CheckResult) {
checkLabel := fmt.Sprintf("%s: %s", result.Category, result.Description)
if lastCategory != result.Category {
if lastCategory != "" {
fmt.Fprintln(w, "")
}

underline := ""
for i := 0; i < len(result.Category); i++ {
underline = underline + "-"
}

filler := ""
lineBreak := "\n"
for i := 0; i < lineWidth-len(checkLabel)-len(okStatus)-len(lineBreak); i++ {
filler = filler + "."
fmt.Fprintln(w, result.Category)
fmt.Fprintln(w, underline)

lastCategory = result.Category
}

spin.Stop()
if result.Retry {
fmt.Fprintf(w, "%s%s%s -- %s%s", checkLabel, filler, retryStatus, result.Err, lineBreak)
spin.Suffix = fmt.Sprintf(" %s -- %s", result.Description, result.Err)
spin.Color("bgBlack", "bold", "fgRed")
return
}

status := okStatus
if result.Err != nil {
status := failStatus
status = failStatus
if result.Warning {
status = warningStatus
status = warnStatus
}
fmt.Fprintf(w, "%s%s%s -- %s%s", checkLabel, filler, status, result.Err, lineBreak)
return
}

fmt.Fprintf(w, "%s%s%s%s", checkLabel, filler, okStatus, lineBreak)
fmt.Fprintln(w, status, result.Description)
if result.Err != nil {
fmt.Fprintf(w, " %s\n", result.Err)
if result.HintURL != "" {
fmt.Fprintf(w, " See %s for hints\n", result.HintURL)
}
}
}

return hc.RunChecks(prettyPrintResults)
Expand Down
77 changes: 41 additions & 36 deletions cli/cmd/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ const (
PodNamespaceEnvVarName = "LINKERD2_PROXY_POD_NAMESPACE"

// for inject reports
hostNetworkDesc = "hostNetwork: pods do not use host networking"
sidecarDesc = "sidecar: pods do not have a proxy or initContainer already injected"
unsupportedDesc = "supported: at least one resource injected"
udpDesc = "udp: pod specs do not include UDP ports"
hostNetworkDesc = "pods do not use host networking"
sidecarDesc = "pods do not have a proxy or initContainer already injected"
unsupportedDesc = "at least one resource injected"
udpDesc = "pod specs do not include UDP ports"
)

type injectOptions struct {
Expand Down Expand Up @@ -75,7 +75,7 @@ sub-folders, or coming from stdin.`,
# Download a resource and inject it through stdin.
curl http://url.to/yml | linkerd inject - | kubectl apply -f -
# Inject all the resources inside a folder and its sub-folders.
linkerd inject <folder> | kubectl apply -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -338,7 +338,8 @@ func (rt resourceTransformerInject) transform(bytes []byte, options *injectOptio
}

report := injectReport{
name: fmt.Sprintf("%s/%s", strings.ToLower(conf.meta.Kind), conf.om.Name),
kind: strings.ToLower(conf.meta.Kind),
name: conf.om.Name,
}

// If we don't inject anything into the pod template then output the
Expand Down Expand Up @@ -377,26 +378,30 @@ func (rt resourceTransformerInject) transform(bytes []byte, options *injectOptio
}

func (resourceTransformerInject) generateReport(injectReports []injectReport, output io.Writer) {
injected := []string{}
injected := []injectReport{}
hostNetwork := []string{}
sidecar := []string{}
udp := []string{}
warningsPrinted := verbose

for _, r := range injectReports {
if !r.hostNetwork && !r.sidecar && !r.unsupportedResource {
injected = append(injected, r.name)
injected = append(injected, r)
}

if r.hostNetwork {
hostNetwork = append(hostNetwork, r.name)
hostNetwork = append(hostNetwork, r.resName())
warningsPrinted = true
}

if r.sidecar {
sidecar = append(sidecar, r.name)
sidecar = append(sidecar, r.resName())
warningsPrinted = true
}

if r.udp {
udp = append(udp, r.name)
udp = append(udp, r.resName())
warningsPrinted = true
}
}

Expand All @@ -407,47 +412,47 @@ func (resourceTransformerInject) generateReport(injectReports []injectReport, ou
// leading newline to separate from yaml output on stdout
output.Write([]byte("\n"))

hostNetworkPrefix := fmt.Sprintf("%s%s", hostNetworkDesc, getFiller(hostNetworkDesc))
if len(hostNetwork) == 0 {
output.Write([]byte(fmt.Sprintf("%s%s\n", hostNetworkPrefix, okStatus)))
} else {
output.Write([]byte(fmt.Sprintf("%s%s -- \"hostNetwork: true\" detected in %s\n", hostNetworkPrefix, warnStatus, strings.Join(hostNetwork, ", "))))
if len(hostNetwork) > 0 {
output.Write([]byte(fmt.Sprintf("%s \"hostNetwork: true\" detected in %s\n", warnStatus, strings.Join(hostNetwork, ", "))))
} else if verbose {
output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, hostNetworkDesc)))
}

sidecarPrefix := fmt.Sprintf("%s%s", sidecarDesc, getFiller(sidecarDesc))
if len(sidecar) == 0 {
output.Write([]byte(fmt.Sprintf("%s%s\n", sidecarPrefix, okStatus)))
} else {
output.Write([]byte(fmt.Sprintf("%s%s -- known sidecar detected in %s\n", sidecarPrefix, warnStatus, strings.Join(sidecar, ", "))))
if len(sidecar) > 0 {
output.Write([]byte(fmt.Sprintf("%s known sidecar detected in %s\n", warnStatus, strings.Join(sidecar, ", "))))
} else if verbose {
output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, sidecarDesc)))
}

unsupportedPrefix := fmt.Sprintf("%s%s", unsupportedDesc, getFiller(unsupportedDesc))
if len(injected) > 0 {
output.Write([]byte(fmt.Sprintf("%s%s\n", unsupportedPrefix, okStatus)))
} else {
output.Write([]byte(fmt.Sprintf("%s%s -- no supported objects found\n", unsupportedPrefix, warnStatus)))
if len(injected) == 0 {
output.Write([]byte(fmt.Sprintf("%s no supported objects found\n", warnStatus)))
} else if verbose {
output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, unsupportedDesc)))
}

udpPrefix := fmt.Sprintf("%s%s", udpDesc, getFiller(udpDesc))
if len(udp) == 0 {
output.Write([]byte(fmt.Sprintf("%s%s\n", udpPrefix, okStatus)))
} else {
if len(udp) > 0 {
verb := "uses"
if len(udp) > 1 {
verb = "use"
}
output.Write([]byte(fmt.Sprintf("%s%s -- %s %s \"protocol: UDP\"\n", udpPrefix, warnStatus, strings.Join(udp, ", "), verb)))
output.Write([]byte(fmt.Sprintf("%s %s %s \"protocol: UDP\"\n", warnStatus, strings.Join(udp, ", "), verb)))
} else if verbose {
output.Write([]byte(fmt.Sprintf("%s %s\n", okStatus, udpDesc)))
}

//
// Summary
//
if warningsPrinted {
output.Write([]byte("\n"))
}

summary := fmt.Sprintf("Summary: %d of %d YAML document(s) injected", len(injected), len(injectReports))
output.Write([]byte(fmt.Sprintf("\n%s\n", summary)))

for _, i := range injected {
output.Write([]byte(fmt.Sprintf(" %s\n", i)))
for _, r := range injectReports {
if !r.hostNetwork && !r.sidecar && !r.unsupportedResource {
output.Write([]byte(fmt.Sprintf("%s \"%s\" injected\n", r.kind, r.name)))
} else {
output.Write([]byte(fmt.Sprintf("%s \"%s\" skipped\n", r.kind, r.name)))
}
}

// trailing newline to separate from kubectl output if piping
Expand Down
Loading

0 comments on commit 4f042de

Please sign in to comment.