Skip to content

Commit b0f0069

Browse files
authored
feat: validate grafanaDep with semver (#515)
* feat: validate grafanaDep with semver * test: add parent key * test: error msg change * ref: log the error * ref: extract to separate analyzer, use semver * test: empty or missing grafana dep * chore: retrigger ci
1 parent e968649 commit b0f0069

8 files changed

Lines changed: 156 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ Run "mage gen:readme" to regenerate this section.
274274
| Logos / `logos` | Detects whether the plugin includes small and large logos to display in the plugin catalog. | None |
275275
| Manifest (Signing) / `manifest` | When a plugin is signed, the zip file will contain a signed `MANIFEST.txt` file. | None |
276276
| Metadata / `metadata` | Checks that `plugin.json` exists and is valid. | None |
277+
| Metadata Grafana Dependency / `grafanadependency` | Checks that dependencies.grafanaDependency in `plugin.json` is valid. | None |
277278
| Metadata Paths / `metadatapaths` | Ensures all paths are valid and images referenced exist. | None |
278279
| Metadata Validity / `metadatavalid` | Ensures metadata is valid and matches plugin schema. | None |
279280
| module.js (exists) / `modulejs` | All plugins require a `module.js` to be loaded. | None |

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/grafana/plugin-validator
33
go 1.25.5
44

55
require (
6+
github.com/Masterminds/semver/v3 v3.4.0
67
github.com/bmatcuk/doublestar/v4 v4.9.2
78
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
89
github.com/fatih/color v1.18.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ github.com/CycloneDX/cyclonedx-go v0.9.3 h1:Pyk/lwavPz7AaZNvugKFkdWOm93MzaIyWmBw
4848
github.com/CycloneDX/cyclonedx-go v0.9.3/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg=
4949
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
5050
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
51+
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
52+
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
5153
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
5254
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
5355
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=

pkg/analysis/passes/analysis.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/grafana/plugin-validator/pkg/analysis/passes/discoverability"
1818
"github.com/grafana/plugin-validator/pkg/analysis/passes/gomanifest"
1919
"github.com/grafana/plugin-validator/pkg/analysis/passes/gosec"
20+
"github.com/grafana/plugin-validator/pkg/analysis/passes/grafanadependency"
2021
"github.com/grafana/plugin-validator/pkg/analysis/passes/includesnested"
2122
"github.com/grafana/plugin-validator/pkg/analysis/passes/jargon"
2223
"github.com/grafana/plugin-validator/pkg/analysis/passes/jssourcemap"
@@ -103,4 +104,5 @@ var Analyzers = []*analysis.Analyzer{
103104
virusscan.Analyzer,
104105
circulardependencies.Analyzer,
105106
codediff.Analyzer,
107+
grafanadependency.Analyzer,
106108
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package grafanadependency
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/Masterminds/semver/v3"
8+
"github.com/grafana/plugin-validator/pkg/analysis"
9+
"github.com/grafana/plugin-validator/pkg/analysis/passes/metadata"
10+
)
11+
12+
var (
13+
invalidGrafanaDependency = &analysis.Rule{Name: "invalid-grafana-dependency", Severity: analysis.Error}
14+
validGrafanaDependency = &analysis.Rule{Name: "valid-grafana-dependency", Severity: analysis.OK}
15+
)
16+
17+
var Analyzer = &analysis.Analyzer{
18+
Name: "grafanadependency",
19+
Requires: []*analysis.Analyzer{metadata.Analyzer},
20+
Run: run,
21+
Rules: []*analysis.Rule{invalidGrafanaDependency, validGrafanaDependency},
22+
ReadmeInfo: analysis.ReadmeInfo{
23+
Name: "Metadata Grafana Dependency",
24+
Description: "Checks that dependencies.grafanaDependency in `plugin.json` is valid.",
25+
},
26+
}
27+
28+
func run(pass *analysis.Pass) (interface{}, error) {
29+
metadataBytes, ok := pass.ResultOf[metadata.Analyzer].([]byte)
30+
if !ok {
31+
return nil, nil
32+
}
33+
34+
var data metadata.Metadata
35+
if err := json.Unmarshal(metadataBytes, &data); err != nil {
36+
// if we fail to unmarshall it means the schema is incorrect
37+
// we will let the metadatavalid validator handle it
38+
return nil, nil
39+
}
40+
41+
_, err := semver.NewConstraint(data.Dependencies.GrafanaDependency)
42+
if err != nil {
43+
pass.ReportResult(
44+
pass.AnalyzerName,
45+
invalidGrafanaDependency,
46+
fmt.Sprintf("plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: %q", data.Dependencies.GrafanaDependency),
47+
"The plugin.json file has an invalid or empty grafanaDependency field. Please refer to the documentation for more information. https://grafana.com/docs/grafana/latest/developers/plugins/metadata/#grafanadependency",
48+
)
49+
return nil, nil
50+
}
51+
52+
if validGrafanaDependency.ReportAll {
53+
pass.ReportResult(pass.AnalyzerName, validGrafanaDependency, "plugin.json: dependencies.grafanaDependency field is valid", "")
54+
}
55+
56+
return nil, nil
57+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package grafanadependency
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/grafana/plugin-validator/pkg/analysis"
8+
"github.com/grafana/plugin-validator/pkg/analysis/passes/metadata"
9+
"github.com/grafana/plugin-validator/pkg/testpassinterceptor"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestGrafanaDependency(t *testing.T) {
14+
for _, tc := range []struct {
15+
name string
16+
pluginJSON string
17+
titleMsg string
18+
}{
19+
{
20+
name: "valid grafanaDependency constraint",
21+
pluginJSON: `{
22+
"id": "test-org-app",
23+
"dependencies": { "grafanaDependency": ">=11.6.0" }
24+
}`,
25+
titleMsg: "",
26+
},
27+
{
28+
name: "complex but valid grafanaDependency constraint",
29+
pluginJSON: `{
30+
"id": "test-org-app",
31+
"dependencies": { "grafanaDependency": ">=11.6.11 <12 || >=12.0.10 <12.1 || >=12.1.7 <12.2 || >=12.2.5" }
32+
}`,
33+
titleMsg: "",
34+
},
35+
{
36+
name: "invalid grafanaDependency constraint",
37+
pluginJSON: `{
38+
"id": "test-org-app",
39+
"dependencies": { "grafanaDependency": ">=invalid2" }
40+
}`,
41+
titleMsg: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \">=invalid2\"",
42+
},
43+
{
44+
name: "empty grafanaDependency",
45+
pluginJSON: `{
46+
"id": "test-org-app",
47+
"dependencies": { "grafanaDependency": "" }
48+
}`,
49+
titleMsg: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
50+
},
51+
{
52+
name: "missing grafanaDependency",
53+
pluginJSON: `{
54+
"id": "test-org-app",
55+
"dependencies": {"plugins": [ ]}
56+
}`,
57+
titleMsg: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
58+
},
59+
} {
60+
t.Run(tc.name, func(t *testing.T) {
61+
var interceptor testpassinterceptor.TestPassInterceptor
62+
pass := &analysis.Pass{
63+
RootDir: filepath.Join("./"),
64+
ResultOf: map[*analysis.Analyzer]interface{}{
65+
metadata.Analyzer: []byte(tc.pluginJSON),
66+
},
67+
Report: interceptor.ReportInterceptor(),
68+
}
69+
70+
_, err := Analyzer.Run(pass)
71+
require.NoError(t, err)
72+
if len(tc.titleMsg) > 0 {
73+
require.Len(t, interceptor.Diagnostics, 1)
74+
require.Equal(
75+
t,
76+
tc.titleMsg,
77+
interceptor.Diagnostics[0].Title,
78+
)
79+
} else {
80+
require.Len(t, interceptor.Diagnostics, 0)
81+
}
82+
})
83+
}
84+
}

pkg/cmd/plugincheck2/main_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,14 @@ func TestIntegration(t *testing.T) {
303303
Name: "code-diff-skipped",
304304
},
305305
},
306+
"grafanadependency": {
307+
{
308+
Severity: "error",
309+
Title: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
310+
Detail: "The plugin.json file has an invalid or empty grafanaDependency field. Please refer to the documentation for more information. https://grafana.com/docs/grafana/latest/developers/plugins/metadata/#grafanadependency",
311+
Name: "invalid-grafana-dependency",
312+
},
313+
},
306314
},
307315
},
308316
},

pkg/runner/runner_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ var tests = []struct {
5050
"plugin.json: plugin id should follow the format org-name-type",
5151
"LLM review skipped due to errors in metadatavalid",
5252
"Code diff skipped due to errors in metadatavalid",
53+
"plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
5354
}},
5455
}
5556

0 commit comments

Comments
 (0)