Skip to content

Commit

Permalink
feat: validation/lint support
Browse files Browse the repository at this point in the history
  • Loading branch information
szkiba committed Jul 26, 2024
1 parent f55beba commit 4357e80
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 16 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ k6registry [flags] <jq filter> [file]
### Flags

```
-c, --compact compact instead of pretty-printed output
-h, --help help for k6registry
-o, --out string write output to file instead of stdout
-m, --mute no output, only validation
--loose skip JSON schema validation
--lint enable built-in linter
-c, --compact compact instead of pretty-printed output
-r, --raw output raw strings, not JSON texts
-y, --yaml output YAML instead of JSON
-V, --version print version
-h, --help help for k6registry
```

<!-- #endregion cli -->
Expand Down
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ inputs:
description: output file name
required: false

mute:
description: no output, only validation
required: false

loose:
description: skip JSON schema validation
required: false

lint:
description: enable built-in linter
required: false

compact:
description: compact instead of pretty-printed output
required: false
Expand Down
6 changes: 5 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type options struct {
raw bool
yaml bool
mute bool
loose bool
lint bool
}

// New creates new cobra command for exec command.
Expand Down Expand Up @@ -61,6 +63,8 @@ func New() (*cobra.Command, error) {

flags.StringVarP(&opts.out, "out", "o", "", "write output to file instead of stdout")
flags.BoolVarP(&opts.mute, "mute", "m", false, "no output, only validation")
flags.BoolVar(&opts.loose, "loose", false, "skip JSON schema validation")
flags.BoolVar(&opts.lint, "lint", false, "enable built-in linter")
flags.BoolVarP(&opts.compact, "compact", "c", false, "compact instead of pretty-printed output")
flags.BoolVarP(&opts.raw, "raw", "r", false, "output raw strings, not JSON texts")
flags.BoolVarP(&opts.yaml, "yaml", "y", false, "output YAML instead of JSON")
Expand Down Expand Up @@ -109,7 +113,7 @@ func run(ctx context.Context, args []string, opts *options) error {
output = file
}

registry, err := load(ctx, input)
registry, err := load(ctx, input, opts.loose, opts.lint)
if err != nil {
return err
}
Expand Down
12 changes: 12 additions & 0 deletions cmd/k6registry/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ func getArgs() []string {

var args []string

if out := getenv("INPUT_MUTE", "false"); len(out) != 0 {
args = append(args, "--mute", out)
}

if out := getenv("INPUT_LOOSE", "false"); len(out) != 0 {
args = append(args, "--loose", out)
}

if out := getenv("INPUT_LINT", "false"); len(out) != 0 {
args = append(args, "--lint", out)
}

if getenv("INPUT_COMPACT", "false") == "true" {
args = append(args, "--compact")
}
Expand Down
40 changes: 31 additions & 9 deletions cmd/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,35 @@ import (
"gopkg.in/yaml.v3"
)

func load(ctx context.Context, in io.Reader) (interface{}, error) {
decoder := yaml.NewDecoder(in)
func load(ctx context.Context, in io.Reader, loose bool, lint bool) (interface{}, error) {
var (
raw []byte
err error
)

if loose {
raw, err = io.ReadAll(in)
} else {
raw, err = validateWithSchema(in)
}

if err != nil {
return nil, err
}

var registry k6registry.Registry

if err := decoder.Decode(&registry); err != nil {
if err := yaml.Unmarshal(raw, &registry); err != nil {
return nil, err
}

registry = append(registry, k6registry.Extension{Module: k6Module, Description: k6Description})

for idx, ext := range registry {
if ext.Repo != nil {
continue
}

if strings.HasPrefix(ext.Module, k6Module) || strings.HasPrefix(ext.Module, ghModulePrefix) {
repo, err := loadGitHub(ctx, ext.Module)
if err != nil {
Expand All @@ -34,6 +51,12 @@ func load(ctx context.Context, in io.Reader) (interface{}, error) {
}
}

if lint {
if err := validateWithLinter(registry); err != nil {
return nil, err
}
}

bin, err := json.Marshal(registry)
if err != nil {
return nil, err
Expand Down Expand Up @@ -84,13 +107,17 @@ func loadGitHub(ctx context.Context, module string) (*k6registry.Repository, err
repo.Homepage = repo.Url
}

repo.Archived = rep.GetArchived()

repo.Description = rep.GetDescription()
repo.Stars = rep.GetStargazersCount()

if lic := rep.GetLicense(); lic != nil {
repo.License = lic.GetSPDXID()
}

repo.Public = rep.GetVisibility() == "public"

tags, _, err := client.Repositories.ListTags(ctx, owner, name, &github.ListOptions{PerPage: 100})
if err != nil {
return nil, err
Expand All @@ -99,12 +126,7 @@ func loadGitHub(ctx context.Context, module string) (*k6registry.Repository, err
for _, tag := range tags {
name := tag.GetName()

if name[0] != 'v' {
continue
}

_, err := semver.NewVersion(name)
if err != nil {
if _, err := semver.NewVersion(name); err != nil {
continue
}

Expand Down
196 changes: 196 additions & 0 deletions cmd/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"io"
"strings"

"github.com/grafana/k6registry"
"github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3"
)

func yaml2json(input []byte) ([]byte, error) {
var data interface{}

if err := yaml.Unmarshal(input, &data); err != nil {
return nil, err
}

return json.Marshal(data)
}

func validateWithSchema(input io.Reader) ([]byte, error) {
yamlRaw, err := io.ReadAll(input)
if err != nil {
return nil, err
}

jsonRaw, err := yaml2json(yamlRaw)
if err != nil {
return nil, err
}

documentLoader := gojsonschema.NewBytesLoader(jsonRaw)
schemaLoader := gojsonschema.NewBytesLoader(k6registry.Schema)

result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return nil, err
}

if result.Valid() {
return yamlRaw, nil
}

var buff strings.Builder

for _, desc := range result.Errors() {
buff.WriteString(fmt.Sprintf(" - %s\n", desc.String()))
}

return nil, fmt.Errorf("%w: schema validation failed\n%s", errInvalidRegistry, buff.String())
}

func validateWithLinter(registry k6registry.Registry) error {
var buff strings.Builder

for _, ext := range registry {
if ok, msgs := lintExtension(ext); !ok {
for _, msg := range msgs {
buff.WriteString(fmt.Sprintf(" - %s\n", msg))
}
}
}

if buff.Len() == 0 {
return nil
}

return fmt.Errorf("%w: linter validation failed\n%s", errInvalidRegistry, buff.String())
}

func hasTopic(ext k6registry.Extension) bool {
found := false

for _, topic := range ext.Repo.Topics {
if topic == "xk6" {
found = true

break
}
}

return found
}

func lintExtension(ext k6registry.Extension) (bool, []string) {
if ext.Repo == nil {
return false, []string{"unsupported module: " + ext.Module}
}

var msgs []string

if len(ext.Repo.Versions) == 0 {
msgs = append(msgs, "no released versions: "+ext.Module)
}

if ext.Repo.Public {
if !hasTopic(ext) && ext.Module != k6Module {
msgs = append(msgs, "missing xk6 topic: "+ext.Module)
}

if len(ext.Repo.License) == 0 {
msgs = append(msgs, "missing license: "+ext.Module)
} else if _, ok := validLicenses[ext.Repo.License]; !ok {
msgs = append(msgs, "unsupported license: "+ext.Repo.License+" "+ext.Module)
}

if ext.Repo.Archived {
msgs = append(msgs, "repository is archived: "+ext.Module)
}
}

if len(msgs) == 0 {
return true, nil
}

return false, msgs
}

var errInvalidRegistry = errors.New("invalid registry")

// source: https://spdx.org/licenses/
// both FSF Free and OSI Approved licenses
var validLicenses = map[string]struct{}{ //nolint:gochecknoglobals
"AFL-1.1": {},
"AFL-1.2": {},
"AFL-2.0": {},
"AFL-2.1": {},
"AFL-3.0": {},
"AGPL-3.0": {},
"AGPL-3.0-only": {},
"AGPL-3.0-or-later": {},
"Apache-1.1": {},
"Apache-2.0": {},
"APSL-2.0": {},
"Artistic-2.0": {},
"BSD-2-Clause": {},
"BSD-3-Clause": {},
"BSL-1.0": {},
"CDDL-1.0": {},
"CPAL-1.0": {},
"CPL-1.0": {},
"ECL-2.0": {},
"EFL-2.0": {},
"EPL-1.0": {},
"EPL-2.0": {},
"EUDatagrid": {},
"EUPL-1.1": {},
"EUPL-1.2": {},
"GPL-2.0-only": {},
"GPL-2.0": {},
"GPL-2.0-or-later": {},
"GPL-3.0-only": {},
"GPL-3.0": {},
"GPL-3.0-or-later": {},
"HPND": {},
"Intel": {},
"IPA": {},
"IPL-1.0": {},
"ISC": {},
"LGPL-2.1": {},
"LGPL-2.1-only": {},
"LGPL-2.1-or-later": {},
"LGPL-3.0": {},
"LGPL-3.0-only": {},
"LGPL-3.0-or-later": {},
"LPL-1.02": {},
"MIT": {},
"MPL-1.1": {},
"MPL-2.0": {},
"MS-PL": {},
"MS-RL": {},
"NCSA": {},
"Nokia": {},
"OFL-1.1": {},
"OSL-1.0": {},
"OSL-2.0": {},
"OSL-2.1": {},
"OSL-3.0": {},
"PHP-3.01": {},
"Python-2.0": {},
"QPL-1.0": {},
"RPSL-1.0": {},
"SISSL": {},
"Sleepycat": {},
"SPL-1.0": {},
"Unlicense": {},
"UPL-1.0": {},
"W3C": {},
"Zlib": {},
"ZPL-2.0": {},
"ZPL-2.1": {},
}
Loading

0 comments on commit 4357e80

Please sign in to comment.