| 
									
										
										
										
											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{ | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 			PreinstallPluginsAsync: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-09-30 19:48:27 +08:00
										 |  |  | 		pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 		&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 { | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 		name                 string | 
					
						
							|  |  |  | 		shouldInstall        bool | 
					
						
							|  |  |  | 		shouldThrowError     bool | 
					
						
							|  |  |  | 		pluginsToInstall     []setting.InstallPlugin | 
					
						
							|  |  |  | 		pluginsToInstallSync []setting.InstallPlugin | 
					
						
							|  |  |  | 		existingPlugins      []*plugins.Plugin | 
					
						
							|  |  |  | 		pluginsToFail        []string | 
					
						
							|  |  |  | 		latestPlugin         *repo.PluginArchiveInfo | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			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", | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 			shouldThrowError: false, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 			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"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 			name:                 "Install a plugin from sync list", | 
					
						
							|  |  |  | 			shouldInstall:        true, | 
					
						
							|  |  |  | 			pluginsToInstallSync: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 			name:                 "when installation fails in sync mode, it should throw an error", | 
					
						
							|  |  |  | 			shouldInstall:        false, | 
					
						
							|  |  |  | 			shouldThrowError:     true, | 
					
						
							|  |  |  | 			pluginsToInstallSync: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 			pluginsToFail:        []string{"myplugin"}, | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			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"}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:                 "should install all plugins - sync and async", | 
					
						
							|  |  |  | 			shouldInstall:        true, | 
					
						
							|  |  |  | 			pluginsToInstallSync: []setting.InstallPlugin{{ID: "myplugin"}}, | 
					
						
							|  |  |  | 			pluginsToInstall:     []setting.InstallPlugin{{ID: "myplugin2"}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2025-08-06 21:02:59 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:                 "should install a plugin with a URL regardless of versioning", | 
					
						
							|  |  |  | 			shouldInstall:        true, | 
					
						
							|  |  |  | 			pluginsToInstallSync: []setting.InstallPlugin{{ID: "our-plugin-datasource", URL: "https://s3.our.domain/grafana-plugins/our-plugin-datasource-1.2.1+linux.zip"}}, | 
					
						
							|  |  |  | 			existingPlugins:      []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "our-plugin-datasource", Info: plugins.Info{Version: "1.2.2"}}}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											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{ | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 					PreinstallPluginsAsync: tt.pluginsToInstall, | 
					
						
							|  |  |  | 					PreinstallPluginsSync:  tt.pluginsToInstallSync, | 
					
						
							| 
									
										
										
										
											2024-08-13 22:57:55 +08:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2025-09-30 19:48:27 +08:00
										 |  |  | 				pluginstore.New(preg, &fakes.FakeLoader{}), | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 				&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") | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 						allPluginsToInstall := append(tt.pluginsToInstallSync, tt.pluginsToInstall...) | 
					
						
							|  |  |  | 						for _, plugin := range allPluginsToInstall { | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 							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
										 |  |  | 			) | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 			if tt.shouldThrowError { | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 				require.ErrorContains(t, err, "Failed to install plugin") | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 				return | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			err = s.Run(context.Background()) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-11-07 20:14:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if tt.shouldInstall { | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 				expectedInstalled := 0 | 
					
						
							|  |  |  | 				expectedInstalledFromURL := 0 | 
					
						
							| 
									
										
										
										
											2025-05-16 22:45:50 +08:00
										 |  |  | 				allPluginsToInstall := append(tt.pluginsToInstallSync, tt.pluginsToInstall...) | 
					
						
							|  |  |  | 				for _, plugin := range allPluginsToInstall { | 
					
						
							| 
									
										
										
										
											2024-11-29 23:02:33 +08:00
										 |  |  | 					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
										 |  |  | } |