Skip to content

Commit

Permalink
feat: check screenshot image type
Browse files Browse the repository at this point in the history
  • Loading branch information
s4kh committed Nov 13, 2024
1 parent 8355c36 commit 2cb7977
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 42 deletions.
83 changes: 64 additions & 19 deletions pkg/analysis/passes/screenshots/screenshots.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,42 @@ package screenshots
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"

"github.com/grafana/plugin-validator/pkg/analysis"
"github.com/grafana/plugin-validator/pkg/analysis/passes/archive"
"github.com/grafana/plugin-validator/pkg/analysis/passes/metadata"
)

var (
screenshots = &analysis.Rule{Name: "screenshots", Severity: analysis.Warning}
screenshots = &analysis.Rule{Name: "screenshots", Severity: analysis.Warning}
screenshotsType = &analysis.Rule{Name: "screenshots-image-type", Severity: analysis.Error}
)

var Analyzer = &analysis.Analyzer{
Name: "screenshots",
Run: checkScreenshotsExist,
Requires: []*analysis.Analyzer{metadata.Analyzer},
Rules: []*analysis.Rule{screenshots},
Run: checkScreenshots,
Requires: []*analysis.Analyzer{metadata.Analyzer, archive.Analyzer},
Rules: []*analysis.Rule{screenshots, screenshotsType},
ReadmeInfo: analysis.ReadmeInfo{
Name: "Screenshots",
Description: "Screenshots are specified in `plugin.json` that will be used in the Grafana plugin catalog.",
},
}
var acceptedImageTypes = []string{"image/jpeg", "image/png", "image/svg+xml", "image/gif"}

