A => LICENSE +13 -0
@@ 1,13 @@
+Copyright 2020 Evan Tann
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
A => README.md +62 -0
@@ 1,62 @@
+# inventory2config
+
+Takes an inventory.json and a service-based SRP config file as inputs and
+outputs an SRP config file pointing at specific IPs.
+
+Example inventory.json:
+
+```json
+{"10.128.0.1":["my-app"]}
+```
+
+Example service-based SRP config file:
+
+```json
+{
+ "Services": {
+ "www.example.com": {
+ "HealthPath": "/health",
+ "Service": "my-app",
+ "Port": 3000
+ },
+ "www-example.internal": {
+ "HealthPath": "/health",
+ "Service": "my-app",
+ "Port": 3001
+ }
+ }
+}
+```
+
+To use:
+
+```bash
+inventory2config -i inventory.json -s config.json > config.json
+```
+
+This outputs a config.json with backends populated with the correct ip:port
+combinations like so:
+
+```json
+{
+ "Services": {
+ "www.example.com": {
+ "HealthPath": "/health",
+ "Service": "my-app",
+ "Port": 3000,
+ "Backends": ["10.128.0.1:3000"]
+ },
+ "www-example.internal": {
+ "HealthPath": "/health",
+ "Service": "my-app",
+ "Port": 3001,
+ "Backends": ["10.128.0.1:3001"]
+ }
+ }
+}
+```
+
+SRP will ignore the Service and Port. They are added for this tool only.
+
+In the event of an error, inventory2json will still output the original file to
+stdout, so no data is lost.
A => go.mod +5 -0
@@ 1,5 @@
+module git.sr.ht/~egtann/inventory2config
+
+go 1.14
+
+require git.sr.ht/~egtann/up v0.0.0-20200324172709-10f0fb584d43
A => go.sum +2 -0
@@ 1,2 @@
+git.sr.ht/~egtann/up v0.0.0-20200324172709-10f0fb584d43 h1:0dsb/Oivfxhd0HDxbIx5SzefqcaYeqL+TWihyNMaLsk=
+git.sr.ht/~egtann/up v0.0.0-20200324172709-10f0fb584d43/go.mod h1:z51L8eNGLPT0f3d/+8danxnrOmuXRh4Ca9HcviG2D0M=
A => main.go +97 -0
@@ 1,97 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ "git.sr.ht/~egtann/up"
+)
+
+func main() {
+ // Always send to stdout our new SRP or the original version
+ srpOut, err := run()
+ fmt.Printf(srpOut)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
+
+func run() (string, error) {
+ // Read an inventory file and an SRP config file
+ var (
+ invFile = flag.String("i", "inventory.json", "inventory file")
+ srpFile = flag.String("s", "config.json", "srp config file")
+ )
+ flag.Parse()
+
+ srpByt, err := ioutil.ReadFile(*srpFile)
+ if err != nil {
+ return "", fmt.Errorf("read srp config: %w", err)
+ }
+ origSRP := string(srpByt)
+ conf := srpConfig{}
+ if err = json.Unmarshal(srpByt, &conf); err != nil {
+ return origSRP, fmt.Errorf("decode srp config: %w", err)
+ }
+
+ inventory, err := parseInventory(*invFile)
+ if err != nil {
+ return origSRP, fmt.Errorf("parse inventory: %w", err)
+ }
+
+ // Gee willikers, Batman, this is O(N^3)
+ for ip, serviceNames := range inventory {
+ middle:
+ for _, name := range serviceNames {
+ for _, backend := range conf.Services {
+ if backend.Service != name {
+ continue
+ }
+ if ip == "" {
+ continue
+ }
+ ipPort := fmt.Sprintf("%s:%d", ip, backend.Port)
+ if backend.Port == 0 {
+ ipPort = ip
+ }
+ backend.Backends = append(backend.Backends, ipPort)
+ conf.Services[name] = backend
+ break middle
+ }
+ }
+ }
+
+ buf := &bytes.Buffer{}
+ enc := json.NewEncoder(buf)
+ enc.SetIndent("", "\t")
+ if err := enc.Encode(conf); err != nil {
+ return origSRP, fmt.Errorf("encode config: %w", err)
+ }
+
+ // TODO(egtann) output the original config via stdout if any error
+ // happens.
+ //
+ // TODO(egtann) on success, output the config formatted for human
+ // editing (i.e. prettify)
+
+ return string(buf.Bytes()), nil
+}
+
+func parseInventory(filename string) (up.Inventory, error) {
+ fi, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer func() { _ = fi.Close() }()
+
+ inv := up.Inventory{}
+ if err = json.NewDecoder(fi).Decode(&inv); err != nil {
+ return nil, fmt.Errorf("decode: %w", err)
+ }
+ return inv, nil
+}
A => srp.go +20 -0
@@ 1,20 @@
+package main
+
+type srpConfig struct {
+ Services map[string]*backend
+ API struct{ Subnet string }
+}
+
+type backend struct {
+ HealthPath string `json:",omitempty"`
+ Backends []string `json:",omitempty"`
+ Redirect *redirect `json:",omitempty"`
+ Service string `json:",omitempty"`
+ Port int `json:",omitempty"`
+}
+
+type redirect struct {
+ URL string `json:",omitempty"`
+ Permanent bool `json:",omitempty"`
+ DiscardPath bool `json:",omitempty"`
+}