diff --git a/cmd/grr/workflow.go b/cmd/grr/workflow.go index f4cb32ff..078908ca 100644 --- a/cmd/grr/workflow.go +++ b/cmd/grr/workflow.go @@ -360,7 +360,7 @@ func serveCmd(registry grizzly.Registry) *cli.Command { cmd := &cli.Command{ Use: "serve ", Short: "Run Grizzly server", - Args: cli.ArgsRange(0, 1), + Args: cli.ArgsAny(), } var opts Opts @@ -379,6 +379,10 @@ func serveCmd(registry grizzly.Registry) *cli.Command { if len(args) > 0 { resourcesPath = args[0] } + watchPaths := []string{resourcesPath} + if len(args) > 1 { + watchPaths = args[1:] + } targets := currentContext.GetTargets(opts.Targets) parser := grizzly.DefaultParser(registry, targets, opts.JsonnetPaths, grizzly.ParserContinueOnError(true)) @@ -400,7 +404,7 @@ func serveCmd(registry grizzly.Registry) *cli.Command { server.SetContext(currentContext.Name) server.SetFormatting(onlySpec, format) if opts.Watch { - server.Watch() + server.Watch(watchPaths) } if opts.OpenBrowser { server.OpenBrowser() diff --git a/docs/content/server.md b/docs/content/server.md index 8652eaa3..60f46475 100644 --- a/docs/content/server.md +++ b/docs/content/server.md @@ -3,24 +3,77 @@ date: 2024-03-14 title: Grizzly Server --- ## An HTTP Server for editing and reviewing -When we manage Grafana dashboards on disk, the Grizzly server makes easy to +When we manage Grafana dashboards on disk, the "Grizzly Server" makes easy to edit and review these resources within an actual Grafana instance, but without needing to publish the dashboard to Grafana. Grafana provides the UI and datasources to make dashboards look right, Grizzly provides the dashboard, directly from local disk. -If the file on disk is writeable (i.e. a pure YAML or JSON file - Jsonnet cannot be written), -then clicking the `Save` icon in Grafana will update your local dashboard file. It will not -change anything in Grafana itself. +The Grizzly server is configured the same way as other Grizzly commands. See +the [configuration section](../configuration)) for more details. -With a Grafana instance configured (as described in the [configuration section](../configuration)), -you can run Grizzly against one or more local files and it will start up an +### Starting the Grizzly Server +The Grizzly server can be started with this command: + +``` +grr serve +``` + +This will start a server, by default listening on port 8080, but it will not have any resources available +for you. It can be opened at http://localhost:8080. With the `-b` argument, Grizzly will open a browser +window. With `-p`, you can change the port that the Grizzly server listens on. + +The next sections will explain the four main scenarios for which the Grizzly server is useful. + +### Editing JSON or YAML files in Grafana +You can run Grizzly against one or more local files and it will start up an HTTP server: ``` -grr serve +grr serve examples/yaml/ ``` -By default, this starts an HTTP server on [http://localhost:8080](http://localhost:8080). -Visiting this URL will show a list of the resources found in . For -now, this is limited to Grafana Dashboards only. +By default, visit http://localhost:8080 to view the Grizzly server. + +### Reviewing changes to JSON or YAML files in Grafana +If you are editing the resources on disk, and just want to use Grafana for review, then use the inbuilt +"watch" functionality. With the below, if any files are changed on disk within the directory identified, +(here, `examples/yaml`), the dashboard will be reloaded within Grafana: + +``` +grr serve -w examples/yaml +``` + +This could be useful if, for example, you use another language (other than jsonnet) to render your +JSON/YAML and want to see the outcomes in Grafana. + +### Reviewing changes to your Jsonnet scripts in Grafana +If you are working with Jsonnet, and your jsonnet codebase covers more than one file, you can specify +the entrypoint for your Jsonnet and the directory to watch independently: + +``` +grr serve -w examples/grr.jsonnet examples +``` + +If your sources are in multiple directories, you can watch multiple sources, e.g: + +``` +grr serve -w examples/grr.jsonnet examples/*.*sonnet examples/vendor +``` + +### Reviewing changes to code in other languages in Grafana +The [Grafana Foundation SDK](https://github.com/grafana/grafana-foundation-sdk) provides libraries in a +range of languages that can be used to render Grafana dashboards. Watching changes to these with Grizzly +is a two stage process, currently requiring an additional tool to watch for changes to source code and +render your dashboard(s) to files. One such tool is [entr](https://github.com/eradman/entr), which can be +used like so (with the Foundation SDK's TypeScript support): + +``` +cd grafana-foundation-sdk/examples/typescript/red-method +npm install +find . | entr -s 'npm run -s dev > ts.json' +``` +Then, in another window: +``` +grr serve -w ts.json +``` diff --git a/pkg/grizzly/server.go b/pkg/grizzly/server.go index a56a7384..e5e7e86d 100644 --- a/pkg/grizzly/server.go +++ b/pkg/grizzly/server.go @@ -30,6 +30,7 @@ type Server struct { Resources Resources UserAgent string ResourcePath string + WatchPaths []string OnlySpec bool OutputFormat string watch bool @@ -79,8 +80,9 @@ func (s *Server) OpenBrowser() { s.openBrowser = true } -func (s *Server) Watch() { +func (s *Server) Watch(watchPaths []string) { s.watch = true + s.WatchPaths = watchPaths } func (s *Server) SetFormatting(onlySpec bool, outputFormat string) { @@ -190,7 +192,13 @@ func (s *Server) Start() error { if err != nil { return err } - err = watcher.Watch(s.ResourcePath) + for _, path := range s.WatchPaths { + err = watcher.Add(path) + if err != nil { + return err + } + } + err = watcher.Watch() if err != nil { return err } diff --git a/pkg/grizzly/watch.go b/pkg/grizzly/watch.go index e81f6fbf..3cdabc81 100644 --- a/pkg/grizzly/watch.go +++ b/pkg/grizzly/watch.go @@ -26,7 +26,7 @@ func NewWatcher(watcherFunc func(path string) error) (*Watcher, error) { return &watcher, nil } -func (w *Watcher) Watch(path string) error { +func (w *Watcher) Add(path string) error { stat, err := os.Stat(path) if err != nil { return err @@ -51,6 +51,9 @@ func (w *Watcher) Watch(path string) error { return err } } + return nil +} +func (w *Watcher) Watch() error { go func() { log.Info("Watching for changes") for { diff --git a/pkg/grizzly/workflow.go b/pkg/grizzly/workflow.go index 9b49aa4b..d3004c27 100644 --- a/pkg/grizzly/workflow.go +++ b/pkg/grizzly/workflow.go @@ -505,7 +505,11 @@ func Watch(registry Registry, watchDir string, resourcePath string, parser Parse if err != nil { return err } - err = watcher.Watch(watchDir) + err = watcher.Add(watchDir) + if err != nil { + return err + } + err = watcher.Watch() if err != nil { return err }