sops/usererrors.go

165 строки
4.3 KiB
Go

package sops
import (
"fmt"
"io"
"strings"
"github.com/fatih/color"
"github.com/goware/prefixer"
"github.com/mitchellh/go-wordwrap"
)
// UserError is a well-formatted error for the purpose of being displayed to
// the end user.
type UserError interface {
error
UserError() string
}
var statusSuccess = color.New(color.FgGreen).Sprint("SUCCESS")
var statusFailed = color.New(color.FgRed).Sprint("FAILED")
type getDataKeyError struct {
RequiredSuccessfulKeyGroups int
GroupResults []error
}
func (err *getDataKeyError) successfulKeyGroups() int {
n := 0
for _, r := range err.GroupResults {
if r == nil {
n++
}
}
return n
}
func (err *getDataKeyError) Error() string {
return fmt.Sprintf("Error getting data key: %d successful groups "+
"required, got %d", err.RequiredSuccessfulKeyGroups,
err.successfulKeyGroups())
}
func (err *getDataKeyError) UserError() string {
var groupErrs []string
for i, res := range err.GroupResults {
groupErr := decryptGroupError{
err: res,
groupName: fmt.Sprintf("%d", i),
}
groupErrs = append(groupErrs, groupErr.UserError())
}
var trailer string
if err.RequiredSuccessfulKeyGroups == 0 {
trailer = "Recovery failed because no master key was able to decrypt " +
"the file. In order for SOPS to recover the file, at least one key " +
"has to be successful, but none were."
} else {
trailer = fmt.Sprintf("Recovery failed because the file was "+
"encrypted with a Shamir threshold of %d, but only %d part(s) "+
"were successfully recovered, one for each successful key group. "+
"In order for SOPS to recover the file, at least %d groups have "+
"to be successful. In order for a group to be successful, "+
"decryption has to succeed with any of the keys in that key group.",
err.RequiredSuccessfulKeyGroups, err.successfulKeyGroups(),
err.RequiredSuccessfulKeyGroups)
}
trailer = wordwrap.WrapString(trailer, 75)
return fmt.Sprintf("Failed to get the data key required to "+
"decrypt the SOPS file.\n\n%s\n\n%s",
strings.Join(groupErrs, "\n\n"), trailer)
}
type decryptGroupError struct {
groupName string
err error
}
func (r *decryptGroupError) Error() string {
return fmt.Sprintf("could not decrypt group %s: %s", r.groupName, r.err)
}
func (r *decryptGroupError) UserError() string {
var status string
if r.err == nil {
status = statusSuccess
} else {
status = statusFailed
}
header := fmt.Sprintf(`Group %s: %s`, r.groupName, status)
if r.err == nil {
return header
}
message := r.err.Error()
if userError, ok := r.err.(UserError); ok {
message = userError.UserError()
}
reader := prefixer.New(strings.NewReader(message), " ")
// Safe to ignore this error, as reading from a strings.Reader can't fail
errMsg, _ := io.ReadAll(reader)
return fmt.Sprintf("%s\n%s", header, string(errMsg))
}
type decryptKeyErrors []error
func (e decryptKeyErrors) Error() string {
return fmt.Sprintf("error decrypting key: %s", []error(e))
}
func (e decryptKeyErrors) UserError() string {
var errStrs []string
for _, err := range []error(e) {
if userErr, ok := err.(UserError); ok {
errStrs = append(errStrs, userErr.UserError())
} else {
errStrs = append(errStrs, err.Error())
}
}
return strings.Join(errStrs, "\n\n")
}
type decryptKeyError struct {
keyName string
errs []error
}
func (e *decryptKeyError) isSuccessful() bool {
for _, err := range e.errs {
if err == nil {
return true
}
}
return false
}
func (e *decryptKeyError) Error() string {
return fmt.Sprintf("error decrypting key %s: %s", e.keyName, e.errs)
}
func (e *decryptKeyError) UserError() string {
var status string
if e.isSuccessful() {
status = statusSuccess
} else {
status = statusFailed
}
header := fmt.Sprintf("%s: %s", e.keyName, status)
if e.isSuccessful() {
return header
}
var errMessages []string
for _, err := range e.errs {
wrappedErr := wordwrap.WrapString(err.Error(), 60)
reader := prefixer.New(strings.NewReader(wrappedErr), " | ")
// Safe to ignore this error, as reading from a strings.Reader can't fail
errMsg, _ := io.ReadAll(reader)
errMsg[0] = '-'
errMessages = append(errMessages, string(errMsg))
}
joinedMsgs := strings.Join(errMessages, "\n\n")
reader := prefixer.New(strings.NewReader(joinedMsgs), " ")
errMsg, _ := io.ReadAll(reader)
return fmt.Sprintf("%s\n%s", header, string(errMsg))
}