| 
									
										
										
										
											2024-08-12 22:39:31 +08:00
										 |  |  | package plugininstaller | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2024-08-12 22:39:31 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/plugins/manager/fakes" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/plugins/manager/registry" | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/repo" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/featuremgmt" | 
					
						
							| 
									
										
										
										
											2025-05-09 21:58:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker" | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" | 
					
						
							| 
									
										
										
										
											2025-05-09 21:58:04 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins" | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2024-08-22 21:17:27 +08:00
										 |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							| 
									
										
										
										
											2024-08-12 22:39:31 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Test if the service is disabled
 | 
					
						
							|  |  |  | func TestService_IsDisabled(t *testing.T) { | 
					
						
							|  |  |  | 	// Create a new service
 | 
					
						
							| 
									
										
										
										
											2024-08-21 22:11:55 +08:00
										 |  |  | 	s, err := ProvideService( | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 		&setting.Cfg{ | 
					
						
							| 
									
										
										
										
											2024-08-21 22:11:55 +08:00
										 |  |  | 			PreinstallPlugins:      []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 			PreinstallPluginsAsync: true, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), | 
					
						
							|  |  |  | 		&fakes.FakePluginInstaller{}, | 
					
						
							| 
									
										
										
										
											2024-08-22 21:17:27 +08:00
										 |  |  | 		prometheus.NewRegistry(), | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 		&fakes.FakePluginRepo{}, | 
					
						
							|  |  |  | 		featuremgmt.WithFeatures(), | 
					
						
							| 
									
										
										
										
											2025-05-09 21:58:04 +08:00
										 |  |  | 		&pluginchecker.FakePluginUpdateChecker{}, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 	) | 
					
						
							| 
									
										
										
										
											2024-08-21 22:11:55 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-08-12 22:39:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Check if the service is disabled
 | 
					
						
							|  |  |  | 	if s.IsDisabled() { | 
					
						
							|  |  |  | 		t.Error("Service should be enabled") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func TestService_Run(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 	tests := []struct { | 
					
						
							|  |  |  | 		name             string | 
					
						
							|  |  |  | 		shouldInstall    bool | 
					
						
							|  |  |  | 		pluginsToInstall []setting.InstallPlugin | 
					
						
							|  |  |  | 		existingPlugins  []*plugins.Plugin | 
					
						
							|  |  |  | 		pluginsToFail    []string | 
					
						
							|  |  |  | 		blocking         bool | 
					
						
							|  |  |  | 		latestPlugin     *repo.PluginArchiveInfo | 
					
						
							|  |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Installs a plugin", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Install a plugin with version", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: "1.0.0"}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Skips already installed plugin", | 
					
						
							|  |  |  | 			shouldInstall:    false, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin"}}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Still installs a plugin if the plugin version does not match", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: "2.0.0"}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Install multiple plugins", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Fails to install a plugin but install the rest", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}}, | 
					
						
							|  |  |  | 			pluginsToFail:    []string{"myplugin1"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Install a blocking plugin", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 			blocking:         true, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Fails to install a blocking plugin", | 
					
						
							|  |  |  | 			shouldInstall:    false, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 			blocking:         true, | 
					
						
							|  |  |  | 			pluginsToFail:    []string{"myplugin"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Updates a plugin", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}}, | 
					
						
							|  |  |  | 			latestPlugin:     &repo.PluginArchiveInfo{Version: "1.0.1"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Should not update a plugin if the latest version is installed", | 
					
						
							|  |  |  | 			shouldInstall:    false, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}}, | 
					
						
							|  |  |  | 			latestPlugin:     &repo.PluginArchiveInfo{Version: "1.0.0"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Should not update a plugin if the latest version is a major version", | 
					
						
							|  |  |  | 			shouldInstall:    false, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}}, | 
					
						
							|  |  |  | 			latestPlugin:     &repo.PluginArchiveInfo{Version: "2.0.0"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Should install a plugin with a URL", | 
					
						
							|  |  |  | 			shouldInstall:    true, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", URL: "https://example.com/myplugin.tar.gz"}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-05-05 20:51:35 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Should not update a plugin if the current version is greater than the latest version", | 
					
						
							|  |  |  | 			shouldInstall:    false, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.1"}}}}, | 
					
						
							|  |  |  | 			latestPlugin:     &repo.PluginArchiveInfo{Version: "1.0.0"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:             "Should not update a plugin if the current version is equal to the latest version, ignoring the prerelease", | 
					
						
							|  |  |  | 			shouldInstall:    false, | 
					
						
							|  |  |  | 			pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}}, | 
					
						
							|  |  |  | 			existingPlugins:  []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}}, | 
					
						
							|  |  |  | 			latestPlugin:     &repo.PluginArchiveInfo{Version: "1.0.0-rc.1"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	for _, tt := range tests { | 
					
						
							|  |  |  | 		t.Run(tt.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			preg := registry.NewInMemory() | 
					
						
							|  |  |  | 			for _, plugin := range tt.existingPlugins { | 
					
						
							|  |  |  | 				err := preg.Add(context.Background(), plugin) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			installed := 0 | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 			installedFromURL := 0 | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 			s, err := ProvideService( | 
					
						
							|  |  |  | 				&setting.Cfg{ | 
					
						
							|  |  |  | 					PreinstallPlugins:      tt.pluginsToInstall, | 
					
						
							|  |  |  | 					PreinstallPluginsAsync: !tt.blocking, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 				pluginstore.New(preg, &fakes.FakeLoader{}), | 
					
						
							|  |  |  | 				&fakes.FakePluginInstaller{ | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 					AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.AddOpts) error { | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 						for _, plugin := range tt.pluginsToFail { | 
					
						
							|  |  |  | 							if plugin == pluginID { | 
					
						
							|  |  |  | 								return errors.New("Failed to install plugin") | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						if !tt.shouldInstall { | 
					
						
							|  |  |  | 							t.Fatal("Should not install plugin") | 
					
						
							|  |  |  | 							return errors.New("Should not install plugin") | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						for _, plugin := range tt.pluginsToInstall { | 
					
						
							|  |  |  | 							if plugin.ID == pluginID && plugin.Version == version { | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 								if opts.URL() != "" { | 
					
						
							|  |  |  | 									installedFromURL++ | 
					
						
							|  |  |  | 								} else { | 
					
						
							|  |  |  | 									installed++ | 
					
						
							|  |  |  | 								} | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 							} | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						return nil | 
					
						
							|  |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 				prometheus.NewRegistry(), | 
					
						
							|  |  |  | 				&fakes.FakePluginRepo{ | 
					
						
							|  |  |  | 					GetPluginArchiveInfoFunc: func(_ context.Context, pluginID, version string, _ repo.CompatOpts) (*repo.PluginArchiveInfo, error) { | 
					
						
							|  |  |  | 						return tt.latestPlugin, nil | 
					
						
							|  |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 				featuremgmt.WithFeatures(featuremgmt.FlagPreinstallAutoUpdate), | 
					
						
							| 
									
										
										
										
											2025-05-09 21:58:04 +08:00
										 |  |  | 				pluginchecker.ProvideService( | 
					
						
							|  |  |  | 					managedplugins.NewNoop(), | 
					
						
							|  |  |  | 					provisionedplugins.NewNoop(), | 
					
						
							|  |  |  | 					&pluginchecker.FakePluginPreinstall{}, | 
					
						
							|  |  |  | 				), | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 			) | 
					
						
							|  |  |  | 			if tt.blocking && !tt.shouldInstall { | 
					
						
							|  |  |  | 				require.ErrorContains(t, err, "Failed to install plugin") | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if !tt.blocking { | 
					
						
							|  |  |  | 				err = s.Run(context.Background()) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if tt.shouldInstall { | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 				expectedInstalled := 0 | 
					
						
							|  |  |  | 				expectedInstalledFromURL := 0 | 
					
						
							|  |  |  | 				for _, plugin := range tt.pluginsToInstall { | 
					
						
							|  |  |  | 					expectedFailed := false | 
					
						
							|  |  |  | 					for _, pluginFail := range tt.pluginsToFail { | 
					
						
							|  |  |  | 						if plugin.ID == pluginFail { | 
					
						
							|  |  |  | 							expectedFailed = true | 
					
						
							|  |  |  | 							break | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if expectedFailed { | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					if plugin.URL != "" { | 
					
						
							|  |  |  | 						expectedInstalledFromURL++ | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						expectedInstalled++ | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				require.Equal(t, expectedInstalled, installed) | 
					
						
							|  |  |  | 				require.Equal(t, expectedInstalledFromURL, installedFromURL) | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | } |