* 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
158 lines
5.1 KiB
Go
158 lines
5.1 KiB
Go
package angulardetector
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins/log"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/angular/angulardetector"
|
|
)
|
|
|
|
const (
|
|
// gcomAngularPatternsPath is the relative path to the GCOM API handler that returns angular detection patterns.
|
|
gcomAngularPatternsPath = "/api/plugins/angular_patterns"
|
|
)
|
|
|
|
var _ angulardetector.DetectorsProvider = &GCOMDetectorsProvider{}
|
|
|
|
// GCOMDetectorsProvider is a DetectorsProvider which fetches patterns from GCOM.
|
|
type GCOMDetectorsProvider struct {
|
|
log log.Logger
|
|
|
|
httpClient *http.Client
|
|
|
|
baseURL string
|
|
}
|
|
|
|
// NewGCOMDetectorsProvider returns a new GCOMDetectorsProvider.
|
|
// baseURL is the GCOM base url, without /api and without a trailing slash (e.g.: https://grafana.com)
|
|
func NewGCOMDetectorsProvider(baseURL string) (angulardetector.DetectorsProvider, error) {
|
|
cl, err := httpclient.New()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("httpclient new: %w", err)
|
|
}
|
|
return &GCOMDetectorsProvider{
|
|
log: log.New("plugins.angulardetector.gcom"),
|
|
baseURL: baseURL,
|
|
httpClient: cl,
|
|
}, nil
|
|
}
|
|
|
|
// ProvideDetectors gets the dynamic angular detectors from the remote source.
|
|
// If an error occurs, the function fails silently by logging an error, and it returns nil.
|
|
func (p *GCOMDetectorsProvider) ProvideDetectors(ctx context.Context) []angulardetector.AngularDetector {
|
|
patterns, err := p.fetch(ctx)
|
|
if err != nil {
|
|
p.log.Warn("Could not fetch remote angular patterns", "error", err)
|
|
return nil
|
|
}
|
|
detectors, err := p.patternsToDetectors(patterns)
|
|
if err != nil {
|
|
p.log.Warn("Could not convert angular patterns to angularDetectors", "error", err)
|
|
return nil
|
|
}
|
|
return detectors
|
|
}
|
|
|
|
// fetch fetches the angular patterns from GCOM and returns them as gcomPatterns.
|
|
// Call angularDetectors() on the returned value to get the corresponding angular detectors.
|
|
func (p *GCOMDetectorsProvider) fetch(ctx context.Context) (gcomPatterns, error) {
|
|
st := time.Now()
|
|
|
|
reqURL, err := url.JoinPath(p.baseURL, gcomAngularPatternsPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("url joinpath: %w", err)
|
|
}
|
|
|
|
p.log.Debug("Fetching dynamic angular detection patterns", "url", reqURL)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("new request with context: %w", err)
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("http do: %w", err)
|
|
}
|
|
defer func() {
|
|
if closeErr := resp.Body.Close(); closeErr != nil {
|
|
p.log.Error("response body close error", "error", err)
|
|
}
|
|
}()
|
|
var out gcomPatterns
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
|
return nil, fmt.Errorf("json decode: %w", err)
|
|
}
|
|
p.log.Debug("Fetched dynamic angular detection patterns", "patterns", len(out), "duration", time.Since(st))
|
|
return out, nil
|
|
}
|
|
|
|
// patternsToDetectors converts a slice of gcomPattern into a slice of angulardetector.AngularDetector, by calling
|
|
// angularDetector() on each gcomPattern.
|
|
func (p *GCOMDetectorsProvider) patternsToDetectors(patterns gcomPatterns) ([]angulardetector.AngularDetector, error) {
|
|
var finalErr error
|
|
detectors := make([]angulardetector.AngularDetector, 0, len(patterns))
|
|
for _, pattern := range patterns {
|
|
d, err := pattern.angularDetector()
|
|
if err != nil {
|
|
// Fail silently in case of an errUnknownPatternType.
|
|
// This allows us to introduce new pattern types without breaking old Grafana versions
|
|
if errors.Is(err, errUnknownPatternType) {
|
|
p.log.Debug("Unknown angular pattern", "name", pattern.Name, "type", pattern.Type, "error", err)
|
|
continue
|
|
}
|
|
// Other error, do not ignore it
|
|
finalErr = errors.Join(finalErr, err)
|
|
}
|
|
detectors = append(detectors, d)
|
|
}
|
|
if finalErr != nil {
|
|
return nil, finalErr
|
|
}
|
|
return detectors, nil
|
|
}
|
|
|
|
// gcomPatternType is a pattern type returned by the GCOM API.
|
|
type gcomPatternType string
|
|
|
|
const (
|
|
gcomPatternTypeContains gcomPatternType = "contains"
|
|
gcomPatternTypeRegex gcomPatternType = "regex"
|
|
)
|
|
|
|
// errUnknownPatternType is returned when a pattern type is not known.
|
|
var errUnknownPatternType = errors.New("unknown pattern type")
|
|
|
|
// gcomPattern is an Angular detection pattern returned by the GCOM API.
|
|
type gcomPattern struct {
|
|
Name string
|
|
Pattern string
|
|
Type gcomPatternType
|
|
}
|
|
|
|
// angularDetector converts a gcomPattern into an AngularDetector, based on its Type.
|
|
// If a pattern type is unknown, it returns an error wrapping errUnknownPatternType.
|
|
func (p *gcomPattern) angularDetector() (angulardetector.AngularDetector, error) {
|
|
switch p.Type {
|
|
case gcomPatternTypeContains:
|
|
return &angulardetector.ContainsBytesDetector{Pattern: []byte(p.Pattern)}, nil
|
|
case gcomPatternTypeRegex:
|
|
re, err := regexp.Compile(p.Pattern)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%q regexp compile: %w", p.Pattern, err)
|
|
}
|
|
return &angulardetector.RegexDetector{Regex: re}, nil
|
|
}
|
|
return nil, fmt.Errorf("%q: %w", p.Type, errUnknownPatternType)
|
|
}
|
|
|
|
// gcomPatterns is a slice of gcomPattern s.
|
|
type gcomPatterns []gcomPattern
|