mirror of https://github.com/fluxcd/cli-utils.git
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
// Copyright 2020 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package errors
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/fluxcd/cli-utils/pkg/inventory"
|
|
"github.com/fluxcd/cli-utils/pkg/manifestreader"
|
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
|
)
|
|
|
|
const (
|
|
DefaultErrorExitCode = 1
|
|
)
|
|
|
|
var errorMsgForType map[reflect.Type]string
|
|
var statusCodeForType map[reflect.Type]int
|
|
|
|
//nolint:gochecknoinits
|
|
func init() {
|
|
errorMsgForType = make(map[reflect.Type]string)
|
|
errorMsgForType[reflect.TypeOf(inventory.NoInventoryObjError{})] = `
|
|
Package uninitialized. Please run "{{.cmdNameBase}} init" command.
|
|
|
|
The package needs to be initialized to generate the template
|
|
which will store state for resource sets. This state is
|
|
necessary to perform functionality such as deleting an entire
|
|
package or automatically deleting omitted resources (pruning).
|
|
`
|
|
|
|
errorMsgForType[reflect.TypeOf(inventory.MultipleInventoryObjError{})] = `
|
|
Package has multiple inventory object templates.
|
|
|
|
The package should have one and only one inventory object template.
|
|
`
|
|
|
|
errorMsgForType[reflect.TypeOf(manifestreader.UnknownTypesError{})] = `
|
|
Unknown type(s) encountered. Every type must either be already installed in the cluster or the CRD must be among the applied manifests.
|
|
|
|
{{- range .err.GroupKinds}}
|
|
{{ printf "%s" . }}
|
|
{{- end}}
|
|
`
|
|
|
|
statusCodeForType = make(map[reflect.Type]int)
|
|
}
|
|
|
|
// CheckErr looks up the appropriate error message and exit status for known
|
|
// errors. It will print the information to the provided io.Writer. If we
|
|
// don't know the error, it delegates to the error handling in cmdutil.
|
|
func CheckErr(w io.Writer, err error, cmdNameBase string) {
|
|
errText, found := textForError(err, cmdNameBase)
|
|
if found {
|
|
exitStatus := findErrExitCode(err)
|
|
if len(errText) > 0 {
|
|
if !strings.HasSuffix(errText, "\n") {
|
|
errText += "\n"
|
|
}
|
|
fmt.Fprint(w, errText)
|
|
}
|
|
os.Exit(exitStatus)
|
|
}
|
|
|
|
cmdutil.CheckErr(err)
|
|
}
|
|
|
|
// textForError looks up the error message based on the type of the error.
|
|
func textForError(baseErr error, cmdNameBase string) (string, bool) {
|
|
errType, found := findErrType(baseErr)
|
|
if !found {
|
|
return "", false
|
|
}
|
|
tmplText, found := errorMsgForType[errType]
|
|
if !found {
|
|
return "", false
|
|
}
|
|
|
|
tmpl, err := template.New("errMsg").Parse(tmplText)
|
|
if err != nil {
|
|
// Just return false here instead of the error. It will just
|
|
// mean a less informative error message and we rather show the
|
|
// original error.
|
|
return "", false
|
|
}
|
|
var b bytes.Buffer
|
|
err = tmpl.Execute(&b, map[string]interface{}{
|
|
"cmdNameBase": cmdNameBase,
|
|
"err": baseErr,
|
|
})
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
return strings.TrimSpace(b.String()), true
|
|
}
|
|
|
|
// findErrType finds the type of the error. It returns the real type in the
|
|
// event the error is actually a pointer to a type.
|
|
func findErrType(err error) (reflect.Type, bool) {
|
|
switch reflect.ValueOf(err).Kind() {
|
|
case reflect.Ptr:
|
|
// If the value of the interface is a pointer, we use the type
|
|
// of the real value.
|
|
return reflect.ValueOf(err).Elem().Type(), true
|
|
case reflect.Struct:
|
|
return reflect.TypeOf(err), true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
// findErrExitCode looks up if there is a defined error code for the provided
|
|
// error type.
|
|
func findErrExitCode(err error) int {
|
|
errType, found := findErrType(err)
|
|
if !found {
|
|
return DefaultErrorExitCode
|
|
}
|
|
if exitStatus, found := statusCodeForType[errType]; found {
|
|
return exitStatus
|
|
}
|
|
return DefaultErrorExitCode
|
|
}
|