mirror of https://github.com/grafana/grafana
Plugins: Angular deprecation: Detect Angular plugins and expose in API (#66824)
* Plugins: Angular deprecation: Detect Angular plugins and expose in API * Plugins: Angular detector: Close module.js * Plugins: Angular detector: consistent error messages * Plugins: Angular detector: Add test for missing module.js * Plugins: Angular detector: Fix integration tests * Plugins: Angular detector: Changed Angular detection patterns * Moved inMemoryFS to test_utils.go * Add different angular detectors * Plugins: Update plugins/data/expectedListResp.json * Plugins: Rename angular property to angularDetected * Plugins: Rename angular to angularDetected in Plugin and PluginDTO * Plugins: Add angularDetected to datasources, apps and plugins frontendsettings * Plugins: Add test for AngularDetected frontend settingspull/68364/head
parent
4310f574db
commit
16359c82a2
@ -0,0 +1,82 @@ |
||||
package angulardetector |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"io" |
||||
"regexp" |
||||
|
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
) |
||||
|
||||
var ( |
||||
_ detector = &containsBytesDetector{} |
||||
_ detector = ®exDetector{} |
||||
) |
||||
|
||||
// detector implements a check to see if a plugin uses Angular.
|
||||
type detector interface { |
||||
// Detect takes the content of a moduleJs file and returns true if the plugin is using Angular.
|
||||
Detect(moduleJs []byte) bool |
||||
} |
||||
|
||||
// containsBytesDetector is a detector that returns true if module.js contains the "pattern" string.
|
||||
type containsBytesDetector struct { |
||||
pattern []byte |
||||
} |
||||
|
||||
// Detect returns true if moduleJs contains the byte slice d.pattern.
|
||||
func (d *containsBytesDetector) Detect(moduleJs []byte) bool { |
||||
return bytes.Contains(moduleJs, d.pattern) |
||||
} |
||||
|
||||
// regexDetector is a detector that returns true if the module.js content matches a regular expression.
|
||||
type regexDetector struct { |
||||
regex *regexp.Regexp |
||||
} |
||||
|
||||
// Detect returns true if moduleJs matches the regular expression d.regex.
|
||||
func (d *regexDetector) Detect(moduleJs []byte) bool { |
||||
return d.regex.Match(moduleJs) |
||||
} |
||||
|
||||
// angularDetectors contains all the detectors to detect Angular plugins.
|
||||
// They are executed in the specified order.
|
||||
var angularDetectors = []detector{ |
||||
&containsBytesDetector{pattern: []byte("PanelCtrl")}, |
||||
&containsBytesDetector{pattern: []byte("QueryCtrl")}, |
||||
&containsBytesDetector{pattern: []byte("app/plugins/sdk")}, |
||||
&containsBytesDetector{pattern: []byte("angular.isNumber(")}, |
||||
&containsBytesDetector{pattern: []byte("editor.html")}, |
||||
&containsBytesDetector{pattern: []byte("ctrl.annotation")}, |
||||
&containsBytesDetector{pattern: []byte("getLegacyAngularInjector")}, |
||||
|
||||
®exDetector{regex: regexp.MustCompile(`['"](app/core/utils/promiseToDigest)|(app/plugins/.*?)|(app/core/core_module)['"]`)}, |
||||
®exDetector{regex: regexp.MustCompile(`from\s+['"]grafana\/app\/`)}, |
||||
®exDetector{regex: regexp.MustCompile(`System\.register\(`)}, |
||||
} |
||||
|
||||
// Inspect open module.js and checks if the plugin is using Angular by matching against its source code.
|
||||
// It returns true if module.js matches against any of the detectors in angularDetectors.
|
||||
func Inspect(p *plugins.Plugin) (isAngular bool, err error) { |
||||
f, err := p.FS.Open("module.js") |
||||
if err != nil { |
||||
return false, fmt.Errorf("open module.js: %w", err) |
||||
} |
||||
defer func() { |
||||
if closeErr := f.Close(); closeErr != nil && err == nil { |
||||
err = fmt.Errorf("close module.js: %w", closeErr) |
||||
} |
||||
}() |
||||
b, err := io.ReadAll(f) |
||||
if err != nil { |
||||
return false, fmt.Errorf("module.js readall: %w", err) |
||||
} |
||||
for _, d := range angularDetectors { |
||||
if d.Detect(b) { |
||||
isAngular = true |
||||
break |
||||
} |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,60 @@ |
||||
package angulardetector |
||||
|
||||
import ( |
||||
"strconv" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/plugins" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestAngularDetector_Inspect(t *testing.T) { |
||||
type tc struct { |
||||
name string |
||||
plugin *plugins.Plugin |
||||
exp bool |
||||
} |
||||
var tcs []tc |
||||
|
||||
// Angular imports
|
||||
for i, content := range [][]byte{ |
||||
[]byte(`import { MetricsPanelCtrl } from 'grafana/app/plugins/sdk';`), |
||||
[]byte(`define(["app/plugins/sdk"],(function(n){return function(n){var t={};function e(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof`), |
||||
[]byte(`define(["app/plugins/sdk"],(function(n){return function(n){var t={};function e(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}return e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof Symbol&&Symbol.toSt`), |
||||
[]byte(`define(["react","lodash","@grafana/data","@grafana/ui","@emotion/css","@grafana/runtime","moment","app/core/utils/datemath","jquery","app/plugins/sdk","app/core/core_module","app/core/core","app/core/table_model","app/core/utils/kbn","app/core/config","angular"],(function(e,t,r,n,i,a,o,s,u,l,c,p,f,h,d,m){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};retur`), |
||||
} { |
||||
tcs = append(tcs, tc{ |
||||
name: "angular " + strconv.Itoa(i), |
||||
plugin: &plugins.Plugin{ |
||||
FS: plugins.NewInMemoryFS(map[string][]byte{ |
||||
"module.js": content, |
||||
}), |
||||
}, |
||||
exp: true, |
||||
}) |
||||
} |
||||
|
||||
// Not angular
|
||||
tcs = append(tcs, tc{ |
||||
name: "not angular", |
||||
plugin: &plugins.Plugin{ |
||||
FS: plugins.NewInMemoryFS(map[string][]byte{ |
||||
"module.js": []byte(`import { PanelPlugin } from '@grafana/data'`), |
||||
}), |
||||
}, |
||||
exp: false, |
||||
}) |
||||
for _, tc := range tcs { |
||||
t.Run(tc.name, func(t *testing.T) { |
||||
isAngular, err := Inspect(tc.plugin) |
||||
require.NoError(t, err) |
||||
require.Equal(t, tc.exp, isAngular) |
||||
}) |
||||
} |
||||
|
||||
t.Run("no module.js", func(t *testing.T) { |
||||
p := &plugins.Plugin{FS: plugins.NewInMemoryFS(map[string][]byte{})} |
||||
_, err := Inspect(p) |
||||
require.ErrorIs(t, err, plugins.ErrFileNotExist) |
||||
}) |
||||
} |
Loading…
Reference in new issue