diff --git a/error/constants.go b/error/constants.go index 07e62f72a..d70986c23 100644 --- a/error/constants.go +++ b/error/constants.go @@ -1,33 +1,41 @@ package error -import "google.golang.org/grpc/codes" +import ( + "google.golang.org/grpc/codes" +) -// list of errors from helm +// list of error strings from Helm. These are part of the errors we check for presence in Helm's error messages. const ( YAMLToJSONConversionError = "error converting YAML to JSON" ClusterUnreachableErrorMsg = "cluster unreachable" - CrdPreconditionErrorMsg = "ensure CRDs are installed first" + CrdPreconditionErrorMsg = "ensure crds are installed first" ArrayStringMismatchErrorMsg = "got array expected string" - //NamespaceNotFoundErrorMsg = "not found" // todo - add more acurate error message, this is very generic - InvalidValueErrorMsg = "Invalid value" + NotFoundErrorMsg = "not found" //this is a generic type constant, an error could be namespace "ns1" not found or service "ser1" not found. + InvalidValueErrorMsg = "invalid value" OperationInProgressErrorMsg = "another operation (install/upgrade/rollback) is in progress" + ForbiddenErrorMsg = "forbidden" ) -// list of internal errors +// list of internal errors, these errors are easy for the users to understand const ( InternalClusterUnreachableErrorMsg = "cluster unreachable" - InternalCrdPreconditionErrorMsg = "ensure CRDs are installed first" - InternalArrayStringMismatchErrorMsg = "got array expected string" - InternalNamespaceNotFoundErrorMsg = "namespace not found" - InternalInvalidValueErrorMsg = "invalid value in manifest" InternalOperationInProgressErrorMsg = "another operation (install/upgrade/rollback) is in progress" ) -var helmErrorInternalErrorMap = map[string]map[string]codes.Code{ - ClusterUnreachableErrorMsg: {InternalClusterUnreachableErrorMsg: codes.DeadlineExceeded}, - CrdPreconditionErrorMsg: {InternalCrdPreconditionErrorMsg: codes.FailedPrecondition}, - //NamespaceNotFoundErrorMsg: {InternalNamespaceNotFoundErrorMsg: codes.Unknown}, - ArrayStringMismatchErrorMsg: {InternalArrayStringMismatchErrorMsg: codes.Unknown}, - InvalidValueErrorMsg: {InternalInvalidValueErrorMsg: codes.Unknown}, - OperationInProgressErrorMsg: {InternalOperationInProgressErrorMsg: codes.FailedPrecondition}, +type errorGrpcCodeTuple struct { + errorMsg string + grpcCode codes.Code +} + +var helmErrorInternalErrorMap = map[string]errorGrpcCodeTuple{ + ClusterUnreachableErrorMsg: {errorMsg: InternalClusterUnreachableErrorMsg, grpcCode: codes.DeadlineExceeded}, + OperationInProgressErrorMsg: {errorMsg: InternalOperationInProgressErrorMsg, grpcCode: codes.FailedPrecondition}, +} + +var DynamicErrorMapping = map[string]codes.Code{ + NotFoundErrorMsg: codes.NotFound, + ForbiddenErrorMsg: codes.PermissionDenied, + InvalidValueErrorMsg: codes.InvalidArgument, + ArrayStringMismatchErrorMsg: codes.InvalidArgument, + CrdPreconditionErrorMsg: codes.FailedPrecondition, } diff --git a/error/utils.go b/error/utils.go index 6b89b3bfe..279ef4ab6 100644 --- a/error/utils.go +++ b/error/utils.go @@ -5,13 +5,35 @@ import ( "strings" ) +// ConvertHelmErrorToInternalError converts known error message from helm to internal error and also maps it with proper grpc code func ConvertHelmErrorToInternalError(err error) error { + genericError := getInternalErrorForGenericErrorTypes(err) + if genericError != nil { + return genericError + } + var internalError error for helmErrMsg, internalErr := range helmErrorInternalErrorMap { if strings.Contains(err.Error(), helmErrMsg) { - for internalErrMsg, internalErrCode := range internalErr { - return status.New(internalErrCode, internalErrMsg).Err() - } + internalError = status.New(internalErr.grpcCode, internalErr.errorMsg).Err() + } + } + return internalError +} + +// getInternalErrorForGenericErrorTypes returns all those kinds of errors which are generic in nature and also dynamic, make sure to return all generic and dynamic errors from this func. instead of putting them in helmErrorInternalErrorMap +func getInternalErrorForGenericErrorTypes(err error) error { + /* + for example:- + 1. if namespace is not found err is:- namespace "ns1" not found, + 2. in case ingress class not found error is of type ingress class: IngressClass.networking.k8s.io "ingress1" not found, + 3. when some resource is forbidden then err can be of many formats one of which is:- Unable to continue with install: could not get information about the resource Ingress "prakash-1-prakash-env3-ingress" in namespace "prakash-ns3": ingresses.networking.k8s.io "prakash-1-prakash-env3-ingress" is forbidden... + etc.. + */ + for errorMsg, code := range DynamicErrorMapping { + if strings.Contains(strings.ToLower(err.Error()), errorMsg) { + return status.New(code, err.Error()).Err() } } + return nil } diff --git a/pkg/service/HelmAppService.go b/pkg/service/HelmAppService.go index 762f429c6..5843c509a 100644 --- a/pkg/service/HelmAppService.go +++ b/pkg/service/HelmAppService.go @@ -61,7 +61,6 @@ import ( const ( hibernateReplicaAnnotation = "hibernator.devtron.ai/replicas" hibernatePatch = `[{"op": "replace", "path": "/spec/replicas", "value":%d}, {"op": "add", "path": "/metadata/annotations", "value": {"%s":"%s"}}]` - chartWorkingDirectory = "/home/devtron/devtroncd/charts/" ReadmeFileName = "README.md" REGISTRY_TYPE_ECR = "ecr" REGISTRYTYPE_GCR = "gcr" @@ -138,7 +137,7 @@ func NewHelmAppServiceImpl(logger *zap.SugaredLogger, k8sService K8sService, clusterRepository: clusterRepository, converter: converter, } - err = os.MkdirAll(chartWorkingDirectory, os.ModePerm) + err = os.MkdirAll(helmReleaseConfig.ChartWorkingDirectory, os.ModePerm) if err != nil { helmAppServiceImpl.logger.Errorw("err in creating dir", "err", err) return nil, err @@ -285,10 +284,14 @@ func (impl HelmAppServiceImpl) getManifestsForExternalResources(restConfig *rest func (impl HelmAppServiceImpl) BuildAppDetail(req *client.AppDetailRequest) (*bean.AppDetail, error) { helmRelease, err := impl.getHelmRelease(req.ClusterConfig, req.Namespace, req.ReleaseName) if err != nil { + impl.logger.Errorw("Error in getting helm release ", "err", err) if errors.Is(err, driver.ErrReleaseNotFound) { return &bean.AppDetail{ReleaseExists: false}, err } - impl.logger.Errorw("Error in getting helm release ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } resourceTreeResponse, err := impl.buildResourceTree(req, helmRelease) @@ -390,6 +393,10 @@ func (impl *HelmAppServiceImpl) FetchApplicationStatus(req *client.AppDetailRequ helmRelease, err := impl.getHelmRelease(req.ClusterConfig, req.Namespace, req.ReleaseName) if err != nil { impl.logger.Errorw("Error in getting helm release ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return helmAppStatus, err } if helmRelease.Info != nil { @@ -413,6 +420,10 @@ func (impl HelmAppServiceImpl) GetHelmAppValues(req *client.AppDetailRequest) (* helmRelease, err := impl.getHelmRelease(req.ClusterConfig, req.Namespace, req.ReleaseName) if err != nil { impl.logger.Errorw("Error in getting helm release ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } @@ -540,6 +551,10 @@ func (impl HelmAppServiceImpl) GetDeploymentHistory(req *client.AppDetailRequest helmReleases, err := impl.getHelmReleaseHistory(req.ClusterConfig, req.Namespace, req.ReleaseName, impl.helmReleaseConfig.MaxCountForHelmRelease) if err != nil { impl.logger.Errorw("Error in getting helm release history ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } helmAppDeployments := make([]*client.HelmAppDeploymentDetail, 0, len(helmReleases)) @@ -579,6 +594,10 @@ func (impl HelmAppServiceImpl) GetDesiredManifest(req *client.ObjectRequest) (*c helmRelease, err := impl.getHelmRelease(req.ClusterConfig, req.ReleaseNamespace, req.ReleaseName) if err != nil { impl.logger.Errorw("Error in getting helm release ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } @@ -616,6 +635,10 @@ func (impl HelmAppServiceImpl) UninstallRelease(releaseIdentifier *client.Releas err = helmClient.UninstallReleaseByName(releaseIdentifier.ReleaseName) if err != nil { impl.logger.Errorw("Error in uninstall release ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } @@ -699,6 +722,10 @@ func (impl HelmAppServiceImpl) GetDeploymentDetail(request *client.DeploymentDet helmReleases, err := impl.getHelmReleaseHistory(releaseIdentifier.ClusterConfig, releaseIdentifier.ReleaseNamespace, releaseIdentifier.ReleaseName, impl.helmReleaseConfig.MaxCountForHelmRelease) if err != nil { impl.logger.Errorw("Error in getting helm release history ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } @@ -781,6 +808,10 @@ func (impl HelmAppServiceImpl) installRelease(ctx context.Context, request *clie err = helmClientObj.AddOrUpdateChartRepo(chartRepo) if err != nil { impl.logger.Errorw("Error in add/update chart repo ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } chartName = fmt.Sprintf("%s/%s", chartRepoName, request.ChartName) @@ -808,6 +839,10 @@ func (impl HelmAppServiceImpl) installRelease(ctx context.Context, request *clie rel, err := helmClientObj.InstallChart(context.Background(), chartSpec) if err != nil { impl.logger.Errorw("Error in install release ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } @@ -879,6 +914,10 @@ func (impl HelmAppServiceImpl) GetNotes(ctx context.Context, request *client.Ins release, err := helmClientObj.GetNotes(chartSpec, HelmTemplateOptions) if err != nil { impl.logger.Errorw("Error in fetching Notes ", "err", err) + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return "", err } if release == nil { @@ -1788,15 +1827,15 @@ func (impl HelmAppServiceImpl) InstallReleaseWithCustomChart(ctx context.Context return false, err } - if _, err := os.Stat(chartWorkingDirectory); os.IsNotExist(err) { - err := os.MkdirAll(chartWorkingDirectory, os.ModePerm) + if _, err := os.Stat(impl.helmReleaseConfig.ChartWorkingDirectory); os.IsNotExist(err) { + err := os.MkdirAll(impl.helmReleaseConfig.ChartWorkingDirectory, os.ModePerm) if err != nil { impl.logger.Errorw("err in creating dir", "err", err) return false, err } } dir := impl.GetRandomString() - referenceChartDir := filepath.Join(chartWorkingDirectory, dir) + referenceChartDir := filepath.Join(impl.helmReleaseConfig.ChartWorkingDirectory, dir) referenceChartDir = fmt.Sprintf("%s.tgz", referenceChartDir) defer impl.CleanDir(referenceChartDir) err = ioutil.WriteFile(referenceChartDir, b.Bytes(), os.ModePerm) @@ -1850,15 +1889,15 @@ func (impl HelmAppServiceImpl) UpgradeReleaseWithCustomChart(ctx context.Context return false, err } - if _, err := os.Stat(chartWorkingDirectory); os.IsNotExist(err) { - err := os.MkdirAll(chartWorkingDirectory, os.ModePerm) + if _, err := os.Stat(impl.helmReleaseConfig.ChartWorkingDirectory); os.IsNotExist(err) { + err := os.MkdirAll(impl.helmReleaseConfig.ChartWorkingDirectory, os.ModePerm) if err != nil { impl.logger.Errorw("err in creating dir", "err", err) return false, err } } dir := impl.GetRandomString() - referenceChartDir := filepath.Join(chartWorkingDirectory, dir) + referenceChartDir := filepath.Join(impl.helmReleaseConfig.ChartWorkingDirectory, dir) referenceChartDir = fmt.Sprintf("%s.tgz", referenceChartDir) defer impl.CleanDir(referenceChartDir) err = ioutil.WriteFile(referenceChartDir, b.Bytes(), os.ModePerm) diff --git a/pkg/service/K8sService.go b/pkg/service/K8sService.go index 55a4ba88c..801f351ba 100644 --- a/pkg/service/K8sService.go +++ b/pkg/service/K8sService.go @@ -8,6 +8,7 @@ import ( k8sUtils "github.com/devtron-labs/common-lib/utils/k8s" k8sCommonBean "github.com/devtron-labs/common-lib/utils/k8s/commonBean" "github.com/devtron-labs/kubelink/bean" + error2 "github.com/devtron-labs/kubelink/error" "go.uber.org/zap" coreV1 "k8s.io/api/core/v1" errors2 "k8s.io/apimachinery/pkg/api/errors" @@ -42,6 +43,7 @@ type HelmReleaseConfig struct { ManifestFetchBatchSize int `env:"MANIFEST_FETCH_BATCH_SIZE" envDefault:"2"` RunHelmInstallInAsyncMode bool `env:"RUN_HELM_INSTALL_IN_ASYNC_MODE" envDefault:"false"` ParentChildGvkMapping string `env:"PARENT_CHILD_GVK_MAPPING" envDefault:""` + ChartWorkingDirectory string `env:"CHART_WORKING_DIRECTORY" envDefault:"/home/devtron/devtroncd/charts/"` } func GetHelmReleaseConfig() (*HelmReleaseConfig, error) { @@ -159,6 +161,10 @@ func (impl K8sServiceImpl) GetChildObjects(restConfig *rest.Config, namespace st if err != nil { statusError, ok := err.(*errors2.StatusError) if !ok || statusError.ErrStatus.Reason != metav1.StatusReasonNotFound { + internalErr := error2.ConvertHelmErrorToInternalError(err) + if internalErr != nil { + err = internalErr + } return nil, err } }