diff --git a/.gitignore b/.gitignore index d8f0730..f45a7be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /build /coverage.txt +/k6registry +/k6registry.exe diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 62d3200..5a979ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,7 +104,7 @@ go tool cover -html=coverage.txt This is the easiest way to create an executable binary (although the release process uses the `goreleaser` tool to create release versions). ```bash -go build -ldflags="-w -s" -o build/k6registry ./cmd/k6registry +go build -ldflags="-w -s" -o k6registry ./cmd/k6registry ``` [build]: <#build---build-the-executable-binary> @@ -114,12 +114,23 @@ go build -ldflags="-w -s" -o build/k6registry ./cmd/k6registry The goreleaser command-line tool is used during the release process. During development, it is advisable to create binaries with the same tool from time to time. ```bash -rm -f build/k6registry -goreleaser build --snapshot --clean --single-target -o build/k6registry +rm -f k6registry +goreleaser build --snapshot --clean --single-target -o k6registry ``` [snapshot]: <#snapshot---creating-an-executable-binary-with-a-snapshot-version> +### docker - Build docker image + +Building a Docker image. Before building the image, it is advisable to perform a snapshot build using goreleaser. To build the image, it is advisable to use the same `Docker.goreleaser` file that `goreleaser` uses during release. + +Requires +: snapshot + +```bash +docker build -t k6registry -f Dockerfile.goreleaser . +``` + ### clean - Delete the build directory ```bash @@ -132,3 +143,9 @@ The most robust thing is to update everything (both the schema and the example) Requires : schema, example, readme + +## legacy - Convert legacy registry + +```bash + go run ./cmd/k6registry . --legacy | yq '.[]|= pick(["module","description","official","cloud","imports","outputs","repo"])|sort_by(.module)' > ./docs/legacy.yaml +``` \ No newline at end of file diff --git a/Dockerfile.goreleaser b/Dockerfile.goreleaser new file mode 100644 index 0000000..334dbdb --- /dev/null +++ b/Dockerfile.goreleaser @@ -0,0 +1,8 @@ +FROM debian:12.6-slim + +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates +RUN update-ca-certificates + +COPY k6registry /usr/bin/ + +ENTRYPOINT ["k6registry"] diff --git a/README.md b/README.md index 99e94c5..f704754 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ Check [k6 Extension Registry Concept](docs/registry.md) for information on desig cloud: true official: true -- module: github.com/grafana/xk6-distruptor +- module: github.com/grafana/xk6-disruptor description: Inject faults to test imports: - - k6/x/distruptor + - k6/x/disruptor official: true - module: github.com/szkiba/xk6-faker @@ -34,6 +34,8 @@ Check [k6 Extension Registry Concept](docs/registry.md) for information on desig - k6/x/faker ``` +A [legacy extension registry](docs/legacy.yaml) converted to the new format is also a good example. + ## Install Precompiled binaries can be downloaded and installed from the [Releases](https://github.com/grafana/k6registry/releases) page. @@ -55,17 +57,33 @@ k6 extension registry processor Command line k6 extension registry processor. -`k6registry` is a command line tool that enables k6 extension registry processing and the generation of customized JSON output for different applications. Processing is based on popular `jq` expressions using an embedded `jq` implementation. +k6registry is a command line tool that enables k6 extension registry processing and the generation of customized JSON output for different applications. Processing is based on popular `jq` expressions using an embedded `jq` implementation. + +The first argument is the jq filter expression. This is the basis for processing. + +The extension registry is read from the YAML format file specified in the second argument. If it is missing, the extension registry is read from the standard input. + +Repository metadata is collected using the repository manager APIs. Currently only the GitHub API is supported. + +The output of the processing will be written to the standard output by default. The output can be saved to a file using the `-o/--out` flag. ``` -k6registry [flags] +k6registry [flags] [file] ``` ### Flags ``` - -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 ``` diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..b61c164 --- /dev/null +++ b/action.yml @@ -0,0 +1,49 @@ +name: k6registry +description: k6 extension registry processor +author: Grafana Labs + +branding: + icon: settings + color: purple + +inputs: + filter: + description: jq compatible filter + required: false + default: . + + in: + description: input file name + required: true + + out: + 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 + + raw: + description: output raw strings, not JSON texts + required: false + + yaml: + description: output YAML instead of JSON + required: false + +runs: + using: docker + image: ghcr.io/grafana/k6registry:v0 diff --git a/cmd/cmd.go b/cmd/cmd.go index 2318ee8..2464f00 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -2,28 +2,155 @@ package cmd import ( + "context" _ "embed" + "encoding/json" + "fmt" + "io" + "os" "github.com/spf13/cobra" + "gopkg.in/yaml.v3" ) //go:embed help.md var help string +type options struct { + out string + compact bool + raw bool + yaml bool + mute bool + loose bool + lint bool +} + // New creates new cobra command for exec command. -func New() *cobra.Command { +func New() (*cobra.Command, error) { + opts := new(options) + + legacy := false + root := &cobra.Command{ - Use: "k6registry", + Use: "k6registry [flags] [file]", Short: "k6 extension registry processor", Long: help, SilenceUsage: true, SilenceErrors: true, DisableAutoGenTag: true, + Args: cobra.RangeArgs(1, 2), CompletionOptions: cobra.CompletionOptions{DisableDefaultCmd: true}, - RunE: func(_ *cobra.Command, _ []string) error { - return nil + RunE: func(cmd *cobra.Command, args []string) error { + if legacy { + return legacyConvert(cmd.Context()) + } + + return run(cmd.Context(), args, opts) }, } - return root + ctx, err := newContext(context.TODO()) + if err != nil { + return nil, err + } + + root.SetContext(ctx) + + flags := root.Flags() + + flags.SortFlags = false + + 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") + root.MarkFlagsMutuallyExclusive("raw", "compact", "yaml", "mute") + + flags.BoolP("version", "V", false, "print version") + + flags.BoolVar(&legacy, "legacy", false, "convert legacy registry") + + cobra.CheckErr(flags.MarkHidden("legacy")) + + return root, nil +} + +//nolint:forbidigo +func run(ctx context.Context, args []string, opts *options) error { + var result error + + input := os.Stdin + + if len(args) > 1 { + file, err := os.Open(args[1]) + if err != nil { + return err + } + + defer func() { + result = file.Close() + }() + + input = file + } + + output := os.Stdout + + if len(opts.out) > 0 { + file, err := os.Create(opts.out) + if err != nil { + return err + } + + defer func() { + result = file.Close() + }() + + output = file + } + + registry, err := load(ctx, input, opts.loose, opts.lint) + if err != nil { + return err + } + + if err := jq(registry, args[0], printer(output, opts)); err != nil { + return err + } + + return result +} + +func printer(output io.Writer, opts *options) func(interface{}) error { + if opts.raw { + return func(v interface{}) error { + _, err := fmt.Fprintln(output, v) + + return err + } + } + + if opts.yaml { + encoder := yaml.NewEncoder(output) + + return encoder.Encode + } + + if opts.mute { + return func(_ interface{}) error { + return nil + } + } + + encoder := json.NewEncoder(output) + + if !opts.compact { + encoder.SetIndent("", " ") + } + + return encoder.Encode } diff --git a/cmd/context.go b/cmd/context.go new file mode 100644 index 0000000..c444bd1 --- /dev/null +++ b/cmd/context.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/cli/go-gh/v2/pkg/api" + "github.com/cli/go-gh/v2/pkg/auth" + "github.com/cli/go-gh/v2/pkg/config" + "github.com/google/go-github/v62/github" +) + +type githubClientKey struct{} + +var errInvalidContext = errors.New("invalid context") + +// contextGitHubClient returns a *github.Client from context. +func contextGitHubClient(ctx context.Context) (*github.Client, error) { + value := ctx.Value(githubClientKey{}) + if value != nil { + if client, ok := value.(*github.Client); ok { + return client, nil + } + } + + return nil, fmt.Errorf("%w: missing github.Client", errInvalidContext) +} + +// newContext prepares GitHub CLI extension context with http.Client and github.Client values. +// You can use ContextHTTPClient and ContextGitHubClient later to get client instances from the context. +func newContext(ctx context.Context) (context.Context, error) { + htc, err := newHTTPClient() + if err != nil { + return nil, err + } + + return context.WithValue(ctx, githubClientKey{}, github.NewClient(htc)), nil +} + +func newHTTPClient() (*http.Client, error) { + var opts api.ClientOptions + + opts.Host, _ = auth.DefaultHost() + + opts.AuthToken, _ = auth.TokenForHost(opts.Host) + if opts.AuthToken == "" { + return nil, fmt.Errorf("authentication token not found for host %s", opts.Host) + } + + if cfg, _ := config.Read(nil); cfg != nil { + opts.UnixDomainSocket, _ = cfg.Get([]string{"http_unix_socket"}) + } + + opts.EnableCache = true + opts.CacheTTL = 2 * time.Hour + + return api.NewHTTPClient(opts) +} diff --git a/cmd/help.md b/cmd/help.md index 7743953..bcfd6c3 100644 --- a/cmd/help.md +++ b/cmd/help.md @@ -1,3 +1,11 @@ Command line k6 extension registry processor. -`k6registry` is a command line tool that enables k6 extension registry processing and the generation of customized JSON output for different applications. Processing is based on popular `jq` expressions using an embedded `jq` implementation. +k6registry is a command line tool that enables k6 extension registry processing and the generation of customized JSON output for different applications. Processing is based on popular `jq` expressions using an embedded `jq` implementation. + +The first argument is the jq filter expression. This is the basis for processing. + +The extension registry is read from the YAML format file specified in the second argument. If it is missing, the extension registry is read from the standard input. + +Repository metadata is collected using the repository manager APIs. Currently only the GitHub API is supported. + +The output of the processing will be written to the standard output by default. The output can be saved to a file using the `-o/--out` flag. diff --git a/cmd/jq.go b/cmd/jq.go new file mode 100644 index 0000000..d1843e7 --- /dev/null +++ b/cmd/jq.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "github.com/itchyny/gojq" +) + +func jq(input interface{}, filter string, output func(interface{}) error) error { + query, err := gojq.Parse(filter) + if err != nil { + return err + } + + iter := query.Run(input) + + for { + v, ok := iter.Next() + if !ok { + break + } + + if err, ok := v.(error); ok { + if err, ok := err.(*gojq.HaltError); ok && err.Value() == nil { //nolint:errorlint + break + } + + return err + } + + if err := output(v); err != nil { + return err + } + } + + return nil +} diff --git a/cmd/k6registry/main.go b/cmd/k6registry/main.go index 0079bac..229cf2f 100644 --- a/cmd/k6registry/main.go +++ b/cmd/k6registry/main.go @@ -12,11 +12,18 @@ import ( var version = "dev" func main() { - runCmd(newCmd(os.Args[1:])) //nolint:forbidigo + log.SetFlags(0) + log.Writer() + + runCmd(newCmd(getArgs())) } func newCmd(args []string) *cobra.Command { - cmd := cmd.New() + cmd, err := cmd.New() + if err != nil { + log.Fatal(formatError(err)) + } + cmd.Version = version cmd.SetArgs(args) @@ -24,10 +31,65 @@ func newCmd(args []string) *cobra.Command { } func runCmd(cmd *cobra.Command) { - log.SetFlags(0) - log.Writer() - if err := cmd.Execute(); err != nil { log.Fatal(formatError(err)) } } + +//nolint:forbidigo +func isGitHubAction() bool { + return os.Getenv("GITHUB_ACTIONS") == "true" +} + +//nolint:forbidigo +func getArgs() []string { + if !isGitHubAction() { + return os.Args[1:] + } + + 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") + } + + if getenv("INPUT_RAW", "false") == "true" { + args = append(args, "--raw") + } + + if getenv("INPUT_YAML", "false") == "true" { + args = append(args, "--yaml") + } + + if out := getenv("INPUT_OUT", ""); len(out) != 0 { + args = append(args, "--out", out) + } + + args = append(args, getenv("INPUT_FILTER", ".")) + + args = append(args, getenv("INPUT_IN", "")) + + return args +} + +//nolint:forbidigo +func getenv(name string, defval string) string { + value, found := os.LookupEnv(name) + if found { + return value + } + + return defval +} diff --git a/cmd/legacy.go b/cmd/legacy.go new file mode 100644 index 0000000..2aeb194 --- /dev/null +++ b/cmd/legacy.go @@ -0,0 +1,204 @@ +package cmd + +import ( + "context" + "encoding/json" + "os" + "strings" + + "github.com/grafana/k6registry" + "gopkg.in/yaml.v3" +) + +type legacyRegistry struct { + Extensions []*legacyExtension `json:"extensions"` +} + +type legacyExtension struct { + Name string `json:"name"` + URL string `json:"url"` + Description string `json:"description"` + Tiers []string `json:"tiers"` + Type []string `json:"type"` +} + +func legacyConvert(ctx context.Context) error { + client, err := contextGitHubClient(ctx) + if err != nil { + return err + } + + content, _, _, err := client.Repositories.GetContents( + ctx, + "grafana", + "k6-docs", + "src/data/doc-extensions/extensions.json", + nil, + ) + if err != nil { + return err + } + + str, err := content.GetContent() + if err != nil { + return err + } + + var legacyReg legacyRegistry + + if err := json.Unmarshal([]byte(str), &legacyReg); err != nil { + return err + } + + reg := make([]*k6registry.Extension, 0, len(legacyReg.Extensions)) + + for _, legacyExt := range legacyReg.Extensions { + ext := new(k6registry.Extension) + + ext.Module = strings.TrimPrefix(legacyExt.URL, "https://") + ext.Description = legacyExt.Description + + for _, tier := range legacyExt.Tiers { + if strings.ToLower(tier) == "official" { + ext.Official = true + } + } + + for _, legacyType := range legacyExt.Type { + typ := strings.ToLower(legacyType) + + if typ == "javascript" { + name := strings.TrimPrefix(legacyExt.Name, "xk6-") + + ext.Imports = []string{"k6/x/" + name} + + continue + } + + name := strings.TrimPrefix(legacyExt.Name, "xk6-output") + name = strings.TrimPrefix(name, "xk6-") + + ext.Outputs = []string{name} + } + + legacyPatch(ext) + + reg = append(reg, ext) + } + + encoder := yaml.NewEncoder(os.Stdout) //nolint:forbidigo + + if err := encoder.Encode(reg); err != nil { + return err + } + + return nil +} + +func legacyPatch(ext *k6registry.Extension) { + override, found := extOverrides[ext.Module] + if !found { + panic("new module: " + ext.Module) + } + + if len(override.imports) != 0 { + if override.imports == "-" { + ext.Imports = nil + } else { + ext.Imports = []string{override.imports} + } + } + + if len(override.outputs) != 0 { + ext.Outputs = []string{override.outputs} + } + + if len(override.module) != 0 { + ext.Module = override.module + } +} + +type extOverride struct { + imports string + outputs string + module string +} + +var extOverrides = map[string]extOverride{ //nolint:gochecknoglobals + "github.com/AckeeCZ/xk6-google-iap": {imports: "k6/x/googleIap"}, + "github.com/BarthV/xk6-es": {outputs: "xk6-es"}, + "github.com/GhMartingit/xk6-mongo": {}, + "github.com/JorTurFer/xk6-input-prometheus": {imports: "k6/x/prometheusread"}, + "github.com/Juandavi1/xk6-prompt": {}, + "github.com/LeonAdato/xk6-output-statsd": {outputs: "output-statsd"}, + "github.com/Maksimall89/xk6-output-clickhouse": {}, + "github.com/NAlexandrov/xk6-tcp": {}, + "github.com/SYM01/xk6-proxy": {}, + "github.com/acuenca-facephi/xk6-read": {}, + "github.com/akiomik/xk6-nostr": {}, + "github.com/anycable/xk6-cable": {}, + "github.com/avitalique/xk6-file": {}, + "github.com/deejiw/xk6-gcp": {}, + "github.com/deejiw/xk6-interpret": {}, + "github.com/distribworks/xk6-ethereum": {}, + "github.com/domsolutions/xk6-fasthttp": {}, + "github.com/dynatrace/xk6-output-dynatrace": {outputs: "output-dynatrace"}, + "github.com/elastic/xk6-output-elasticsearch": {outputs: "output-elasticsearch"}, + "github.com/fornfrey/xk6-celery": {}, + "github.com/frankhefeng/xk6-oauth-pkce": {}, + "github.com/gjergjsheldija/xk6-mllp": {}, + "github.com/golioth/xk6-coap": {}, + "github.com/gpiechnik2/xk6-httpagg": {}, + "github.com/gpiechnik2/xk6-smtp": {}, + "github.com/grafana/xk6-client-prometheus-remote": {imports: "k6/x/remotewrite"}, + "github.com/grafana/xk6-client-tracing": {imports: "k6/x/tracing"}, + "github.com/grafana/xk6-dashboard": {}, + "github.com/grafana/xk6-disruptor": {}, + "github.com/grafana/xk6-exec": {}, + "github.com/grafana/xk6-kubernetes": {}, + "github.com/grafana/xk6-loki": {}, + "github.com/grafana/xk6-notification": {}, + "github.com/grafana/xk6-output-influxdb": {outputs: "xk6-influxdb"}, + "github.com/grafana/xk6-output-kafka": {outputs: "xk6-kafka"}, + "github.com/grafana/xk6-output-timescaledb": {}, + "github.com/grafana/xk6-sql": {}, + "github.com/grafana/xk6-ssh": {}, + "github.com/goharbor/xk6-harbor": {}, + "github.com/heww/xk6-harbor": {module: "github.com/goharbor/xk6-harbor"}, + "github.com/kelseyaubrecht/xk6-webtransport": {}, + "github.com/kubeshop/xk6-tracetest": {}, + "github.com/leonyork/xk6-output-timestream": {}, + "github.com/maksimall89/xk6-telegram": {}, + "github.com/martymarron/xk6-output-prometheus-pushgateway": {outputs: "output-prometheus-pushgateway"}, + "github.com/mcosta74/xk6-plist": {}, + "github.com/mostafa/xk6-kafka": {}, + "github.com/mridehalgh/xk6-sqs": {}, + "github.com/oleiade/xk6-kv": {}, + "github.com/patrick-janeiro/xk6-neo4j": {}, + "github.com/phymbert/xk6-sse": {}, + "github.com/pmalhaire/xk6-mqtt": {}, + "github.com/skibum55/xk6-git": {}, + "github.com/szkiba/xk6-ansible-vault": {}, + "github.com/szkiba/xk6-cache": {outputs: "cache", imports: "-"}, + "github.com/szkiba/xk6-chai": {}, + "github.com/szkiba/xk6-csv": {}, + "github.com/szkiba/xk6-dotenv": {}, + "github.com/szkiba/xk6-faker": {}, + "github.com/szkiba/xk6-g0": {}, + "github.com/szkiba/xk6-mock": {}, + "github.com/szkiba/xk6-output-plugin": {}, + "github.com/szkiba/xk6-prometheus": {}, + "github.com/szkiba/xk6-toml": {}, + "github.com/szkiba/xk6-top": {}, + "github.com/grafana/xk6-ts": {}, + "github.com/szkiba/xk6-ts": {module: "github.com/grafana/xk6-ts"}, + "github.com/szkiba/xk6-yaml": {}, + "github.com/thmshmm/xk6-opentelemetry": {}, + "github.com/thotasrinath/xk6-couchbase": {}, + "github.com/tmieulet/xk6-cognito": {}, + "github.com/walterwanderley/xk6-stomp": {}, + "github.com/nicholasvuono/xk6-playwright": {}, + "github.com/ydarias/xk6-nats": {}, + "go.k6.io/k6": {}, + "github.com/wosp-io/xk6-playwright": {module: "github.com/nicholasvuono/xk6-playwright"}, +} diff --git a/cmd/load.go b/cmd/load.go new file mode 100644 index 0000000..8b92733 --- /dev/null +++ b/cmd/load.go @@ -0,0 +1,143 @@ +package cmd + +import ( + "context" + "encoding/json" + "io" + "strings" + + "github.com/Masterminds/semver" + "github.com/google/go-github/v62/github" + "github.com/grafana/k6registry" + "gopkg.in/yaml.v3" +) + +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 := yaml.Unmarshal(raw, ®istry); 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 { + return nil, err + } + + registry[idx].Repo = repo + } + } + + if lint { + if err := validateWithLinter(registry); err != nil { + return nil, err + } + } + + bin, err := json.Marshal(registry) + if err != nil { + return nil, err + } + + var result []interface{} + + if err := json.Unmarshal(bin, &result); err != nil { + return nil, err + } + + return result, nil +} + +func loadGitHub(ctx context.Context, module string) (*k6registry.Repository, error) { + client, err := contextGitHubClient(ctx) + if err != nil { + return nil, err + } + + var owner, name string + + if module == k6Module { + owner = "grafana" + name = "k6" + } else { + parts := strings.SplitN(module, "/", 4) + + owner = parts[1] + name = parts[2] + } + + repo := new(k6registry.Repository) + + rep, _, err := client.Repositories.Get(ctx, owner, name) + if err != nil { + return nil, err + } + + repo.Topics = rep.Topics + + repo.Url = rep.GetHTMLURL() + repo.Name = rep.GetName() + repo.Owner = rep.GetOwner().GetLogin() + + repo.Homepage = rep.GetHomepage() + if len(repo.Homepage) == 0 { + 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 + } + + for _, tag := range tags { + name := tag.GetName() + + if _, err := semver.NewVersion(name); err != nil { + continue + } + + repo.Versions = append(repo.Versions, name) + } + + return repo, nil +} + +const ( + ghModulePrefix = "github.com/" + k6Module = "go.k6.io/k6" + k6Description = "A modern load testing tool, using Go and JavaScript" +) diff --git a/cmd/validate.go b/cmd/validate.go new file mode 100644 index 0000000..e10f9e7 --- /dev/null +++ b/cmd/validate.go @@ -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": {}, +} diff --git a/docs/example.yaml b/docs/example.yaml index 810175c..f13981a 100644 --- a/docs/example.yaml +++ b/docs/example.yaml @@ -11,10 +11,10 @@ cloud: true official: true -- module: github.com/grafana/xk6-distruptor +- module: github.com/grafana/xk6-disruptor description: Inject faults to test imports: - - k6/x/distruptor + - k6/x/disruptor official: true - module: github.com/szkiba/xk6-faker diff --git a/docs/legacy.yaml b/docs/legacy.yaml new file mode 100644 index 0000000..4d4eef9 --- /dev/null +++ b/docs/legacy.yaml @@ -0,0 +1,302 @@ +- module: github.com/AckeeCZ/xk6-google-iap + description: Provides access to Google Auth token + imports: + - k6/x/googleIap +- module: github.com/BarthV/xk6-es + description: Output test results to Elasticsearch + outputs: + - xk6-es +- module: github.com/GhMartingit/xk6-mongo + description: Load-test Mongo no-SQL databases + imports: + - k6/x/mongo +- module: github.com/JorTurFer/xk6-input-prometheus + description: Enables real-time input from prometheus + imports: + - k6/x/prometheusread +- module: github.com/Juandavi1/xk6-prompt + description: Support for input arguments via UI. + imports: + - k6/x/prompt +- module: github.com/LeonAdato/xk6-output-statsd + description: Enables real-time output of test metrics to a StatsD service + outputs: + - output-statsd +- module: github.com/Maksimall89/xk6-output-clickhouse + description: Export results to ClickHouse + outputs: + - -clickhouse +- module: github.com/NAlexandrov/xk6-tcp + description: Send data to TCP port + imports: + - k6/x/tcp +- module: github.com/SYM01/xk6-proxy + description: Add a dynamic proxy support to k6. Allow changing the HTTP proxy settings in the script. + imports: + - k6/x/proxy +- module: github.com/acuenca-facephi/xk6-read + description: Read files and directories + imports: + - k6/x/read +- module: github.com/akiomik/xk6-nostr + description: Interact with Nostr relays + imports: + - k6/x/nostr +- module: github.com/anycable/xk6-cable + description: Test Action Cable and AnyCable functionality + imports: + - k6/x/cable +- module: github.com/avitalique/xk6-file + description: Write files + imports: + - k6/x/file +- module: github.com/deejiw/xk6-gcp + description: A k6 extension for Google Cloud Platform services. + imports: + - k6/x/gcp +- module: github.com/deejiw/xk6-interpret + description: Interpret Go code + imports: + - k6/x/interpret +- module: github.com/distribworks/xk6-ethereum + description: K6 extension for ethereum protocols + imports: + - k6/x/ethereum +- module: github.com/domsolutions/xk6-fasthttp + description: Enable RPS increase & file streaming on HTTP/1.1 requests + imports: + - k6/x/fasthttp +- module: github.com/dynatrace/xk6-output-dynatrace + description: Export results to Dynatrace + outputs: + - output-dynatrace +- module: github.com/elastic/xk6-output-elasticsearch + description: Export results to Elasticsearch 8.x + outputs: + - output-elasticsearch +- module: github.com/fornfrey/xk6-celery + description: Generate load on Celery workers + imports: + - k6/x/celery +- module: github.com/frankhefeng/xk6-oauth-pkce + description: Generate OAuth PKCE code verifier and code challenge + imports: + - k6/x/oauth-pkce +- module: github.com/gjergjsheldija/xk6-mllp + description: Simple MLLP sender for k6 + imports: + - k6/x/mllp +- module: github.com/goharbor/xk6-harbor + description: Client for load testing Harbor container registry + imports: + - k6/x/harbor +- module: github.com/golioth/xk6-coap + description: Interact with Constrained Application Protocol endpoints. + imports: + - k6/x/coap +- module: github.com/gpiechnik2/xk6-httpagg + description: Aggregate HTTP requests into an HTML report + imports: + - k6/x/httpagg +- module: github.com/gpiechnik2/xk6-smtp + description: Use SMTP protocol to send emails + imports: + - k6/x/smtp +- module: github.com/grafana/xk6-client-prometheus-remote + description: Test Prometheus Remote Write performance + imports: + - k6/x/remotewrite +- module: github.com/grafana/xk6-client-tracing + description: Client for load testing distributed tracing backends + official: true + imports: + - k6/x/tracing +- module: github.com/grafana/xk6-dashboard + description: Create a web-based metrics dashboard + official: true + outputs: + - dashboard +- module: github.com/grafana/xk6-disruptor + description: "Inject faults to test \U0001F4A3" + official: true + imports: + - k6/x/disruptor +- module: github.com/grafana/xk6-exec + description: Run external commands + official: true + imports: + - k6/x/exec +- module: github.com/grafana/xk6-kubernetes + description: Interact with Kubernetes clusters + official: true + imports: + - k6/x/kubernetes +- module: github.com/grafana/xk6-loki + description: Client for load testing Loki + official: true + imports: + - k6/x/loki +- module: github.com/grafana/xk6-notification + description: Create notifications + official: true + imports: + - k6/x/notification +- module: github.com/grafana/xk6-output-influxdb + description: Export results to InfluxDB v2 + official: true + outputs: + - xk6-influxdb +- module: github.com/grafana/xk6-output-kafka + description: Export k6 results in real-time to Kafka + official: true + outputs: + - xk6-kafka +- module: github.com/grafana/xk6-output-timescaledb + description: Export k6 results to TimescaleDB + official: true + outputs: + - -timescaledb +- module: github.com/grafana/xk6-sql + description: Load-test SQL Servers (PostgreSQL, MySQL and SQLite3 for now) + official: true + imports: + - k6/x/sql +- module: github.com/grafana/xk6-ssh + description: SSH + official: true + imports: + - k6/x/ssh +- module: github.com/grafana/xk6-ts + description: Add TypeScript support for k6 + imports: + - k6/x/ts +- module: github.com/kelseyaubrecht/xk6-webtransport + description: Add support for webtransport protocol + imports: + - k6/x/webtransport +- module: github.com/kubeshop/xk6-tracetest + description: Support for Tracetest test execution and tracing client + outputs: + - tracetest +- module: github.com/leonyork/xk6-output-timestream + description: Export results to AWS Timestream + outputs: + - -timestream +- module: github.com/maksimall89/xk6-telegram + description: Interact with Telegram Bots + imports: + - k6/x/telegram +- module: github.com/martymarron/xk6-output-prometheus-pushgateway + description: Export results to Prometheus pushgateway + imports: + - k6/x/output-prometheus-pushgateway + outputs: + - output-prometheus-pushgateway +- module: github.com/mcosta74/xk6-plist + description: Parse/serialize property list (.plist) payloads + imports: + - k6/x/plist +- module: github.com/mostafa/xk6-kafka + description: Load-test Apache Kafka. Includes support for Avro messages + imports: + - k6/x/kafka +- module: github.com/mridehalgh/xk6-sqs + description: Produce to an SQS queue + imports: + - k6/x/sqs +- module: github.com/nicholasvuono/xk6-playwright + description: Browser automation and end-to-end web testing using Playwright + imports: + - k6/x/playwright +- module: github.com/oleiade/xk6-kv + description: Share key-value data between VUs + imports: + - k6/x/kv +- module: github.com/patrick-janeiro/xk6-neo4j + description: Interact with Neo4J graph databases + imports: + - k6/x/neo4j +- module: github.com/phymbert/xk6-sse + description: A k6 extension for Server-Sent Events (SSE) + imports: + - k6/x/sse +- module: github.com/pmalhaire/xk6-mqtt + description: mqtt extension + imports: + - k6/x/mqtt +- module: github.com/skibum55/xk6-git + description: Clone Git repositories from tests + imports: + - k6/x/git +- module: github.com/szkiba/xk6-ansible-vault + description: Encrypt and decrypt Ansible Vault + imports: + - k6/x/ansible-vault +- module: github.com/szkiba/xk6-cache + description: Enable vendoring remote HTTP modules to a single source-control-friendly file + outputs: + - cache +- module: github.com/szkiba/xk6-chai + description: Embed k6chaijs into the k6 binary + imports: + - k6/x/chai +- module: github.com/szkiba/xk6-csv + description: Parse CSV values + imports: + - k6/x/csv +- module: github.com/szkiba/xk6-dotenv + description: Load env vars from a .env file + imports: + - k6/x/dotenv +- module: github.com/szkiba/xk6-faker + description: Generate random fake data + imports: + - k6/x/faker +- module: github.com/szkiba/xk6-g0 + description: Write k6 tests in golang + imports: + - k6/x/g0 +- module: github.com/szkiba/xk6-mock + description: Mock HTTP(S) servers + imports: + - k6/x/mock +- module: github.com/szkiba/xk6-output-plugin + description: Write k6 output extension as a dynamically loadable plugin using your favorite programming language + outputs: + - -plugin +- module: github.com/szkiba/xk6-prometheus + description: Prometheus HTTP exporter for k6 + outputs: + - prometheus +- module: github.com/szkiba/xk6-toml + description: Encode and decode TOML values + imports: + - k6/x/toml +- module: github.com/szkiba/xk6-top + description: Updating the current k6 metrics summaries on the terminal during the test run + outputs: + - top +- module: github.com/szkiba/xk6-yaml + description: Encode and decode YAML values + imports: + - k6/x/yaml +- module: github.com/thmshmm/xk6-opentelemetry + description: Generate OpenTelemetry signals from within your test scripts + imports: + - k6/x/opentelemetry +- module: github.com/thotasrinath/xk6-couchbase + description: Load-test Couchbase no-SQL databases + imports: + - k6/x/couchbase +- module: github.com/tmieulet/xk6-cognito + description: Get a cognito access token using USER_SRP_AUTH flow + imports: + - k6/x/cognito +- module: github.com/walterwanderley/xk6-stomp + description: Client for STOMP protocol + imports: + - k6/x/stomp +- module: github.com/ydarias/xk6-nats + description: Provides NATS support for k6 tests + imports: + - k6/x/nats diff --git a/docs/registry.md b/docs/registry.md index fae9afe..35c4dce 100644 --- a/docs/registry.md +++ b/docs/registry.md @@ -77,13 +77,29 @@ The `true` value of the `official` flag indicates that the extension is official Repository metadata provided by the extension's git repository manager. Repository metadata are not registered, they are queried at processing time using the repository manager API. +#### Owner + +The `owner` property contains the owner of the extension's git repository. + +#### Name + +The `name` property contains the name of the extension's git repository. + +#### License + +The `license` property contains the SPDX ID of the extension's license. For more information about SPDX, visit https://spdx.org/licenses/ + +#### Public + +The `true` value of the `public` flag indicates that the repository is public, available to anyone. + #### URL The `url` property contains the URL of the repository. The `url` is provided by the repository manager and can be displayed in a browser. #### Homepage -The `homepage` property contains the the project homepage URL. If no homepage is set, the value is the same as the `url` property. +The `homepage` property contains the project homepage URL. If no homepage is set, the value is the same as the `url` property. #### Stars @@ -93,14 +109,16 @@ The `stars` property contains the number of stars in the extension's repository. The `topics` property contains the repository topics. Topics make it easier to find the repository. It is recommended to set the `xk6` topic to the extensions repository. -#### Tags - -The `tags` property contains the repository's git tags. States of the git repository marked with tags can be reproduced. Versions are also tags, they must meet certain format requirements. - #### Versions The `versions` property contains the list of supported versions. Versions are tags whose format meets the requirements of semantic versioning. Version tags often start with the letter `v`, which is not part of the semantic version. +#### Archived + +The `true` value of the `archived` flag indicates that the repository is archived, read only. + +If a repository is archived, it usually means that the owner has no intention of maintaining it. Such extensions should be removed from the registry. + ## Registry Processing The source of the registry is a YAML file optimized for human use. For programs that use the registry, it is advisable to generate an output in JSON format optimized for the given application (for example, an extension catalog for the k6build service). @@ -117,19 +135,22 @@ erDiagram "custom JSON" }|--|{ "application" : uses ``` -The registry is processed based on the popular `jq` expressions. Predefined `jq` filters should be included for common uses (for example, extension catalog generation). +The registry is processed based on the popular `jq` expressions. The input of the processing is the extension registry supplemented with repository metadata (for example, available versions, number of stars, etc). The output of the processing is defined by the `jq` filter. ### Registry Validation -The registry is validated using [JSON schema](https://grafana.github.io/k6registry/registry.schema.json). Requirements that cannot be validated using the JSON schema are validated using custom logic. +The registry is validated using [JSON schema](https://grafana.github.io/k6registry/registry.schema.json). Requirements that cannot be validated using the JSON schema are validated using custom linter. -Custom validation logic checks the following for each extension: +Custom linter checks the following for each extension: - - is the go module path valid? - - is there at least one versioned release? + - Is the go module path valid? + - Is there at least one versioned release? + - Is a valid license configured? + - Is the xk6 topic set for the repository? + - Is the repository not archived? -Validation is always done before processing. The noop filter ('.') can be used for validation only by ignoring the output. +Schema based validation is always done before processing. The noop filter ('.') can be used for validation only by ignoring (or muting) the output. -It is strongly recommended to validate the extension registry after each modification, but at least before approving the change. +It is strongly recommended to lint the extension registry after each modification, but at least before approving the change. diff --git a/docs/registry.schema.json b/docs/registry.schema.json index a3c5701..719fd2a 100644 --- a/docs/registry.schema.json +++ b/docs/registry.schema.json @@ -82,10 +82,22 @@ "repository": { "type": "object", "description": "Repository metadata.\n\nMetadata provided by the extension's git repository manager. Repository metadata are not registered, they are queried at runtime using the repository manager API.\n", + "required": [ + "name", + "owner", + "url" + ], "properties": { + "name": { + "type": "string", + "description": "The name of the repository.\n" + }, + "owner": { + "type": "string", + "description": "The owner of the repository.\n" + }, "url": { "type": "string", - "default": "", "description": "URL of the repository.\n\nThe URL is provided by the repository manager and can be displayed in a browser.\n" }, "homepage": { @@ -110,19 +122,27 @@ }, "description": "Repository topics.\n\nTopics make it easier to find the repository. It is recommended to set the xk6 topic to the extensions repository.\n" }, - "tags": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The repository's git tags.\n\nStates of the git repository marked with tags can be reproduced. Versions are also tags, they must meet certain format requirements.\n" - }, "versions": { "type": "array", "items": { "type": "string" }, "description": "List of supported versions.\n\nVersions are tags whose format meets the requirements of semantic versioning. Version tags often start with the letter `v`, which is not part of the semantic version.\n" + }, + "public": { + "type": "boolean", + "default": "false", + "description": "Public repository flag.\n\nA `true` value indicates that the repository is public, available to anyone.\n" + }, + "license": { + "type": "string", + "default": "", + "description": "The SPDX ID of the extension's license.\n\nFor more information about SPDX, visit https://spdx.org/licenses/\n" + }, + "archived": { + "type": "boolean", + "default": "false", + "description": "Archived repository flag.\n\nA `true` value indicates that the repository is archived, read only.\n\nIf a repository is archived, it usually means that the owner has no intention of maintaining it. Such extensions should be removed from the registry.\n" } } } diff --git a/docs/registry.schema.yaml b/docs/registry.schema.yaml index 5c1b82f..085e59c 100644 --- a/docs/registry.schema.yaml +++ b/docs/registry.schema.yaml @@ -98,10 +98,21 @@ $defs: Repository metadata. Metadata provided by the extension's git repository manager. Repository metadata are not registered, they are queried at runtime using the repository manager API. + required: + - name + - owner + - url properties: + name: + type: string + description: | + The name of the repository. + owner: + type: string + description: | + The owner of the repository. url: type: string - default: "" description: | URL of the repository. @@ -133,14 +144,6 @@ $defs: Repository topics. Topics make it easier to find the repository. It is recommended to set the xk6 topic to the extensions repository. - tags: - type: array - items: - type: string - description: | - The repository's git tags. - - States of the git repository marked with tags can be reproduced. Versions are also tags, they must meet certain format requirements. versions: type: array items: @@ -149,3 +152,26 @@ $defs: List of supported versions. Versions are tags whose format meets the requirements of semantic versioning. Version tags often start with the letter `v`, which is not part of the semantic version. + public: + type: boolean + default: "false" + description: | + Public repository flag. + + A `true` value indicates that the repository is public, available to anyone. + license: + type: string + default: "" + description: | + The SPDX ID of the extension's license. + + For more information about SPDX, visit https://spdx.org/licenses/ + archived: + type: boolean + default: "false" + description: | + Archived repository flag. + + A `true` value indicates that the repository is archived, read only. + + If a repository is archived, it usually means that the owner has no intention of maintaining it. Such extensions should be removed from the registry. diff --git a/go.mod b/go.mod index b646df0..a12b3d0 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,35 @@ module github.com/grafana/k6registry go 1.22 require ( + github.com/Masterminds/semver v1.5.0 + github.com/cli/go-gh/v2 v2.9.0 + github.com/google/go-github/v62 v62.0.0 github.com/grafana/clireadme v0.1.0 + github.com/itchyny/gojq v0.12.16 github.com/spf13/cobra v1.8.1 + github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/term v0.22.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cli/safeexec v1.0.0 // indirect + github.com/cli/shurcooL-graphql v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/henvic/httpretty v0.0.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/itchyny/timefmt-go v0.1.6 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 14d605b..8876987 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,84 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/cli/go-gh/v2 v2.9.0 h1:D3lTjEneMYl54M+WjZ+kRPrR5CEJ5BHS05isBPOV3LI= +github.com/cli/go-gh/v2 v2.9.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE= +github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= +github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= +github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= +github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= +github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/grafana/clireadme v0.1.0 h1:KYEYSnYdSzmHf3bufaK6fQZ5j4dzvM/T+G6Ba+qNnAM= github.com/grafana/clireadme v0.1.0/go.mod h1:Wy4KIG2ZBGMYAYyF9l7qAy+yoJVasqk/txsRgoRI3gc= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= +github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= +github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8= +github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/registry.go b/registry.go index 67eb0b3..f76656d 100644 --- a/registry.go +++ b/registry.go @@ -1,2 +1,9 @@ // Package k6registry contains the data model of the k6 extensions registry. package k6registry + +import _ "embed" + +// Schema contains JSON schema for registry JSON. +// +//go:embed docs/registry.schema.json +var Schema []byte diff --git a/registry_gen.go b/registry_gen.go index 1dc52e7..125a429 100644 --- a/registry_gen.go +++ b/registry_gen.go @@ -99,6 +99,15 @@ type Registry []Extension // are not registered, they are queried at runtime using the repository manager // API. type Repository struct { + // Archived repository flag. + // + // A `true` value indicates that the repository is archived, read only. + // + // If a repository is archived, it usually means that the owner has no intention + // of maintaining it. Such extensions should be removed from the registry. + // + Archived bool `json:"archived,omitempty" yaml:"archived,omitempty" mapstructure:"archived,omitempty"` + // Repository description. // Description string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` @@ -109,6 +118,26 @@ type Repository struct { // Homepage string `json:"homepage,omitempty" yaml:"homepage,omitempty" mapstructure:"homepage,omitempty"` + // The SPDX ID of the extension's license. + // + // For more information about SPDX, visit https://spdx.org/licenses/ + // + License string `json:"license,omitempty" yaml:"license,omitempty" mapstructure:"license,omitempty"` + + // The name of the repository. + // + Name string `json:"name" yaml:"name" mapstructure:"name"` + + // The owner of the repository. + // + Owner string `json:"owner" yaml:"owner" mapstructure:"owner"` + + // Public repository flag. + // + // A `true` value indicates that the repository is public, available to anyone. + // + Public bool `json:"public,omitempty" yaml:"public,omitempty" mapstructure:"public,omitempty"` + // The number of stars in the extension's repository. // // The extension's popularity is indicated by how many users have starred the @@ -116,13 +145,6 @@ type Repository struct { // Stars int `json:"stars,omitempty" yaml:"stars,omitempty" mapstructure:"stars,omitempty"` - // The repository's git tags. - // - // States of the git repository marked with tags can be reproduced. Versions are - // also tags, they must meet certain format requirements. - // - Tags []string `json:"tags,omitempty" yaml:"tags,omitempty" mapstructure:"tags,omitempty"` - // Repository topics. // // Topics make it easier to find the repository. It is recommended to set the xk6 @@ -135,7 +157,7 @@ type Repository struct { // The URL is provided by the repository manager and can be displayed in a // browser. // - Url string `json:"url,omitempty" yaml:"url,omitempty" mapstructure:"url,omitempty"` + Url string `json:"url" yaml:"url" mapstructure:"url"` // List of supported versions. // diff --git a/releases/v0.1.1.md b/releases/v0.1.1.md index 9a50147..85badd6 100644 --- a/releases/v0.1.1.md +++ b/releases/v0.1.1.md @@ -1,3 +1,3 @@ k6registry `v0.1.1` is here 🎉! -This is an internal maintenance release that introduces CLI functionality. +This is an internal maintenance release that introduces CLI functionality and GitHub action. diff --git a/tools/gendoc/main.go b/tools/gendoc/main.go index 3d373a0..c1f8b3e 100644 --- a/tools/gendoc/main.go +++ b/tools/gendoc/main.go @@ -7,6 +7,6 @@ import ( ) func main() { - root := cmd.New() + root, _ := cmd.New() clireadme.Main(root, 1) }