From 0f4a47b18095fe0651947fde24cbb20f5141c880 Mon Sep 17 00:00:00 2001
From: Andres Martinez Gotor <andres.martinez@grafana.com>
Date: Thu, 18 Apr 2024 13:04:22 +0200
Subject: [PATCH] Plugin load errors: Add more well-known errors (#85960)

---
 packages/grafana-data/src/types/plugin.ts     |  2 ++
 .../manager/pipeline/initialization/steps.go  |  7 ++++--
 .../manager/pipeline/validation/steps.go      |  5 ++++-
 pkg/plugins/models.go                         | 22 ++++++++++++++++---
 .../pluginsintegration/pipeline/steps.go      |  1 +
 .../components/PluginDetailsDisabledError.tsx |  5 +++++
 6 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts
index 5c934d3b299..c006b72fe45 100644
--- a/packages/grafana-data/src/types/plugin.ts
+++ b/packages/grafana-data/src/types/plugin.ts
@@ -43,6 +43,8 @@ export enum PluginErrorCode {
   missingSignature = 'signatureMissing',
   invalidSignature = 'signatureInvalid',
   modifiedSignature = 'signatureModified',
+  failedBackendStart = 'failedBackendStart',
+  angular = 'angular',
 }
 
 /** Describes error returned from Grafana plugins API call */
diff --git a/pkg/plugins/manager/pipeline/initialization/steps.go b/pkg/plugins/manager/pipeline/initialization/steps.go
index c27cf667602..2df09d1144a 100644
--- a/pkg/plugins/manager/pipeline/initialization/steps.go
+++ b/pkg/plugins/manager/pipeline/initialization/steps.go
@@ -79,8 +79,11 @@ func newBackendProcessStarter(processManager process.Manager) *BackendClientStar
 // Start will start the backend plugin process.
 func (b *BackendClientStarter) Start(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
 	if err := b.processManager.Start(ctx, p); err != nil {
-		b.log.Error("Could not start plugin", "pluginId", p.ID, "error", err)
-		return nil, err
+		b.log.Error("Could not start plugin backend", "pluginId", p.ID, "error", err)
+		return nil, (&plugins.Error{
+			PluginID:  p.ID,
+			ErrorCode: plugins.ErrorCodeFailedBackendStart,
+		}).WithMessage(err.Error())
 	}
 	return p, nil
 }
diff --git a/pkg/plugins/manager/pipeline/validation/steps.go b/pkg/plugins/manager/pipeline/validation/steps.go
index 20dee23f780..10af0cce5e4 100644
--- a/pkg/plugins/manager/pipeline/validation/steps.go
+++ b/pkg/plugins/manager/pipeline/validation/steps.go
@@ -106,7 +106,10 @@ func (a *AngularDetector) Validate(ctx context.Context, p *plugins.Plugin) error
 		// Do not initialize plugins if they're using Angular and Angular support is disabled
 		if p.Angular.Detected && !a.cfg.AngularSupportEnabled {
 			a.log.Error("Refusing to initialize plugin because it's using Angular, which has been disabled", "pluginId", p.ID)
-			return errors.New("angular plugins are not supported")
+			return (&plugins.Error{
+				PluginID:  p.ID,
+				ErrorCode: plugins.ErrorAngular,
+			}).WithMessage("angular plugins are not supported")
 		}
 	}
 	p.Angular.HideDeprecation = slices.Contains(a.cfg.HideAngularDeprecation, p.ID)
diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go
index f9e263eefe4..0b76206fe08 100644
--- a/pkg/plugins/models.go
+++ b/pkg/plugins/models.go
@@ -235,9 +235,11 @@ type AppDTO struct {
 }
 
 const (
-	errorCodeSignatureMissing  ErrorCode = "signatureMissing"
-	errorCodeSignatureModified ErrorCode = "signatureModified"
-	errorCodeSignatureInvalid  ErrorCode = "signatureInvalid"
+	errorCodeSignatureMissing   ErrorCode = "signatureMissing"
+	errorCodeSignatureModified  ErrorCode = "signatureModified"
+	errorCodeSignatureInvalid   ErrorCode = "signatureInvalid"
+	ErrorCodeFailedBackendStart ErrorCode = "failedBackendStart"
+	ErrorAngular                ErrorCode = "angular"
 )
 
 type ErrorCode string
@@ -246,9 +248,14 @@ type Error struct {
 	ErrorCode       `json:"errorCode"`
 	PluginID        string          `json:"pluginId,omitempty"`
 	SignatureStatus SignatureStatus `json:"status,omitempty"`
+	message         string          `json:"-"`
 }
 
 func (e Error) Error() string {
+	if e.message != "" {
+		return e.message
+	}
+
 	if e.SignatureStatus != "" {
 		switch e.SignatureStatus {
 		case SignatureStatusInvalid:
@@ -266,6 +273,10 @@ func (e Error) Error() string {
 }
 
 func (e Error) AsErrorCode() ErrorCode {
+	if e.ErrorCode != "" {
+		return e.ErrorCode
+	}
+
 	switch e.SignatureStatus {
 	case SignatureStatusInvalid:
 		return errorCodeSignatureInvalid
@@ -280,6 +291,11 @@ func (e Error) AsErrorCode() ErrorCode {
 	return ""
 }
 
+func (e *Error) WithMessage(m string) *Error {
+	e.message = m
+	return e
+}
+
 // Access-Control related definitions
 
 // RoleRegistration stores a role and its assignments to basic roles
diff --git a/pkg/services/pluginsintegration/pipeline/steps.go b/pkg/services/pluginsintegration/pipeline/steps.go
index 85adcc6c60d..5401323d41b 100644
--- a/pkg/services/pluginsintegration/pipeline/steps.go
+++ b/pkg/services/pluginsintegration/pipeline/steps.go
@@ -89,6 +89,7 @@ func newRegisterPluginRoles(registry plugins.RoleRegistry) *RegisterPluginRoles
 func (r *RegisterPluginRoles) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
 	if err := r.roleRegistry.DeclarePluginRoles(ctx, p.ID, p.Name, p.Roles); err != nil {
 		r.log.Warn("Declare plugin roles failed.", "pluginId", p.ID, "error", err)
+		return nil, err
 	}
 	return p, nil
 }
diff --git a/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx b/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx
index 344e287f283..d7ca8094e17 100644
--- a/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx
+++ b/public/app/features/plugins/admin/components/PluginDetailsDisabledError.tsx
@@ -66,6 +66,11 @@ function renderDescriptionFromError(error?: PluginErrorCode): ReactElement {
           version of this plugin.
         </p>
       );
+    case PluginErrorCode.failedBackendStart:
+      return <p>This plugin failed to start. Server logs can provide more information.</p>;
+    case PluginErrorCode.angular:
+      // Error message already rendered by AngularDeprecationPluginNotice
+      return <></>;
     default:
       return (
         <p>