Skip to content

Commit

Permalink
Merge pull request #8 from grafana/7-implement-cli
Browse files Browse the repository at this point in the history
implement cli
  • Loading branch information
szkiba authored Jul 26, 2024
2 parents 969da3d + 4357e80 commit 646f59f
Show file tree
Hide file tree
Showing 24 changed files with 1,478 additions and 60 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/build
/coverage.txt
/k6registry
/k6registry.exe
23 changes: 20 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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
Expand All @@ -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
```
8 changes: 8 additions & 0 deletions Dockerfile.goreleaser
Original file line number Diff line number Diff line change
@@ -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"]
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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] <jq filter> [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
```

<!-- #endregion cli -->
Expand Down
49 changes: 49 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -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
137 changes: 132 additions & 5 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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] <jq filter> [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
}
61 changes: 61 additions & 0 deletions cmd/context.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 646f59f

Please sign in to comment.