| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | package repo | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	"archive/zip" | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							|  |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/http/httptest" | 
					
						
							|  |  |  | 	"os" | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/log" | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	dummyPluginJSON = `{ "id": "grafana-test-datasource" }` | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | func TestGetPluginArchive(t *testing.T) { | 
					
						
							|  |  |  | 	tcs := []struct { | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 		name     string | 
					
						
							|  |  |  | 		sha      string | 
					
						
							|  |  |  | 		apiOpSys string | 
					
						
							|  |  |  | 		apiArch  string | 
					
						
							|  |  |  | 		apiUrl   string | 
					
						
							|  |  |  | 		err      error | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	}{ | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "Happy path", | 
					
						
							|  |  |  | 			sha:  "69f698961b6ea651211a187874434821c4727cc22de022e3a7059116d21c75b1", | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name: "Incorrect SHA returns error", | 
					
						
							|  |  |  | 			sha:  "1a2b3c", | 
					
						
							| 
									
										
										
										
											2023-11-30 22:49:27 +08:00
										 |  |  | 			err:  ErrChecksumMismatchBase, | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 		{ | 
					
						
							|  |  |  | 			name:     "Core plugin", | 
					
						
							|  |  |  | 			sha:      "69f698961b6ea651211a187874434821c4727cc22de022e3a7059116d21c75b1", | 
					
						
							|  |  |  | 			apiOpSys: "any", | 
					
						
							|  |  |  | 			apiUrl:   "https://github.com/grafana/grafana/tree/main/public/app/plugins/test", | 
					
						
							| 
									
										
										
										
											2023-11-30 22:49:27 +08:00
										 |  |  | 			err:      ErrCorePluginBase, | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 		}, | 
					
						
							|  |  |  | 		{ | 
					
						
							|  |  |  | 			name:   "Decoupled core plugin", | 
					
						
							|  |  |  | 			sha:    "69f698961b6ea651211a187874434821c4727cc22de022e3a7059116d21c75b1", | 
					
						
							|  |  |  | 			apiUrl: "https://github.com/grafana/grafana/tree/main/public/app/plugins/test", | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	pluginZip := createPluginArchive(t) | 
					
						
							|  |  |  | 	d, err := os.ReadFile(pluginZip.Name()) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		err = pluginZip.Close() | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		err = os.RemoveAll(pluginZip.Name()) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	for _, tc := range tcs { | 
					
						
							|  |  |  | 		t.Run(tc.name, func(t *testing.T) { | 
					
						
							|  |  |  | 			const ( | 
					
						
							|  |  |  | 				pluginID       = "grafana-test-datasource" | 
					
						
							|  |  |  | 				version        = "1.0.2" | 
					
						
							|  |  |  | 				opSys          = "darwin" | 
					
						
							|  |  |  | 				arch           = "amd64" | 
					
						
							|  |  |  | 				grafanaVersion = "10.0.0" | 
					
						
							|  |  |  | 			) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 			srvd := srvData{ | 
					
						
							|  |  |  | 				pluginID:       pluginID, | 
					
						
							|  |  |  | 				version:        version, | 
					
						
							|  |  |  | 				opSys:          tc.apiOpSys, | 
					
						
							|  |  |  | 				arch:           tc.apiArch, | 
					
						
							|  |  |  | 				url:            tc.apiUrl, | 
					
						
							|  |  |  | 				grafanaVersion: grafanaVersion, | 
					
						
							|  |  |  | 				sha:            tc.sha, | 
					
						
							|  |  |  | 				archive:        d, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if srvd.opSys == "" { | 
					
						
							|  |  |  | 				srvd.opSys = opSys | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if srvd.arch == "" && srvd.opSys != "any" { | 
					
						
							|  |  |  | 				srvd.arch = arch | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			srv := mockPluginVersionsAPI(t, srvd) | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 			t.Cleanup(srv.Close) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			m := NewManager(ManagerCfg{ | 
					
						
							|  |  |  | 				SkipTLSVerify: false, | 
					
						
							|  |  |  | 				BaseURL:       srv.URL, | 
					
						
							|  |  |  | 				Logger:        log.NewTestPrettyLogger(), | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			co := NewCompatOpts(grafanaVersion, opSys, arch) | 
					
						
							|  |  |  | 			archive, err := m.GetPluginArchive(context.Background(), pluginID, version, co) | 
					
						
							|  |  |  | 			if tc.err != nil { | 
					
						
							| 
									
										
										
										
											2023-11-30 22:49:27 +08:00
										 |  |  | 				require.ErrorIs(t, err, tc.err) | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			verifyArchive(t, archive) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func verifyArchive(t *testing.T, archive *PluginArchive) { | 
					
						
							|  |  |  | 	t.Helper() | 
					
						
							|  |  |  | 	require.NotNil(t, archive) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pJSON, err := archive.File.Open("plugin.json") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	defer func() { require.NoError(t, pJSON.Close()) }() | 
					
						
							|  |  |  | 	buf := new(bytes.Buffer) | 
					
						
							|  |  |  | 	_, err = buf.ReadFrom(pJSON) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	require.Equal(t, dummyPluginJSON, buf.String()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func createPluginArchive(t *testing.T) *os.File { | 
					
						
							|  |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pluginZip, err := os.CreateTemp(".", "test-plugin.zip") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	zipWriter := zip.NewWriter(pluginZip) | 
					
						
							|  |  |  | 	pJSON, err := zipWriter.Create("plugin.json") | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	_, err = pJSON.Write([]byte(dummyPluginJSON)) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 	err = zipWriter.Close() | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return pluginZip | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type srvData struct { | 
					
						
							|  |  |  | 	pluginID       string | 
					
						
							|  |  |  | 	version        string | 
					
						
							|  |  |  | 	opSys          string | 
					
						
							|  |  |  | 	arch           string | 
					
						
							|  |  |  | 	sha            string | 
					
						
							|  |  |  | 	grafanaVersion string | 
					
						
							|  |  |  | 	archive        []byte | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 	url            string | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server { | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	t.Helper() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mux := http.NewServeMux() | 
					
						
							|  |  |  | 	// mock plugin version data
 | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 	mux.HandleFunc(fmt.Sprintf("/%s/versions", data.pluginID), func(w http.ResponseWriter, r *http.Request) { | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 		require.Equal(t, data.grafanaVersion, r.Header.Get("grafana-version")) | 
					
						
							|  |  |  | 		require.NotNil(t, fmt.Sprintf("grafana %s", data.grafanaVersion), r.Header.Get("User-Agent")) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  | 		w.Header().Set("Content-Type", "application/json") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 		platform := data.opSys | 
					
						
							|  |  |  | 		if data.arch != "" { | 
					
						
							|  |  |  | 			platform += "-" + data.arch | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 		_, _ = w.Write([]byte(fmt.Sprintf(` | 
					
						
							|  |  |  | 				{ | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 					"items": [{ | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 						"version": "%s", | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 						"packages": { | 
					
						
							|  |  |  | 							"%s": { | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 								"sha256": "%s" | 
					
						
							|  |  |  | 							} | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 						}, | 
					
						
							| 
									
										
										
										
											2024-08-22 19:47:00 +08:00
										 |  |  | 						"url": "%s", | 
					
						
							|  |  |  | 						"isCompatible": true | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 					}] | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2023-11-21 19:46:26 +08:00
										 |  |  | 			`, data.version, platform, data.sha, data.url), | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 		)) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	// mock plugin archive
 | 
					
						
							|  |  |  | 	mux.HandleFunc(fmt.Sprintf("/%s/versions/%s/download", data.pluginID, data.version), func(w http.ResponseWriter, r *http.Request) { | 
					
						
							|  |  |  | 		w.WriteHeader(http.StatusOK) | 
					
						
							|  |  |  | 		w.Header().Set("Content-Type", "application/zip") | 
					
						
							|  |  |  | 		_, _ = w.Write(data.archive) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return httptest.NewServer(mux) | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type versionArg struct { | 
					
						
							| 
									
										
										
										
											2024-08-22 19:47:00 +08:00
										 |  |  | 	version      string | 
					
						
							|  |  |  | 	arch         []string | 
					
						
							|  |  |  | 	isCompatible *bool | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | func createPluginVersions(versions ...versionArg) []Version { | 
					
						
							| 
									
										
										
										
											2024-06-15 02:16:36 +08:00
										 |  |  | 	vs := make([]Version, len(versions)) | 
					
						
							|  |  |  | 	for i, version := range versions { | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 		ver := Version{ | 
					
						
							| 
									
										
										
										
											2024-08-22 19:47:00 +08:00
										 |  |  | 			Version:      version.version, | 
					
						
							|  |  |  | 			IsCompatible: version.isCompatible, | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if version.arch != nil { | 
					
						
							|  |  |  | 			ver.Arch = map[string]ArchMeta{} | 
					
						
							|  |  |  | 			for _, arch := range version.arch { | 
					
						
							|  |  |  | 				ver.Arch[arch] = ArchMeta{ | 
					
						
							|  |  |  | 					SHA256: fmt.Sprintf("sha256_%s", arch), | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-06-15 02:16:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		vs[i] = ver | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	return vs | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | } |