Plugins: Angular detector: Remote patterns fetching (#69843)

* Plugins: Angular detector: Remote patterns fetching

* Renamed PatternType to GCOMPatternType

* Renamed files

* Renamed more files

* Moved files again

* Add type checks, unexport GCOM structs

* Cache failures, update log messages, fix GCOM URL

* Fail silently for unknown pattern types, update docstrings

* Fix tests

* Rename gcomPattern.Value to gcomPattern.Pattern

* Refactoring

* Add FlagPluginsRemoteAngularDetectionPatterns feature flag

* Fix tests

* Re-generate feature flags

* Add TestProvideInspector, renamed TestDefaultStaticDetectorsInspector

* Add TestProvideInspector

* Add TestContainsBytesDetector and TestRegexDetector

* Renamed getter to provider

* More tests

* TestStaticDetectorsProvider, TestSequenceDetectorsProvider

* GCOM tests

* Lint

* Made detector.detect unexported, updated docstrings

* Allow changing grafana.com URL

* Fix API path, add more logs

* Update tryUpdateRemoteDetectors docstring

* Use angulardetector http client

* Return false, nil if module.js does not exist

* Chore: Split angualrdetector into angularinspector and angulardetector packages

Moved files around, changed references and fixed tests:
- Split the old angulardetector package into angular/angulardetector and angular/angularinspector
- angulardetector provides the detection structs/interfaces (Detector, DetectorsProvider...)
- angularinspector provides the actual angular detection service used directly in pluginsintegration
- Exported most of the stuff that was private and now put into angulardetector, as it is not required by angularinspector

* Renamed detector.go -> angulardetector.go and inspector.go -> angularinspector.go

Forgot to rename those two files to match the package's names

* Renamed angularinspector.ProvideInspector to angularinspector.ProvideService

* Renamed "harcoded" to "static" and "remote" to "dynamic"

from PR review, matches the same naming schema used for signing keys fetching

* Fix merge conflict on updated angular patterns

* Removed GCOM cache

* Renamed Detect to DetectAngular and Detector to AngularDetector

* Fix call to NewGCOMDetectorsProvider in newDynamicInspector

* Removed unused test function newError500GCOMScenario

* Added angularinspector service definition in pluginsintegration

* Moved dynamic inspector into pluginsintegration

* Move gcom angulardetectorsprovider into pluginsintegration

* Log errUnknownPatternType at debug level

* re-generate feature flags

* fix error log
This commit is contained in:
Giuseppe Guerra
2023-06-26 15:33:21 +02:00
committed by GitHub
parent 903af7e29c
commit cca9d89733
25 changed files with 949 additions and 282 deletions
@@ -0,0 +1,69 @@
package angulardetector
import (
"bytes"
"context"
"regexp"
)
var (
_ AngularDetector = &ContainsBytesDetector{}
_ AngularDetector = &RegexDetector{}
_ DetectorsProvider = &StaticDetectorsProvider{}
_ DetectorsProvider = SequenceDetectorsProvider{}
)
// AngularDetector implements a check to see if a js file is using angular APIs.
type AngularDetector interface {
// DetectAngular takes the content of a js file and returns true if the plugin is using Angular.
DetectAngular(js []byte) bool
}
// ContainsBytesDetector is an AngularDetector that returns true if module.js contains the "pattern" string.
type ContainsBytesDetector struct {
Pattern []byte
}
// DetectAngular returns true if moduleJs contains the byte slice d.pattern.
func (d *ContainsBytesDetector) DetectAngular(moduleJs []byte) bool {
return bytes.Contains(moduleJs, d.Pattern)
}
// RegexDetector is an AngularDetector that returns true if the module.js content matches a regular expression.
type RegexDetector struct {
Regex *regexp.Regexp
}
// DetectAngular returns true if moduleJs matches the regular expression d.regex.
func (d *RegexDetector) DetectAngular(moduleJs []byte) bool {
return d.Regex.Match(moduleJs)
}
// DetectorsProvider can provide multiple AngularDetectors used for Angular detection.
type DetectorsProvider interface {
// ProvideDetectors returns a slice of AngularDetector.
ProvideDetectors(ctx context.Context) []AngularDetector
}
// StaticDetectorsProvider is a DetectorsProvider that always returns a pre-defined slice of AngularDetector.
type StaticDetectorsProvider struct {
Detectors []AngularDetector
}
func (p *StaticDetectorsProvider) ProvideDetectors(_ context.Context) []AngularDetector {
return p.Detectors
}
// SequenceDetectorsProvider is a DetectorsProvider that wraps a slice of other DetectorsProvider, and returns the first
// provided result that isn't empty.
type SequenceDetectorsProvider []DetectorsProvider
func (p SequenceDetectorsProvider) ProvideDetectors(ctx context.Context) []AngularDetector {
for _, provider := range p {
if detectors := provider.ProvideDetectors(ctx); len(detectors) > 0 {
return detectors
}
}
return nil
}
@@ -0,0 +1,120 @@
package angulardetector
import (
"context"
"regexp"
"testing"
"github.com/stretchr/testify/require"
)
var testDetectors = []AngularDetector{
&ContainsBytesDetector{Pattern: []byte("PanelCtrl")},
&ContainsBytesDetector{Pattern: []byte("QueryCtrl")},
}
func TestContainsBytesDetector(t *testing.T) {
detector := &ContainsBytesDetector{Pattern: []byte("needle")}
t.Run("contains", func(t *testing.T) {
require.True(t, detector.DetectAngular([]byte("lorem needle ipsum haystack")))
})
t.Run("not contains", func(t *testing.T) {
require.False(t, detector.DetectAngular([]byte("ippif")))
})
}
func TestRegexDetector(t *testing.T) {
detector := &RegexDetector{Regex: regexp.MustCompile("hello world(?s)")}
for _, tc := range []struct {
name string
s string
exp bool
}{
{name: "match 1", s: "hello world", exp: true},
{name: "match 2", s: "bla bla hello world bla bla", exp: true},
{name: "match 3", s: "bla bla hello worlds bla bla", exp: true},
{name: "no match", s: "bla bla hello you reading this test code", exp: false},
} {
t.Run(tc.s, func(t *testing.T) {
r := detector.DetectAngular([]byte(tc.s))
require.Equal(t, tc.exp, r, "DetectAngular result should be correct")
})
}
}
func TestStaticDetectorsProvider(t *testing.T) {
p := StaticDetectorsProvider{Detectors: testDetectors}
detectors := p.ProvideDetectors(context.Background())
require.NotEmpty(t, detectors)
require.Equal(t, testDetectors, detectors)
}
type fakeDetectorsProvider struct {
calls int
returns []AngularDetector
}
func (p *fakeDetectorsProvider) ProvideDetectors(_ context.Context) []AngularDetector {
p.calls += 1
return p.returns
}
func TestSequenceDetectorsProvider(t *testing.T) {
for _, tc := range []struct {
name string
fakeProviders []*fakeDetectorsProvider
exp func(t *testing.T, fakeProviders []*fakeDetectorsProvider, detectors []AngularDetector)
}{
{
name: "returns first non-empty provided angularDetectors (first)",
fakeProviders: []*fakeDetectorsProvider{
{returns: testDetectors},
{returns: nil},
},
exp: func(t *testing.T, fakeProviders []*fakeDetectorsProvider, detectors []AngularDetector) {
require.NotEmpty(t, detectors)
require.Len(t, detectors, len(fakeProviders[0].returns))
require.Equal(t, fakeProviders[0].returns, detectors)
require.Equal(t, 1, fakeProviders[0].calls, "fake provider 0 should be called")
require.Zero(t, fakeProviders[1].calls, "fake provider 1 should not be called")
},
},
{
name: "returns first non-empty provided angularDetectors (second)",
fakeProviders: []*fakeDetectorsProvider{
{returns: nil},
{returns: testDetectors},
},
exp: func(t *testing.T, fakeProviders []*fakeDetectorsProvider, detectors []AngularDetector) {
require.NotEmpty(t, detectors)
require.Len(t, detectors, len(fakeProviders[1].returns))
require.Equal(t, fakeProviders[1].returns, detectors)
for i, p := range fakeProviders {
require.Equalf(t, 1, p.calls, "fake provider %d should be called", i)
}
},
},
{
name: "returns nil if all providers return empty",
fakeProviders: []*fakeDetectorsProvider{
{returns: nil},
{returns: []AngularDetector{}},
},
exp: func(t *testing.T, fakeProviders []*fakeDetectorsProvider, detectors []AngularDetector) {
require.Empty(t, detectors, "should not return any angularDetectors")
for i, p := range fakeProviders {
require.Equalf(t, 1, p.calls, "fake provider %d should be called", i)
}
},
},
} {
t.Run(tc.name, func(t *testing.T) {
seq := make(SequenceDetectorsProvider, 0, len(tc.fakeProviders))
for _, p := range tc.fakeProviders {
seq = append(seq, DetectorsProvider(p))
}
detectors := seq.ProvideDetectors(context.Background())
tc.exp(t, tc.fakeProviders, detectors)
})
}
}