From 4bb39c91990a00370de1b1e8a39fffe93df9a3c7 Mon Sep 17 00:00:00 2001 From: Mya Pitzeruse Date: Wed, 25 May 2022 23:17:08 -0500 Subject: [PATCH] feat(catalog): support exporting catalog to different output formats --- README.md | 33 ++++++++++++++++++++++++---- catalog/serve.go | 57 +++++++++++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 8c218ce..9e3ac43 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ _The minimal, declarative service catalog._ -Pronounced "MC" as in the master of ceremonies. - ## Background In the last three jobs I've worked at, it's always been a hassle trying to locate the various dashboards, documentation, @@ -60,11 +58,38 @@ func main() { Once you've built your catalog, you can easily run a landing page by executing the catalog file. -``` +```sh $ go run ./catalog.go ``` This starts a web server for you to interact with on `localhost:8080`. If `:8080` is already in use, you can configure the bind address by passing the `-bind_address` flag with the desired host and port. -![Screenshot](screenshot.png) +
+ Screenshot +
+ +### Exporting your catalog + +Instead of needing to compile a binary or host your catalog using `go run`, you can export your catalog to HTML or JSON. +This makes it easy to drop into existing self-host platforms or leverage with other popular systems. + +```sh +$ go run ./catalog.go -output html > index.html +$ go run ./catalog.go -output json > catalog.json +``` + +### Protecting your catalog using oauth-proxy + +Regardless of how you host your catalog, you'll likely want to protect access to it. An easy way to do this is using the +[oauth-proxy][] project. This project provides common OAuth2 client functionality to any project, making it easy to +require authentication in order to access a system / project. + + + +Until I have more of a concrete guide, you can follow my setup [here](https://github.com/mjpitz/mjpitz/blob/main/infra/helm/catalog/values.yaml). +A simple analogy to this deployment would be a docker compose file with two services, one for the oauth-proxy and the +other for the catalog (bound to 127.0.0.1). Using the new `-output` functionality, this deployment could definitely +be simplified. + +[oauth-proxy]: https://oauth2-proxy.github.io/oauth2-proxy diff --git a/catalog/serve.go b/catalog/serve.go index 729aa35..002f93b 100644 --- a/catalog/serve.go +++ b/catalog/serve.go @@ -6,10 +6,13 @@ package catalog import ( "bytes" _ "embed" + "encoding/json" "flag" "html/template" + "io" "log" "net/http" + "os" "time" "github.com/mjpitz/emc/catalog/service" @@ -18,6 +21,11 @@ import ( //go:embed index.html.tpl var catalog string +var funcs = map[string]any{ + "mod": func(mod, v int) int { return v % mod }, + "eq": func(exp, act int) bool { return exp == act }, +} + // Spec defines the requirements for hosting a catalog. type Spec struct { Services []service.Spec @@ -36,39 +44,44 @@ func Service(label string, options ...service.Option) Option { // Serve provides command line functionality for running the service catalog. func Serve(options ...Option) { addr := flag.String("bind_address", "127.0.0.1:8080", "the address the service should bind to when serving content") + output := flag.String("output", "", "set to output the catalog to stdout (valid html, json)") flag.Parse() start := time.Now() - t := template.Must(template.New("catalog"). - Funcs(map[string]any{ - "mod": func(mod, v int) int { - return v % mod - }, - "eq": func(exp, act int) bool { - return exp == act - }, - }). - Parse(catalog)) - spec := Spec{} for _, opt := range options { opt(&spec) } - html := bytes.NewBuffer(nil) - err := t.Execute(html, spec) - if err != nil { - panic(err) + buffer := bytes.NewBuffer(nil) + var err error + + switch { + case output == nil || *output == "" || *output == "html": + err = template.Must(template.New("catalog").Funcs(funcs).Parse(catalog)).Execute(buffer, spec) + case *output == "json": + err = json.NewEncoder(buffer).Encode(spec) } - http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - http.ServeContent(w, r, "", start, bytes.NewReader(html.Bytes())) - }) - - log.Printf("serving on %s\n", *addr) - err = http.ListenAndServe(*addr, http.DefaultServeMux) if err != nil { - panic(err) + log.Fatal("failed to render output", err) + } + + if output != nil && *output != "" { + _, err = io.Copy(os.Stdout, bytes.NewReader(buffer.Bytes())) + if err != nil { + log.Fatal("failed to write buffer to stdout", err) + } + } else { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + http.ServeContent(w, r, "", start, bytes.NewReader(buffer.Bytes())) + }) + + log.Printf("serving on %s\n", *addr) + err = http.ListenAndServe(*addr, http.DefaultServeMux) + if err != nil { + log.Fatal("failed to serve content", err) + } } }