diff options
Diffstat (limited to 'clients/netbox/config_file_client.go')
-rw-r--r-- | clients/netbox/config_file_client.go | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/clients/netbox/config_file_client.go b/clients/netbox/config_file_client.go new file mode 100644 index 0000000..6023916 --- /dev/null +++ b/clients/netbox/config_file_client.go | |||
@@ -0,0 +1,151 @@ | |||
1 | package netbox | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "encoding/json" | ||
6 | "fmt" | ||
7 | "io/fs" | ||
8 | "net" | ||
9 | "path/filepath" | ||
10 | |||
11 | "github.com/mitchellh/mapstructure" | ||
12 | "gopkg.in/yaml.v2" | ||
13 | ) | ||
14 | |||
15 | type ConfigFileNetboxClient struct { | ||
16 | cfg map[string]any | ||
17 | } | ||
18 | |||
19 | var _ NetboxClient = (*ConfigFileNetboxClient)(nil) | ||
20 | |||
21 | // NewConfigFileClient creates a new ConfigFileNetboxClient by loading a | ||
22 | // named config file from a filesystem and unmarshalling it. The config | ||
23 | // file can be in JSON or YAML format, determined by a .json, .yaml, or | ||
24 | // .yml extension. The configuration must be nested within a key in that | ||
25 | // file to support sharing the file with other subsystems. | ||
26 | // | ||
27 | // See method docs for the extected format of this file. | ||
28 | func NewConfigFileClient(filesystem fs.FS, name, key string) (NetboxClient, error) { | ||
29 | fp, err := filesystem.Open(name) | ||
30 | if err != nil { | ||
31 | return nil, err | ||
32 | } | ||
33 | defer fp.Close() | ||
34 | |||
35 | fc := map[string]any{} | ||
36 | |||
37 | switch e := filepath.Ext(name); e { | ||
38 | case ".yaml", ".yml": | ||
39 | if err := yaml.NewDecoder(fp).Decode(&fc); err != nil { | ||
40 | return nil, err | ||
41 | } | ||
42 | case ".json": | ||
43 | if err := json.NewDecoder(fp).Decode(&fc); err != nil { | ||
44 | return nil, err | ||
45 | } | ||
46 | default: | ||
47 | return nil, fmt.Errorf("Config files with extension %s are not supported", e) | ||
48 | } | ||
49 | |||
50 | ck, ok := fc[key] | ||
51 | if !ok { | ||
52 | return nil, fmt.Errorf("Key %s does not exist in config file", key) | ||
53 | } | ||
54 | |||
55 | cfg := map[string]any{} | ||
56 | if err := mapstructure.Decode(ck, &cfg); err != nil { | ||
57 | return nil, fmt.Errorf("Config file key was not of correct type: %w", err) | ||
58 | } | ||
59 | |||
60 | return &ConfigFileNetboxClient{ | ||
61 | cfg: cfg, | ||
62 | }, nil | ||
63 | } | ||
64 | |||
65 | // GetSitePrefixesWithTag gets a list of IP prefixes for a site based on | ||
66 | // some tag. | ||
67 | // | ||
68 | // The data format of the config file should be, under the key "sites": | ||
69 | // | ||
70 | // map[string]map[string][]string{ | ||
71 | // "site_name": map[string][]string{ | ||
72 | // "tag_name": []string{ | ||
73 | // "127.0.0.1/24", | ||
74 | // }, | ||
75 | // }, | ||
76 | // } | ||
77 | // | ||
78 | // In JSON format: | ||
79 | // | ||
80 | // { "sites": { | ||
81 | // "site": { | ||
82 | // "tag_name": [ "127.0.0.1/24" ] | ||
83 | // } | ||
84 | // } | ||
85 | func (c *ConfigFileNetboxClient) GetSitePrefixesWithTag(ctx context.Context, site string, tag string) ([]*net.IPNet, error) { | ||
86 | s, ok := c.cfg["sites"] | ||
87 | if !ok { | ||
88 | return nil, fmt.Errorf("No key 'sites' in config file") | ||
89 | } | ||
90 | |||
91 | sites := map[string]map[string][]string{} | ||
92 | if err := mapstructure.Decode(s, &sites); err != nil { | ||
93 | return nil, fmt.Errorf("Key 'sites' not in correct format: %w", err) | ||
94 | } | ||
95 | |||
96 | tags, ok := sites[site] | ||
97 | if !ok { | ||
98 | return nil, fmt.Errorf("Site %s not in sites", site) | ||
99 | } | ||
100 | |||
101 | values, ok := tags[tag] | ||
102 | if !ok { | ||
103 | return nil, fmt.Errorf("Tag %s not in tags", tag) | ||
104 | } | ||
105 | |||
106 | return parseValues(values) | ||
107 | } | ||
108 | |||
109 | // GetPrefixesWithTag gets a list fo IP prefixes based on some tag. | ||
110 | // | ||
111 | // The data format of the config file should be, under the key "tags": | ||
112 | // | ||
113 | // map[string][]string{ | ||
114 | // "tag_name": []string{ | ||
115 | // "127.0.0.1/24", | ||
116 | // }, | ||
117 | // } | ||
118 | // | ||
119 | // In JSON format: | ||
120 | // | ||
121 | // { "tags": { "tag_name": [ "127.0.0.1/24" ] } } | ||
122 | func (c *ConfigFileNetboxClient) GetPrefixesWithTag(ctx context.Context, tag string) ([]*net.IPNet, error) { | ||
123 | t, ok := c.cfg["tags"] | ||
124 | if !ok { | ||
125 | return nil, fmt.Errorf("No key 'tags' in config file") | ||
126 | } | ||
127 | |||
128 | tags := map[string][]string{} | ||
129 | if err := mapstructure.Decode(t, &tags); err != nil { | ||
130 | return nil, fmt.Errorf("Key 'tags' not in correct format: %w", err) | ||
131 | } | ||
132 | |||
133 | values, ok := tags[tag] | ||
134 | if !ok { | ||
135 | return nil, fmt.Errorf("Tag %s not in tags", tag) | ||
136 | } | ||
137 | |||
138 | return parseValues(values) | ||
139 | } | ||
140 | |||
141 | func parseValues(v []string) ([]*net.IPNet, error) { | ||
142 | out := make([]*net.IPNet, len(v)) | ||
143 | for i, n := range v { | ||
144 | _, in, err := net.ParseCIDR(n) | ||
145 | if err != nil { | ||
146 | return nil, err | ||
147 | } | ||
148 | out[i] = in | ||
149 | } | ||
150 | return out, nil | ||
151 | } | ||