Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ Run "mage gen:readme" to regenerate this section.
| Logos / `logos` | Detects whether the plugin includes small and large logos to display in the plugin catalog. | None |
| Manifest (Signing) / `manifest` | When a plugin is signed, the zip file will contain a signed `MANIFEST.txt` file. | None |
| Metadata / `metadata` | Checks that `plugin.json` exists and is valid. | None |
| Metadata Grafana Dependency / `grafanadependency` | Checks that dependencies.grafanaDependency in `plugin.json` is valid. | None |
| Metadata Paths / `metadatapaths` | Ensures all paths are valid and images referenced exist. | None |
| Metadata Validity / `metadatavalid` | Ensures metadata is valid and matches plugin schema. | None |
| module.js (exists) / `modulejs` | All plugins require a `module.js` to be loaded. | None |
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/grafana/plugin-validator
go 1.25.5

require (
github.com/Masterminds/semver/v3 v3.4.0
github.com/bmatcuk/doublestar/v4 v4.9.2
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
github.com/fatih/color v1.18.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ github.com/CycloneDX/cyclonedx-go v0.9.3 h1:Pyk/lwavPz7AaZNvugKFkdWOm93MzaIyWmBw
github.com/CycloneDX/cyclonedx-go v0.9.3/go.mod h1:vcK6pKgO1WanCdd61qx4bFnSsDJQ6SbM2ZuMIgq86Jg=
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI=
github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
Expand Down
2 changes: 2 additions & 0 deletions pkg/analysis/passes/analysis.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/grafana/plugin-validator/pkg/analysis/passes/discoverability"
"github.com/grafana/plugin-validator/pkg/analysis/passes/gomanifest"
"github.com/grafana/plugin-validator/pkg/analysis/passes/gosec"
"github.com/grafana/plugin-validator/pkg/analysis/passes/grafanadependency"
"github.com/grafana/plugin-validator/pkg/analysis/passes/includesnested"
"github.com/grafana/plugin-validator/pkg/analysis/passes/jargon"
"github.com/grafana/plugin-validator/pkg/analysis/passes/jssourcemap"
Expand Down Expand Up @@ -103,4 +104,5 @@ var Analyzers = []*analysis.Analyzer{
virusscan.Analyzer,
circulardependencies.Analyzer,
codediff.Analyzer,
grafanadependency.Analyzer,
}
57 changes: 57 additions & 0 deletions pkg/analysis/passes/grafanadependency/grafanadependency.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package grafanadependency

import (
"encoding/json"
"fmt"

"github.com/Masterminds/semver/v3"
"github.com/grafana/plugin-validator/pkg/analysis"
"github.com/grafana/plugin-validator/pkg/analysis/passes/metadata"
)

var (
invalidGrafanaDependency = &analysis.Rule{Name: "invalid-grafana-dependency", Severity: analysis.Error}
validGrafanaDependency = &analysis.Rule{Name: "valid-grafana-dependency", Severity: analysis.OK}
)

var Analyzer = &analysis.Analyzer{
Name: "grafanadependency",
Requires: []*analysis.Analyzer{metadata.Analyzer},
Run: run,
Rules: []*analysis.Rule{invalidGrafanaDependency, validGrafanaDependency},
ReadmeInfo: analysis.ReadmeInfo{
Name: "Metadata Grafana Dependency",
Description: "Checks that dependencies.grafanaDependency in `plugin.json` is valid.",
},
}

func run(pass *analysis.Pass) (interface{}, error) {
metadataBytes, ok := pass.ResultOf[metadata.Analyzer].([]byte)
if !ok {
return nil, nil
}

var data metadata.Metadata
if err := json.Unmarshal(metadataBytes, &data); err != nil {
// if we fail to unmarshall it means the schema is incorrect
// we will let the metadatavalid validator handle it
return nil, nil
}

_, err := semver.NewConstraint(data.Dependencies.GrafanaDependency)
if err != nil {
pass.ReportResult(
pass.AnalyzerName,
invalidGrafanaDependency,
fmt.Sprintf("plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: %q", data.Dependencies.GrafanaDependency),
"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",
)
return nil, nil
}

if validGrafanaDependency.ReportAll {
pass.ReportResult(pass.AnalyzerName, validGrafanaDependency, "plugin.json: dependencies.grafanaDependency field is valid", "")
}

return nil, nil
}
84 changes: 84 additions & 0 deletions pkg/analysis/passes/grafanadependency/grafanadependency_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package grafanadependency

import (
"path/filepath"
"testing"

"github.com/grafana/plugin-validator/pkg/analysis"
"github.com/grafana/plugin-validator/pkg/analysis/passes/metadata"
"github.com/grafana/plugin-validator/pkg/testpassinterceptor"
"github.com/stretchr/testify/require"
)

func TestGrafanaDependency(t *testing.T) {
for _, tc := range []struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add test cases for:

  • Empty grafanaDependency
  • Missing grafanaDependency (if that makes sense, since there's also the schema validation)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Added the tests. Unmarshal succeeds and NewConstraint returns error - improper constraint thus same title message is returned

name string
pluginJSON string
titleMsg string
}{
{
name: "valid grafanaDependency constraint",
pluginJSON: `{
"id": "test-org-app",
"dependencies": { "grafanaDependency": ">=11.6.0" }
}`,
titleMsg: "",
},
{
name: "complex but valid grafanaDependency constraint",
pluginJSON: `{
"id": "test-org-app",
"dependencies": { "grafanaDependency": ">=11.6.11 <12 || >=12.0.10 <12.1 || >=12.1.7 <12.2 || >=12.2.5" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using github.com/Masterminds/semver/v3 v3.4.0 cause hashicorp/go-version treats this as invalid. Grafana treats as valid -> grafana/grafana#118090

}`,
titleMsg: "",
},
{
name: "invalid grafanaDependency constraint",
pluginJSON: `{
"id": "test-org-app",
"dependencies": { "grafanaDependency": ">=invalid2" }
}`,
titleMsg: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \">=invalid2\"",
},
{
name: "empty grafanaDependency",
pluginJSON: `{
"id": "test-org-app",
"dependencies": { "grafanaDependency": "" }
}`,
titleMsg: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
},
{
name: "missing grafanaDependency",
pluginJSON: `{
"id": "test-org-app",
"dependencies": {"plugins": [ ]}
}`,
titleMsg: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
},
} {
t.Run(tc.name, func(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(tc.pluginJSON),
},
Report: interceptor.ReportInterceptor(),
}

_, err := Analyzer.Run(pass)
require.NoError(t, err)
if len(tc.titleMsg) > 0 {
require.Len(t, interceptor.Diagnostics, 1)
require.Equal(
t,
tc.titleMsg,
interceptor.Diagnostics[0].Title,
)
} else {
require.Len(t, interceptor.Diagnostics, 0)
}
})
}
}
8 changes: 8 additions & 0 deletions pkg/cmd/plugincheck2/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,14 @@ func TestIntegration(t *testing.T) {
Name: "code-diff-skipped",
},
},
"grafanadependency": {
{
Severity: "error",
Title: "plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
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",
Name: "invalid-grafana-dependency",
},
},
},
},
},
Expand Down
1 change: 1 addition & 0 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ var tests = []struct {
"plugin.json: plugin id should follow the format org-name-type",
"LLM review skipped due to errors in metadatavalid",
"Code diff skipped due to errors in metadatavalid",
"plugin.json: dependencies.grafanaDependency field has invalid or empty version constraint: \"\"",
}},
}

Expand Down
Loading