diff --git a/internal/errors/resolver/git.go b/internal/errors/resolver/git.go index 877ceca65c..11e8903f4b 100644 --- a/internal/errors/resolver/git.go +++ b/internal/errors/resolver/git.go @@ -27,50 +27,6 @@ func init() { AddErrorResolver(&gitExecErrorResolver{}) } -const ( - genericGitExecError = ` -Error: Failed to execute git command {{ printf "%q " .gitcmd }} -{{- if gt (len .repo) 0 -}} -against repo {{ printf "%q " .repo }} -{{- end }} -{{- if gt (len .ref) 0 -}} -for reference {{ printf "%q " .ref }} -{{- end }} - -{{- template "ExecOutputDetails" . }} -` - - unknownRefGitExecError = ` -Error: Unknown ref {{ printf "%q" .ref }}. Please verify that the reference exists in upstream repo {{ printf "%q" .repo }}. - -{{- template "ExecOutputDetails" . }} -` - - noGitBinaryError = ` -Error: No git executable found. kpt requires git to be installed and available in the path. - -{{- template "ExecOutputDetails" . }} -` - - httpsAuthRequired = ` -Error: Repository {{ printf "%q" .repo }} requires authentication. kpt does not support this for the 'https' protocol. Please use the 'git' protocol instead. - -{{- template "ExecOutputDetails" . }} -` - - repositoryUnavailable = ` -Error: Unable to access repository {{ printf "%q" .repo }}. - -{{- template "ExecOutputDetails" . }} -` - - repositoryNotFound = ` -Error: Repository {{ printf "%q" .repo }} not found. - -{{- template "ExecOutputDetails" . }} -` -) - // gitExecErrorResolver is an implementation of the ErrorResolver interface // that can produce error messages for errors of the gitutil.GitExecError type. type gitExecErrorResolver struct{} @@ -82,29 +38,55 @@ func (*gitExecErrorResolver) Resolve(err error) (ResolvedResult, bool) { } fullCommand := fmt.Sprintf("git %s %s", gitExecErr.Command, strings.Join(gitExecErr.Args, " ")) - tmplArgs := map[string]interface{}{ - "gitcmd": fullCommand, - "repo": gitExecErr.Repo, - "ref": gitExecErr.Ref, - "stdout": gitExecErr.StdOut, - "stderr": gitExecErr.StdErr, - } + var msg string switch gitExecErr.Type { case gitutil.UnknownReference: - msg = ExecuteTemplate(unknownRefGitExecError, tmplArgs) + msg = fmt.Sprintf("Error: Unknown ref %q. Please verify that the reference exists in upstream repo %q.", gitExecErr.Ref, gitExecErr.Repo) + msg = msg + "\n" + BuildOutputDetails(gitExecErr.StdOut, gitExecErr.StdErr) + case gitutil.GitExecutableNotFound: - msg = ExecuteTemplate(noGitBinaryError, tmplArgs) + msg = "Error: No git executable found. kpt requires git to be installed and available in the path." + msg = msg + "\n" + BuildOutputDetails(gitExecErr.StdOut, gitExecErr.StdErr) + case gitutil.HTTPSAuthRequired: - msg = ExecuteTemplate(httpsAuthRequired, tmplArgs) + msg = fmt.Sprintf("Error: Repository %q requires authentication.", gitExecErr.Repo) + msg += " kpt does not support this for the 'https' protocol." + msg += " Please use the 'git' protocol instead." + msg = msg + "\n" + BuildOutputDetails(gitExecErr.StdOut, gitExecErr.StdErr) + case gitutil.RepositoryUnavailable: - msg = ExecuteTemplate(repositoryUnavailable, tmplArgs) + msg = fmt.Sprintf("Error: Unable to access repository %q.", gitExecErr.Repo) + msg = msg + "\n" + BuildOutputDetails(gitExecErr.StdOut, gitExecErr.StdErr) + case gitutil.RepositoryNotFound: - msg = ExecuteTemplate(repositoryNotFound, tmplArgs) + msg = fmt.Sprintf("Error: Repository %q not found.", gitExecErr.Repo) + msg = msg + "\n" + BuildOutputDetails(gitExecErr.StdOut, gitExecErr.StdErr) default: - msg = ExecuteTemplate(genericGitExecError, tmplArgs) + msg = fmt.Sprintf("Error: Failed to execute git command %q", fullCommand) + if gitExecErr.Repo != "" { + msg += fmt.Sprintf(" against repo %q", gitExecErr.Repo) + } + if gitExecErr.Ref != "" { + msg += fmt.Sprintf(" for reference %q", gitExecErr.Ref) + } + msg = msg + "\n" + BuildOutputDetails(gitExecErr.StdOut, gitExecErr.StdErr) } return ResolvedResult{ Message: msg, }, true } + +func BuildOutputDetails(stdout string, stderr string) string { + var sb strings.Builder + if len(stdout) > 0 || len(stderr) > 0 { + sb.WriteString("\nDetails:\n") + } + if len(stdout) > 0 { + sb.WriteString(stdout) + } + if len(stderr) > 0 { + sb.WriteString(stderr) + } + return sb.String() +} diff --git a/internal/errors/resolver/live.go b/internal/errors/resolver/live.go index ce70321cbb..a715e5f288 100644 --- a/internal/errors/resolver/live.go +++ b/internal/errors/resolver/live.go @@ -15,6 +15,8 @@ package resolver import ( + "fmt" + initialization "github.com/GoogleContainerTools/kpt/commands/live/init" "github.com/GoogleContainerTools/kpt/internal/cmdutil" "github.com/GoogleContainerTools/kpt/internal/errors" @@ -45,14 +47,6 @@ Error: Package has multiple inventory object templates. The package should have one and only one inventory object template. ` - resourceGroupCRDInstallErrorMsg = ` -Error: Unable to install the ResourceGroup CRD. - -{{- if gt (len .cause) 0 }} -{{ printf "\nDetails:" }} -{{ printf "%s" .cause }} -{{- end }} -` //nolint:lll noResourceGroupCRDMsg = ` Error: The ResourceGroup CRD was not found in the cluster. Please install it either by using the '--install-resource-group' flag or the 'kpt live install-resource-group' command. @@ -75,25 +69,6 @@ Error: Inventory information has already been added to the package Kptfile objec multipleResourceGroupsMsg = ` Error: Multiple ResourceGroup objects found. Please make sure at most one ResourceGroup object exists within the package. -` - - //nolint:lll - inventoryInfoValidationMsg = ` -Error: The inventory information is not valid. Please update the information in the ResourceGroup file or provide information with the command line flags. To generate the inventory information the first time, use the 'kpt live init' command. - -Details: -{{- range .err.Violations}} -{{printf "%s" .Reason }} -{{- end}} -` - - unknownTypesMsg = ` -Error: {{ printf "%d" (len .err.GroupVersionKinds) }} resource types could not be found in the cluster or as CRDs among the applied resources. - -Resource types: -{{- range .err.GroupVersionKinds}} -{{ printf "%s" .String }} -{{- end}} ` ) @@ -104,92 +79,79 @@ type liveErrorResolver struct{} func (*liveErrorResolver) Resolve(err error) (ResolvedResult, bool) { var noInventoryObjError *inventory.NoInventoryObjError if errors.As(err, &noInventoryObjError) { - return ResolvedResult{ - Message: ExecuteTemplate(noInventoryObjErrorMsg, map[string]interface{}{ - "err": *noInventoryObjError, - }), - }, true + msg := noInventoryObjErrorMsg + return ResolvedResult{Message: msg}, true } var multipleInventoryObjError *inventory.MultipleInventoryObjError if errors.As(err, &multipleInventoryObjError) { - return ResolvedResult{ - Message: ExecuteTemplate(multipleInventoryObjErrorMsg, map[string]interface{}{ - "err": *multipleInventoryObjError, - }), - }, true + msg := multipleInventoryObjErrorMsg + return ResolvedResult{Message: msg}, true } var resourceGroupCRDInstallError *cmdutil.ResourceGroupCRDInstallError if errors.As(err, &resourceGroupCRDInstallError) { - return ResolvedResult{ - Message: ExecuteTemplate(resourceGroupCRDInstallErrorMsg, map[string]interface{}{ - "cause": resourceGroupCRDInstallError.Err.Error(), - }), - }, true + msg := "Error: Unable to install the ResourceGroup CRD." + + cause := resourceGroupCRDInstallError.Err + msg += fmt.Sprintf("\nDetails: %v", cause) + + return ResolvedResult{Message: msg}, true } var noResourceGroupCRDError *cmdutil.NoResourceGroupCRDError if errors.As(err, &noResourceGroupCRDError) { - return ResolvedResult{ - Message: ExecuteTemplate(noResourceGroupCRDMsg, map[string]interface{}{ - "err": *noResourceGroupCRDError, - }), - }, true + msg := noResourceGroupCRDMsg + return ResolvedResult{Message: msg}, true } var invExistsError *initialization.InvExistsError if errors.As(err, &invExistsError) { - return ResolvedResult{ - Message: ExecuteTemplate(invInfoAlreadyExistsMsg, map[string]interface{}{ - "err": *invExistsError, - }), - }, true + msg := invInfoAlreadyExistsMsg + return ResolvedResult{Message: msg}, true } var invInfoInRGAlreadyExistsError *initialization.InvInRGExistsError if errors.As(err, &invInfoInRGAlreadyExistsError) { - return ResolvedResult{ - Message: ExecuteTemplate(invInfoInRGAlreadyExistsMsg, map[string]interface{}{ - "err": *invInfoInRGAlreadyExistsError, - }), - }, true + msg := invInfoInRGAlreadyExistsMsg + return ResolvedResult{Message: msg}, true } var invInKfExistsError *initialization.InvInKfExistsError if errors.As(err, &invInKfExistsError) { - return ResolvedResult{ - Message: ExecuteTemplate(invInfoInKfAlreadyExistsMsg, map[string]interface{}{ - "err": *invInKfExistsError, - }), - }, true + msg := invInfoInKfAlreadyExistsMsg + return ResolvedResult{Message: msg}, true } var multipleResourceGroupsError *pkg.MultipleResourceGroupsError if errors.As(err, &multipleResourceGroupsError) { - return ResolvedResult{ - Message: ExecuteTemplate(multipleResourceGroupsMsg, map[string]interface{}{ - "err": *multipleResourceGroupsError, - }), - }, true + msg := multipleResourceGroupsMsg + return ResolvedResult{Message: msg}, true } var inventoryInfoValidationError *live.InventoryInfoValidationError if errors.As(err, &inventoryInfoValidationError) { - return ResolvedResult{ - Message: ExecuteTemplate(inventoryInfoValidationMsg, map[string]interface{}{ - "err": *inventoryInfoValidationError, - }), - }, true + msg := "Error: The inventory information is not valid." + msg += " Please update the information in the ResourceGroup file or provide information with the command line flags." + msg += " To generate the inventory information the first time, use the 'kpt live init' command." + + msg += "\nDetails:\n" + for _, v := range inventoryInfoValidationError.Violations { + msg += fmt.Sprintf("%s\n", v.Reason) + } + + return ResolvedResult{Message: msg}, true } var unknownTypesError *manifestreader.UnknownTypesError if errors.As(err, &unknownTypesError) { - return ResolvedResult{ - Message: ExecuteTemplate(unknownTypesMsg, map[string]interface{}{ - "err": *unknownTypesError, - }), - }, true + msg := fmt.Sprintf("Error: %d resource types could not be found in the cluster or as CRDs among the applied resources.", len(unknownTypesError.GroupVersionKinds)) + msg += "\n\nResource types:\n" + for _, gvk := range unknownTypesError.GroupVersionKinds { + msg += fmt.Sprintf("%s\n", gvk) + } + + return ResolvedResult{Message: msg}, true } var resultError *common.ResultError diff --git a/internal/errors/resolver/pkg.go b/internal/errors/resolver/pkg.go index 63e17a857c..71592aa206 100644 --- a/internal/errors/resolver/pkg.go +++ b/internal/errors/resolver/pkg.go @@ -15,6 +15,7 @@ package resolver import ( + "fmt" "os" "github.com/GoogleContainerTools/kpt/internal/errors" @@ -27,33 +28,6 @@ func init() { AddErrorResolver(&pkgErrorResolver{}) } -const ( - noKptfileMsg = ` -Error: No Kptfile found at {{ printf "%q" .path }}. -` - - //nolint:lll - deprecatedv1Alpha1KptfileMsg = ` -Error: Kptfile at {{ printf "%q" .path }} has an old version ({{ printf "%q" .version }}) of the Kptfile schema. -Please update the package to the latest format by following https://kpt.dev/installation/migration. -` - - deprecatedv1Alpha2KptfileMsg = ` -Error: Kptfile at {{ printf "%q" .path }} has an old version ({{ printf "%q" .version }}) of the Kptfile schema. -Please run "kpt fn eval -i gcr.io/kpt-fn/fix:v0.2 --include-meta-resources" to upgrade the package and retry. -` - - unknownKptfileResourceMsg = ` -Error: Kptfile at {{ printf "%q" .path }} has an unknown resource type ({{ printf "%q" .gvk.String }}). -` - - kptfileReadErrMsg = ` -Error: Kptfile at {{ printf "%q" .path }} can't be read. - -{{- template "NestedErrDetails" . }} -` -) - // pkgErrorResolver is an implementation of the ErrorResolver interface // that can produce error messages for errors of the pkg.KptfileError type. type pkgErrorResolver struct{} @@ -62,22 +36,15 @@ func (*pkgErrorResolver) Resolve(err error) (ResolvedResult, bool) { var kptfileError *pkg.KptfileError if errors.As(err, &kptfileError) { path := kptfileError.Path - tmplArgs := map[string]interface{}{ - "path": path, - "err": kptfileError, - } - return resolveNestedErr(kptfileError, tmplArgs) + return resolveNestedErr(kptfileError, path.String()) } var remoteKptfileError *pkg.RemoteKptfileError if errors.As(err, &remoteKptfileError) { path := remoteKptfileError.RepoSpec.RepoRef() - tmplArgs := map[string]interface{}{ - "path": path, - "err": kptfileError, - } - return resolveNestedErr(remoteKptfileError, tmplArgs) + + return resolveNestedErr(remoteKptfileError, path) } var validateError *kptfile.ValidateError @@ -90,42 +57,58 @@ func (*pkgErrorResolver) Resolve(err error) (ResolvedResult, bool) { return ResolvedResult{}, false } -func resolveNestedErr(err error, tmplArgs map[string]interface{}) (ResolvedResult, bool) { +func resolveNestedErr(err error, path string) (ResolvedResult, bool) { if errors.Is(err, os.ErrNotExist) { + msg := fmt.Sprintf("Error: No Kptfile found at %q.", path) + return ResolvedResult{ - Message: ExecuteTemplate(noKptfileMsg, tmplArgs), + Message: msg, }, true } var deprecatedv1alpha1KptfileError *pkg.DeprecatedKptfileError if errors.As(err, &deprecatedv1alpha1KptfileError) && deprecatedv1alpha1KptfileError.Version == "v1alpha1" { - tmplArgs["version"] = deprecatedv1alpha1KptfileError.Version - errMsg := deprecatedv1Alpha1KptfileMsg + msg := fmt.Sprintf("Error: Kptfile at %q has an old version (%q) of the Kptfile schema.\n", path, deprecatedv1alpha1KptfileError.Version) + msg += "Please update the package to the latest format by following https://kpt.dev/installation/migration." + return ResolvedResult{ - Message: ExecuteTemplate(errMsg, tmplArgs), + Message: msg, }, true } var deprecatedv1alpha2KptfileError *pkg.DeprecatedKptfileError if errors.As(err, &deprecatedv1alpha2KptfileError) && - deprecatedv1alpha1KptfileError.Version == "v1alpha2" { - tmplArgs["version"] = deprecatedv1alpha2KptfileError.Version - errMsg := deprecatedv1Alpha2KptfileMsg + deprecatedv1alpha2KptfileError.Version == "v1alpha2" { + msg := fmt.Sprintf("Error: Kptfile at %q has an old version (%q) of the Kptfile schema.\n", path, deprecatedv1alpha2KptfileError.Version) + msg += "Please run \"kpt fn eval -i gcr.io/kpt-fn/fix:v0.2 --include-meta-resources\" to upgrade the package and retry." + return ResolvedResult{ - Message: ExecuteTemplate(errMsg, tmplArgs), + Message: msg, }, true } var unknownKptfileResourceError *pkg.UnknownKptfileResourceError if errors.As(err, &unknownKptfileResourceError) { - tmplArgs["gvk"] = unknownKptfileResourceError.GVK + msg := fmt.Sprintf("Error: Kptfile at %q has an unknown resource type (%q).", path, unknownKptfileResourceError.GVK.String()) return ResolvedResult{ - Message: ExecuteTemplate(unknownKptfileResourceMsg, tmplArgs), + Message: msg, }, true } + msg := fmt.Sprintf("Error: Kptfile at %q can't be read.", path) + if err != nil { + var kptFileError *pkg.KptfileError + if errors.As(err, &kptFileError) { + if kptFileError.Err != nil { + msg += fmt.Sprintf("\n\nDetails:\n%v", kptFileError.Err) + } + } else { + msg += fmt.Sprintf("\n\nDetails:\n%v", err) + } + } + return ResolvedResult{ - Message: ExecuteTemplate(kptfileReadErrMsg, tmplArgs), + Message: msg, }, true } diff --git a/internal/errors/resolver/template.go b/internal/errors/resolver/template.go deleted file mode 100644 index 7ad186e241..0000000000 --- a/internal/errors/resolver/template.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package resolver - -import ( - "bytes" - "fmt" - "strings" - "text/template" -) - -var baseTemplate = func() *template.Template { - tmpl := template.New("base") - tmpl = template.Must(tmpl.Parse(detailsHelperTemplate)) - tmpl = template.Must(tmpl.Parse(nestedErrTemplate)) - return tmpl -}() - -var ( - // detailsHelperTemplate is a helper subtemplate that is available to - // the top-level templates. It is useful when including information from - // execing other commands in the error message. - detailsHelperTemplate = ` -{{- define "ExecOutputDetails" }} -{{- if or (gt (len .stdout) 0) (gt (len .stderr) 0)}} -{{ printf "\nDetails:" }} -{{- end }} - -{{- if gt (len .stdout) 0 }} -{{ printf "%s" .stdout }} -{{- end }} - -{{- if gt (len .stderr) 0 }} -{{ printf "%s" .stderr }} -{{- end }} -{{ end }} -` - - // nestedErrTemplate is a helper subtemplate for printing details from - // a nested error. - nestedErrTemplate = ` -{{- define "NestedErrDetails" }} -{{- if .err }} -{{- if .err.Err }} -{{- if gt (len .err.Err.Error) 0 }} -{{ printf "\nDetails:" }} -{{ printf "%s" .err.Err.Error }} -{{- end }} -{{- end }} -{{- end }} -{{ end }} -` -) - -// ExecuteTemplate takes the provided template string and data, and renders -// the template. If something goes wrong, it panics. -func ExecuteTemplate(text string, data interface{}) string { - tmpl := template.Must(baseTemplate.Clone()) - template.Must(tmpl.Parse(text)) - - var b bytes.Buffer - execErr := tmpl.Execute(&b, data) - if execErr != nil { - panic(fmt.Errorf("error executing template: %w", execErr)) - } - return strings.TrimSpace(b.String()) -} diff --git a/internal/errors/resolver/update.go b/internal/errors/resolver/update.go index f5f1737497..6b43e3eca1 100644 --- a/internal/errors/resolver/update.go +++ b/internal/errors/resolver/update.go @@ -15,6 +15,8 @@ package resolver import ( + "fmt" + "github.com/GoogleContainerTools/kpt/internal/errors" "github.com/GoogleContainerTools/kpt/internal/util/update" ) @@ -24,17 +26,6 @@ func init() { AddErrorResolver(&updateErrorResolver{}) } -var ( - //nolint:lll - pkgNotGitRepo = ` -Package {{ printf "%q" .repo }} is not within a git repository. Please initialize a repository using 'git init' and then commit the changes using 'git commit -m ""'. -` - - pkgRepoDirty = ` -Package {{ printf "%q" .repo }} contains uncommitted changes. Please commit the changes using 'git commit -m ""'. -` -) - // updateErrorResolver is an implementation of the ErrorResolver interface // to resolve update errors. type updateErrorResolver struct{} @@ -44,16 +35,15 @@ func (*updateErrorResolver) Resolve(err error) (ResolvedResult, bool) { var pkgNotGitRepoError *update.PkgNotGitRepoError if errors.As(err, &pkgNotGitRepoError) { - msg = ExecuteTemplate(pkgNotGitRepo, map[string]interface{}{ - "repo": pkgNotGitRepoError.Path, - }) + //nolint:lll + msg = fmt.Sprintf("Package %q is not within a git repository.", pkgNotGitRepoError.Path) + msg += " Please initialize a repository using 'git init' and then commit the changes using 'git commit -m \"\"'." } var pkgRepoDirtyError *update.PkgRepoDirtyError if errors.As(err, &pkgRepoDirtyError) { - msg = ExecuteTemplate(pkgRepoDirty, map[string]interface{}{ - "repo": pkgRepoDirtyError.Path, - }) + msg = fmt.Sprintf("Package %q contains uncommitted changes.", pkgRepoDirtyError.Path) + msg += " Please commit the changes using 'git commit -m \"\"'." } if msg != "" { diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index 3e553fa7a4..7f9736df16 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -64,7 +64,7 @@ type KptfileError struct { } func (k *KptfileError) Error() string { - return fmt.Sprintf("error reading Kptfile at %q: %s", k.Path.String(), k.Err.Error()) + return fmt.Sprintf("error reading Kptfile at %q: %v", k.Path.String(), k.Err) } func (k *KptfileError) Unwrap() error {