func checkScreenshotsExist(pass *analysis.Pass) (interface{}, error) {
func checkScreenshots(pass *analysis.Pass) (interface{}, error) {
metadataBody, ok := pass.ResultOf[metadata.Analyzer].([]byte)
if !ok {
return nil, nil
}
archiveDir, ok := pass.ResultOf[archive.Analyzer].(string)
if !ok {
return nil, nil
}

var data metadata.Metadata
if err := json.Unmarshal(metadataBody, &data); err != nil {
Expand All @@ -39,24 +49,59 @@ func checkScreenshotsExist(pass *analysis.Pass) (interface{}, error) {
explanation := "Screenshots are displayed in the Plugin catalog. Please add at least one screenshot to your plugin.json."
pass.ReportResult(pass.AnalyzerName, screenshots, "plugin.json: should include screenshots for the Plugin catalog", explanation)
return data.Info.Screenshots, nil
} else {
reportCount := 0
for _, screenshot := range data.Info.Screenshots {
if strings.TrimSpace(screenshot.Path) == "" {
reportCount++
pass.ReportResult(pass.AnalyzerName, screenshots, fmt.Sprintf("plugin.json: invalid empty screenshot path: %q", screenshot.Name), "The screenshot path must not be empty.")
}
}
}

if reportCount > 0 {
return nil, nil
reportCount := 0
for _, screenshot := range data.Info.Screenshots {
if strings.TrimSpace(screenshot.Path) == "" {
reportCount++
pass.ReportResult(pass.AnalyzerName, screenshots, fmt.Sprintf("plugin.json: invalid empty screenshot path: %q", screenshot.Name), "The screenshot path must not be empty.")
} else if !validImageType(filepath.Join(archiveDir, screenshot.Path)) {
reportCount++
pass.ReportResult(pass.AnalyzerName, screenshotsType, fmt.Sprintf("invalid screenshot image type: %q. Accepted image types: %q", screenshot.Path, acceptedImageTypes), "The screenshot image type invalid.")
}
}

if screenshots.ReportAll {
screenshots.Severity = analysis.OK
pass.ReportResult(pass.AnalyzerName, screenshots, "plugin.json: includes screenshots for the Plugin catalog", "")
}
if reportCount > 0 {
return nil, nil
}

if screenshots.ReportAll {
screenshots.Severity = analysis.OK
pass.ReportResult(pass.AnalyzerName, screenshots, "plugin.json: includes screenshots for the Plugin catalog", "")
}

if screenshotsType.ReportAll {
screenshotsType.Severity = analysis.OK
pass.ReportResult(pass.AnalyzerName, screenshotsType, "screenshots are valid image type", "")
}

return data.Info.Screenshots, nil
}

func validImageType(imgPath string) bool {
file, err := os.Open(imgPath)
if err != nil {
fmt.Printf("cannot open file: %v\n", err)
return false
}
defer file.Close()

// 512 is enough for getting the content type
// https://pkg.go.dev/net/http#DetectContentType
buffer := make([]byte, 512)
if _, err := file.Read(buffer); err != nil {
fmt.Printf("cannot read file: %v\n", err)
return false
}

mimeType := http.DetectContentType(buffer)

for _, accepted := range acceptedImageTypes {
if accepted == mimeType {
return true
}
}

return false
}
129 changes: 106 additions & 23 deletions pkg/analysis/passes/screenshots/screenshots_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/grafana/plugin-validator/pkg/analysis"
"github.com/grafana/plugin-validator/pkg/analysis/passes/archive"
"github.com/grafana/plugin-validator/pkg/analysis/passes/metadata"
"github.com/grafana/plugin-validator/pkg/testpassinterceptor"
"github.com/stretchr/testify/require"
Expand All @@ -13,20 +14,21 @@ import (
func TestValidScreenshots(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
const pluginJsonContent = `{
"name": "my plugin name",
"info": {
"screenshots": [
{
"path": "img/screenshot1.png",
"name": "screenshot1"
}
]
}
}`
"name": "my plugin name",
"info": {
"screenshots": [
{
"path": "testdata/valid.png",
"name": "screenshot1"
}
]
}
}`
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(pluginJsonContent),
archive.Analyzer: filepath.Join("."),
},
Report: interceptor.ReportInterceptor(),
}
Expand All @@ -39,15 +41,16 @@ func TestValidScreenshots(t *testing.T) {
func TestNoScreenshots(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
const pluginJsonContent = `{
"name": "my plugin name",
"info": {
"screenshots": []
}
}`
"name": "my plugin name",
"info": {
"screenshots": []
}
}`
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(pluginJsonContent),
archive.Analyzer: filepath.Join("."),
},
Report: interceptor.ReportInterceptor(),
}
Expand All @@ -62,18 +65,19 @@ func TestNoScreenshots(t *testing.T) {
func TestInvalidScreenshotPath(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
const pluginJsonContent = `{
"name": "my plugin name",
"info": {
"screenshots": [{
"path": "",
"name": "screenshot1"
}]
}
}`
"name": "my plugin name",
"info": {
"screenshots": [{
"path": "",
"name": "screenshot1"
}]
}
}`
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(pluginJsonContent),
archive.Analyzer: filepath.Join("."),
},
Report: interceptor.ReportInterceptor(),
}
Expand All @@ -83,3 +87,82 @@ func TestInvalidScreenshotPath(t *testing.T) {
require.Len(t, interceptor.Diagnostics, 1)
require.Equal(t, interceptor.Diagnostics[0].Title, "plugin.json: invalid empty screenshot path: \"screenshot1\"")
}

func TestInvalidScreenshotImageType(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
const pluginJsonContent = `{
"name": "my plugin name",
"info": {
"screenshots": [{
"name": "enhance your existing systems",
"path": "testdata/invalid.avif"
}]
}
}`
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(pluginJsonContent),
archive.Analyzer: filepath.Join("."),
},
Report: interceptor.ReportInterceptor(),
}

_, err := Analyzer.Run(pass)
require.NoError(t, err)
require.Len(t, interceptor.Diagnostics, 1)
require.Equal(t, `invalid screenshot image type: "testdata/invalid.avif". Accepted image types: ["image/jpeg" "image/png" "image/svg+xml" "image/gif"]`, interceptor.Diagnostics[0].Title)
}

func TestTextfileScreenshotImage(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
const pluginJsonContent = `{
"name": "my plugin name",
"info": {
"screenshots": [
{
"name": "test",
"path": "testdata/textfile.png"
}]
}
}`
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(pluginJsonContent),
archive.Analyzer: filepath.Join("."),
},
Report: interceptor.ReportInterceptor(),
}

_, err := Analyzer.Run(pass)
require.NoError(t, err)
require.Len(t, interceptor.Diagnostics, 1)
require.Equal(t, `invalid screenshot image type: "testdata/textfile.png". Accepted image types: ["image/jpeg" "image/png" "image/svg+xml" "image/gif"]`, interceptor.Diagnostics[0].Title)
}

func TestJpgScreenshotImage(t *testing.T) {
var interceptor testpassinterceptor.TestPassInterceptor
const pluginJsonContent = `{
"name": "my plugin name",
"info": {
"screenshots": [
{
"name": "test",
"path": "testdata/valid.jpg"
}]
}
}`
pass := &analysis.Pass{
RootDir: filepath.Join("./"),
ResultOf: map[*analysis.Analyzer]interface{}{
metadata.Analyzer: []byte(pluginJsonContent),
archive.Analyzer: filepath.Join("."),
},
Report: interceptor.ReportInterceptor(),
}

_, err := Analyzer.Run(pass)
require.NoError(t, err)
require.Len(t, interceptor.Diagnostics, 0)
}
Binary file not shown.
1 change: 1 addition & 0 deletions pkg/analysis/passes/screenshots/testdata/textfile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 2cb7977

Please sign in to comment.