diff --git a/cmd/cli/osm.go b/cmd/cli/osm.go index 6007b020c2..dc25e17c22 100644 --- a/cmd/cli/osm.go +++ b/cmd/cli/osm.go @@ -46,6 +46,7 @@ func newRootCmd(config *action.Configuration, in io.Reader, out io.Writer, args newProxyCmd(config, out), newTrafficPolicyCmd(out), newUninstallCmd(config, in, out), + newSupportCmd(out), ) _ = flags.Parse(args) diff --git a/cmd/cli/support.go b/cmd/cli/support.go new file mode 100644 index 0000000000..f14d7099c8 --- /dev/null +++ b/cmd/cli/support.go @@ -0,0 +1,24 @@ +package main + +import ( + "io" + + "github.com/spf13/cobra" +) + +const supportCmdDescription = ` +This command consists of multiple subcommands related supportability and +associated tooling, such as examining error codes. +` + +func newSupportCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "support", + Short: "supportability tooling", + Long: supportCmdDescription, + Args: cobra.NoArgs, + } + cmd.AddCommand(newSupportErrInfoCmd(out)) + + return cmd +} diff --git a/cmd/cli/support_err.go b/cmd/cli/support_err.go new file mode 100644 index 0000000000..584cac5e22 --- /dev/null +++ b/cmd/cli/support_err.go @@ -0,0 +1,88 @@ +package main + +import ( + "io" + "sort" + + "github.com/olekukonko/tablewriter" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/openservicemesh/osm/pkg/errcode" +) + +const errInfoDescription = ` +This command lists the mapping of one or all error codes to their description.` + +const errInfoExample = ` +Get the description for the error code E1000 +# osm support error-info E1000 + +Get the description for all error codes +# osm support error-info +` + +type errInfoCmd struct { + out io.Writer +} + +func newSupportErrInfoCmd(out io.Writer) *cobra.Command { + errInfoCmd := &errInfoCmd{ + out: out, + } + + cmd := &cobra.Command{ + Use: "error-info", + Short: "lists mapping of error code to its description", + Long: errInfoDescription, + Args: cobra.MaximumNArgs(1), + RunE: func(_ *cobra.Command, args []string) error { + var errCode string + if len(args) != 0 { + errCode = args[0] + } + return errInfoCmd.run(errCode) + }, + Example: errInfoExample, + } + + return cmd +} + +func (cmd *errInfoCmd) run(errCode string) error { + table := tablewriter.NewWriter(cmd.out) + table.SetHeader([]string{"Error code", "Description"}) + table.SetRowLine(true) + table.SetColWidth(70) + + if errCode != "" { + // Print the error code description mapping only for the given error code + e, err := errcode.FromStr(errCode) + if err != nil { + return errors.Errorf("Error code '%s' is not a valid error code format. Should be of the form Exxxx, ex. E1000.", errCode) + } + description, ok := errcode.ErrCodeMap[e] + if !ok { + return errors.Errorf("Error code '%s' is not a valid error code recognized by OSM", errCode) + } + table.Append([]string{errCode, description}) + } else { + // Print the error code description mapping for all error codes + var sortedErrKeys []errcode.ErrCode + for err := range errcode.ErrCodeMap { + sortedErrKeys = append(sortedErrKeys, err) + } + sort.Slice(sortedErrKeys, func(i, j int) bool { + return sortedErrKeys[i] < sortedErrKeys[j] + }) + + for _, key := range sortedErrKeys { + desc := errcode.ErrCodeMap[key] + table.Append([]string{key.String(), desc}) + } + } + + table.Render() + + return nil +} diff --git a/cmd/cli/support_err_test.go b/cmd/cli/support_err_test.go new file mode 100644 index 0000000000..371e6f5734 --- /dev/null +++ b/cmd/cli/support_err_test.go @@ -0,0 +1,51 @@ +package main + +import ( + "bytes" + "testing" + + tassert "github.com/stretchr/testify/assert" +) + +// TestErrInfoRun tests errInfoCmd.run() + +func TestErrInfoRun(t *testing.T) { + assert := tassert.New(t) + + testCases := []struct { + name string + errCode string + expectErr bool + }{ + { + name: "valid error code as input", + errCode: "E1000", + expectErr: false, + }, + { + name: "invalid error code format as input", + errCode: "Foo", + expectErr: true, + }, + { + name: "valid error code format but unrecognized code as input", + errCode: "E10000", + expectErr: true, + }, + { + name: "list all error codes", + errCode: "", + expectErr: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var out bytes.Buffer + cmd := &errInfoCmd{out: &out} + err := cmd.run(tc.errCode) + + assert.Equal(tc.expectErr, err != nil) + }) + } +} diff --git a/pkg/errcode/errcode.go b/pkg/errcode/errcode.go index 3895c0dab9..0cf286ab59 100644 --- a/pkg/errcode/errcode.go +++ b/pkg/errcode/errcode.go @@ -4,9 +4,12 @@ package errcode import ( "fmt" + "strconv" + "strings" ) -type errCode int +// ErrCode defines the type to represent error codes +type ErrCode int const ( // Kind defines the kind for the error code constants @@ -16,7 +19,7 @@ const ( // Range 1000-1050 is reserved for errors related to application startup or bootstrapping const ( // ErrInvalidCLIArgument indicates an invalid CLI argument - ErrInvalidCLIArgument errCode = iota + 1000 + ErrInvalidCLIArgument ErrCode = iota + 1000 // ErrSettingLogLevel indicates the specified log level could not be set ErrSettingLogLevel @@ -37,7 +40,7 @@ const ( // Range 2000-2500 is reserved for errors related to traffic policies const ( // ErrDedupEgressTrafficMatches indicates an error related to deduplicating egress traffic matches - ErrDedupEgressTrafficMatches errCode = iota + 2000 + ErrDedupEgressTrafficMatches ErrCode = iota + 2000 // ErrDedupEgressClusterConfigs indicates an error related to deduplicating egress cluster configs ErrDedupEgressClusterConfigs @@ -71,22 +74,33 @@ const ( // Range 3000-3500 is reserved for errors related to k8s constructs (service accounts, namespaces, etc.) const ( // ErrServiceHostnames indicates the hostnames associated with a service could not be computed - ErrServiceHostnames errCode = iota + 3000 + ErrServiceHostnames ErrCode = iota + 3000 // ErrNoMatchingServiceForServiceAccount indicates there are no services associated with the service account ErrNoMatchingServiceForServiceAccount ) // String returns the error code as a string, ex. E1000 -func (e errCode) String() string { +func (e ErrCode) String() string { return fmt.Sprintf("E%d", e) } +// FromStr returns the ErrCode representation for the given error code string +// Ex. E1000 is converted to ErrInvalidCLIArgument +func FromStr(e string) (ErrCode, error) { + errStr := strings.TrimLeft(e, "E") + errInt, err := strconv.Atoi(errStr) + if err != nil { + return ErrCode(-1), err + } + return ErrCode(errInt), nil +} + +// ErrCodeMap defines the mapping of error codes to their description. // Note: error code description mappings must be defined in the same order // as they appear in the error code definitions - from lowest to highest // ranges in the order they appear within the range. -//nolint: deadcode,varcheck,unused -var errCodeMap = map[errCode]string{ +var ErrCodeMap = map[ErrCode]string{ // // Range 1000-1050 //