Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend version command to help troubleshoot porter #642

Merged
merged 10 commits into from
Sep 30, 2019
3 changes: 2 additions & 1 deletion cmd/porter/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func buildVersionCommand(p *porter.Porter) *cobra.Command {
opts := version.Options{}
opts := porter.VersionOpts{}
cmd := &cobra.Command{
Use: "version",
Short: "Print the application version",
Expand All @@ -25,6 +25,7 @@ func buildVersionCommand(p *porter.Porter) *cobra.Command {
f := cmd.Flags()
f.StringVarP(&opts.RawFormat, "output", "o", string(version.DefaultVersionFormat),
"Specify an output format. Allowed values: json, plaintext")
f.BoolVarP(&opts.System, "system", "s", false, "Print system debug information")

return cmd
}
1 change: 1 addition & 0 deletions docs/content/cli/version.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ porter version [flags]
```
-h, --help help for version
-o, --output string Specify an output format. Allowed values: json, plaintext (default "plaintext")
-s, --system Print system debug information
```

### Options inherited from parent commands
Expand Down
97 changes: 95 additions & 2 deletions pkg/porter/version.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,111 @@
package porter

import (
"bytes"
"fmt"
"github.com/deislabs/porter/pkg"
"github.com/deislabs/porter/pkg/context"
"github.com/deislabs/porter/pkg/mixin"
"github.com/deislabs/porter/pkg/porter/version"
"github.com/deislabs/porter/pkg/printer"
"github.com/pkg/errors"
"runtime"
"text/template"
)

func (p *Porter) PrintVersion(opts version.Options) error {
type VersionOpts struct {
version.Options
System bool
}

type SystemInfo struct {
OS string
Arch string
}

type Mixins []mixin.Metadata

type SystemDebugInfo struct {
Version mixin.Metadata `json:"version"`
SysInfo SystemInfo `json:"system"`
Mixins Mixins `json:"mixins"`
}

func (mixins Mixins) PrintMixinsTable() string {
buffer := &bytes.Buffer{}
printMixinRow :=
func(v interface{}) []interface{} {
m, ok := v.(mixin.Metadata)
if !ok {
return nil
}
return []interface{}{m.Name, m.VersionInfo.Version, m.VersionInfo.Author}
}
err := printer.PrintTable(buffer, mixins, printMixinRow, "Name", "Version", "Author")
if err != nil {
return ""
}
return buffer.String()
}

func (p *Porter) PrintVersion(opts VersionOpts) error {
metadata := mixin.Metadata{
Name: "porter",
VersionInfo: mixin.VersionInfo{
Version: pkg.Version,
Commit: pkg.Commit,
},
}
return version.PrintVersion(p.Context, opts, metadata)

if opts.System {
return p.PrintDebugInfo(p.Context, opts, metadata)
}

return version.PrintVersion(p.Context, opts.Options, metadata)
}

func getSystemInfo() *SystemInfo {
return &SystemInfo{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
}
}

func (p *Porter) PrintDebugInfo(ctx *context.Context, opts VersionOpts, versionMetadata mixin.Metadata) error {
opts.RawFormat = string(printer.FormatPlaintext)
mixins, err := p.ListMixins()
sysInfo := getSystemInfo()
if err != nil {
_ = errors.Wrap(err, "Failed to get list of mixins")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of ignoring the error (like it's doing now), or returning it (which would make it very hard to use this command for troubleshooting problems 😅 ), we can have it print a debug message and then keep going:

The contributing guide has a section on how to handle debug logging:

https://github.com/deislabs/porter/blob/master/CONTRIBUTING.md#logging

You can check p.Debug and then print to p.Err following the example in the guide, then have the rest of the command continue. That way even if we can't print any mixin data, at least we are able to print the data we do have.

}
sysDebugInfo := SystemDebugInfo{
Version: versionMetadata,
SysInfo: *sysInfo,
Mixins: mixins,
}

switch opts.Format {
case printer.FormatJson:
return printer.PrintJson(ctx.Out, sysDebugInfo)
case printer.FormatPlaintext:
plaintextTmpl := `{{.Version.Name}} {{.Version.VersionInfo.Version}} ({{.Version.VersionInfo.Commit}})

System
-------
os: {{.SysInfo.OS}}
arch: {{.SysInfo.Arch}}
{{if .Mixins}}
Mixins
-------
{{.Mixins.PrintMixinsTable}}{{end}}
`
tmpl, err := template.New("systemDebugInfo").Parse(plaintextTmpl)
if err != nil {
_ = errors.Wrap(err, "Failed to print system debug information")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, if we can't parse the template, then we should return the error, and not ignore it with _ because it's an unrecoverable error.

If the template isn't parsed, then continuing on to call Execute on the template (which is nil when err is populated) will cause a nil reference exception.

}
err = tmpl.Execute(ctx.Out, sysDebugInfo)
return err
default:
return fmt.Errorf("unsupported format: %s", opts.Format)
}
}
67 changes: 64 additions & 3 deletions pkg/porter/version_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package porter

import (
"fmt"
"github.com/stretchr/testify/assert"
"runtime"
"strings"
"testing"

"github.com/deislabs/porter/pkg"
"github.com/deislabs/porter/pkg/porter/version"
"github.com/deislabs/porter/pkg/printer"
"github.com/stretchr/testify/require"
)
Expand All @@ -16,7 +18,7 @@ func TestPrintVersion(t *testing.T) {

p := NewTestPorter(t)

opts := version.Options{}
opts := VersionOpts{}
err := opts.Validate()
require.NoError(t, err)
p.PrintVersion(opts)
Expand All @@ -34,7 +36,7 @@ func TestPrintJsonVersion(t *testing.T) {

p := NewTestPorter(t)

opts := version.Options{}
opts := VersionOpts{}
opts.RawFormat = string(printer.FormatJson)
err := opts.Validate()
require.NoError(t, err)
Expand All @@ -51,3 +53,62 @@ func TestPrintJsonVersion(t *testing.T) {
t.Fatalf("invalid output:\nWANT:\t%q\nGOT:\t%q\n", wantOutput, gotOutput)
}
}

func TestPrintDebugInfoJsonVersion(t *testing.T) {
pkg.Commit = "abc123"
pkg.Version = "v1.2.3"

p := NewTestPorter(t)

opts := VersionOpts{System: true}
p.TestConfig.SetupPorterHome()
opts.RawFormat = string(printer.FormatJson)
err := opts.Validate()
require.Nil(t, err)
p.PrintVersion(opts)

gotOutput := p.TestConfig.TestContext.GetOutput()
wantOutput := fmt.Sprintf(`{
"version": {
"name": "porter",
"version": "v1.2.3",
"commit": "abc123"
},
"system": {
"OS": "%s",
"Arch": "%s"
},
"mixins": [
{
"name": "exec",
"version": "v1.0",
"commit": "abc123",
"author": "Deis Labs"
}
]
}
`, runtime.GOOS, runtime.GOARCH)
assert.Equal(t, wantOutput, gotOutput)
}

func TestPrintDebugInfoPlainTextVersion(t *testing.T) {
pkg.Commit = "abc123"
pkg.Version = "v1.2.3"

p := NewTestPorter(t)

opts := VersionOpts{System: true}
p.TestConfig.SetupPorterHome()
err := opts.Validate()
require.Nil(t, err)
p.PrintVersion(opts)

versionOutput := "porter v1.2.3 (abc123)"
mixinsOutput := "exec v1.0 Deis Labs"
systemOutput := fmt.Sprintf("os: %s\narch: %s", runtime.GOOS, runtime.GOARCH)

gotOutput := p.TestConfig.TestContext.GetOutput()
assert.Contains(t, gotOutput, versionOutput)
assert.Contains(t, gotOutput, mixinsOutput)
assert.Contains(t, gotOutput, systemOutput)
}