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)
+
+
+
+
+### 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)
+ }
}
}