Skip to content

Commit b3ea1e9

Browse files
authored
5 add tag to all objects created or updated by the script (#6)
* #5 get or create tag * #5 add tag with update * #5 only add tag when needed
1 parent dcb59ce commit b3ea1e9

File tree

6 files changed

+163
-35
lines changed

6 files changed

+163
-35
lines changed

cmd/netbox-oxidized-sync/netbox-oxidized-sync.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
func worker(id int, jobs <-chan httphelper.OxidizedNode, results chan<- int, netboxdevices *[]model.NetboxDevice, oxidizedhttp *httphelper.OxidizedHTTPClient, netboxhttp *httphelper.NetboxHTTPClient) {
1616
for j := range jobs {
17-
log.Printf("Got oxided device: '%s' on worker %s",j.Name, strconv.Itoa(id), )
17+
log.Printf("Got oxided device: '%s' on worker %s", j.Name, strconv.Itoa(id))
1818

1919
idx := slices.IndexFunc(*netboxdevices, func(c model.NetboxDevice) bool { return c.Name == j.Name })
2020
if idx == -1 {
@@ -37,7 +37,7 @@ func worker(id int, jobs <-chan httphelper.OxidizedNode, results chan<- int, net
3737
}
3838
interfacesToUpdate := netboxparser.ParseFortigateInterfaces(fortigateInterfaces, &netboxInterfaceForDevice, strconv.Itoa(netboxDevice.ID))
3939
netboxhttp.UpdateOrCreateInferface(&interfacesToUpdate, &netboxVlansForSite, netboxDevice.Site.ID, netboxDevice.Tenant.ID)
40-
40+
4141
default:
4242
log.Printf("Model '%s' currently not supported", j.Model)
4343
}
@@ -63,7 +63,7 @@ func loadOxidizedDevices(oxidizedhttp *httphelper.OxidizedHTTPClient, netboxhttp
6363
go worker(w, jobs, results, &devices, oxidizedhttp, netboxhttp)
6464
}
6565

66-
for _, element := range nodes {
66+
for _, element := range nodes {
6767
jobs <- element
6868
}
6969
close(jobs)
@@ -84,5 +84,7 @@ func main() {
8484
netboxhttp := httphelper.NewNetbox(conf.Netbox.BaseURL, conf.Netbox.APIKey, conf.Netbox.Roles)
8585
oxidizedhttp := httphelper.NewOxidized(conf.Oxidized.BaseURL, conf.Oxidized.Username, conf.Oxidized.Password)
8686

87+
netboxhttp.GetManagedTag(conf.Netbox.TagName)
88+
8789
loadOxidizedDevices(&oxidizedhttp, &netboxhttp)
8890
}

configs/example.settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
"netbox": {
33
"base_url": "http://localhost:8000",
44
"api_key": "xxxx-xxxx-xxxx",
5-
"roles": ""
5+
"roles": "",
6+
"tag-name": "oxidized-sync"
67
},
78
"oxidized": {
89
"base_url": "http://localhost:8001",

internal/confighelper/confighelper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type Config struct {
1111
BaseURL string `json:"base_url"`
1212
APIKey string `json:"api_key"`
1313
Roles string `json:"roles"`
14+
TagName string `json:"tag-name"`
1415
} `json:"netbox"`
1516
Oxidized struct {
1617
BaseURL string `json:"base_url"`

internal/httphelper/netbox.go

Lines changed: 111 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,45 +20,56 @@ type netboxResult struct {
2020
}
2121

2222
type interfacePatchData struct {
23-
Description string `json:"description,omitempty"`
24-
Enabled *bool `json:"enabled,omitempty"`
25-
Parent int `json:"parent,omitempty"`
26-
Lag int `json:"lag,omitempty"`
27-
Bridge int `json:"bridge,omitempty"`
28-
InterfaceType string `json:"type,omitempty"`
29-
Mode string `json:"mode,omitempty"`
30-
UntaggedVlan int `json:"untagged_vlan,omitempty"`
23+
Description string `json:"description,omitempty"`
24+
Enabled *bool `json:"enabled,omitempty"`
25+
Parent int `json:"parent,omitempty"`
26+
Lag int `json:"lag,omitempty"`
27+
Bridge int `json:"bridge,omitempty"`
28+
InterfaceType string `json:"type,omitempty"`
29+
Mode string `json:"mode,omitempty"`
30+
UntaggedVlan int `json:"untagged_vlan,omitempty"`
31+
Tags []string `json:"tags,omitempty"`
3132
}
3233

3334
type interfacePostData struct {
34-
Device int `json:"device"`
35-
Name string `json:"name"`
36-
InterfaceType string `json:"type"`
37-
Description string `json:"description,omitempty"`
38-
Enabled *bool `json:"enabled,omitempty"`
39-
UntaggedVlan int `json:"untagged_vlan,omitempty"`
40-
Mode string `json:"mode,omitempty"`
41-
Parent int `json:"parent,omitempty"`
42-
Bridge int `json:"bridge,omitempty"`
43-
Lag int `json:"lag,omitempty"`
35+
Device int `json:"device"`
36+
Name string `json:"name"`
37+
InterfaceType string `json:"type"`
38+
Description string `json:"description,omitempty"`
39+
Enabled *bool `json:"enabled,omitempty"`
40+
UntaggedVlan int `json:"untagged_vlan,omitempty"`
41+
Mode string `json:"mode,omitempty"`
42+
Parent int `json:"parent,omitempty"`
43+
Bridge int `json:"bridge,omitempty"`
44+
Lag int `json:"lag,omitempty"`
45+
Tags []string `json:"tags,omitempty"`
4446
}
4547

4648
type vlanPostData struct {
47-
SiteId int `json:"site,omitempty"`
48-
TenantId int `json:"tenant,omitempty"`
49-
VlanId int `json:"vid"`
50-
Name string `json:"name"`
49+
SiteId int `json:"site,omitempty"`
50+
TenantId int `json:"tenant,omitempty"`
51+
VlanId int `json:"vid"`
52+
Name string `json:"name"`
53+
Tags []string `json:"tags,omitempty"`
54+
}
55+
56+
type tagPostData struct {
57+
Name string `json:"name"`
58+
Slug string `json:"slug"`
59+
Color string `json:"color,omitempty"`
60+
Description string `json:"description,omitempty"`
5161
}
5262

5363
type netboxData interface {
54-
model.NetboxInterface | model.NetboxDevice | model.NetboxVlan
64+
model.NetboxInterface | model.NetboxDevice | model.NetboxVlan | model.NetboxTag
5565
}
5666

5767
type NetboxHTTPClient struct {
5868
apikey string
5969
baseurl string
6070
client http.Client
6171
rolesfilter string
72+
defaultTag model.NetboxTag
6273
}
6374

6475
func NewNetbox(baseurl string, apikey string, roles string) NetboxHTTPClient {
@@ -80,10 +91,68 @@ func NewNetbox(baseurl string, apikey string, roles string) NetboxHTTPClient {
8091
rolesfilter = sb.String()
8192
}
8293

83-
e := NetboxHTTPClient{apikey, baseurl, *client, rolesfilter}
94+
e := NetboxHTTPClient{apikey, baseurl, *client, rolesfilter, model.NetboxTag{}}
8495
return e
8596
}
8697

98+
func (e *NetboxHTTPClient) GetManagedTag(tagName string) {
99+
tag, err := getNetboxTagByName(tagName, e)
100+
if err != nil {
101+
slog.Error("Error getting tags", err)
102+
}
103+
if tag.ID == 0 {
104+
newTag := e.createNetboxTag(tagName)
105+
e.defaultTag = newTag
106+
} else {
107+
e.defaultTag = tag
108+
}
109+
}
110+
111+
func getNetboxTagByName(tagName string, e *NetboxHTTPClient) (model.NetboxTag, error) {
112+
requestURL := fmt.Sprintf("%s/api/extras/tags/", e.baseurl)
113+
tags, err := apiRequest[model.NetboxTag](requestURL, e)
114+
if err != nil {
115+
return model.NetboxTag{}, err
116+
}
117+
for _, iface := range tags {
118+
if iface.Name == tagName {
119+
return iface, nil
120+
}
121+
}
122+
return model.NetboxTag{}, nil
123+
}
124+
125+
func slugify(input string) string {
126+
var result string
127+
128+
result = strings.ToLower(input)
129+
result = strings.Replace(result, " ", "-", -1)
130+
131+
return result
132+
}
133+
134+
func (e *NetboxHTTPClient) createNetboxTag(tagName string) model.NetboxTag {
135+
var postData tagPostData
136+
postData.Name = tagName
137+
postData.Slug = slugify(tagName)
138+
postData.Description = "Auto generated tag to track objects created by the oxidized sync"
139+
postData.Color = "72599f"
140+
141+
data, _ := json.Marshal(postData)
142+
requestURL := fmt.Sprintf("%s/api/extras/tags/", e.baseurl)
143+
resBody, err := TokenAuthHTTPPost(requestURL, e.apikey, &e.client, data)
144+
if err != nil {
145+
slog.Error(err.Error())
146+
}
147+
148+
var result model.NetboxTag
149+
err = json.Unmarshal(resBody, &result)
150+
if err != nil {
151+
slog.Error(err.Error())
152+
}
153+
return result
154+
}
155+
87156
func loopAPIRequest(path string, e *NetboxHTTPClient) (netboxResult, error) {
88157
resBody, err := TokenAuthHTTPGet(path, e.apikey, &e.client)
89158
if err != nil {
@@ -165,6 +234,7 @@ func (e *NetboxHTTPClient) createVlan(SiteId int, TenantId int, VlanId int, Name
165234
postData.SiteId = SiteId
166235
postData.VlanId = VlanId
167236
postData.TenantId = TenantId
237+
postData.Tags = []string{strconv.Itoa(e.defaultTag.ID)}
168238

169239
data, _ := json.Marshal(postData)
170240
requestURL := fmt.Sprintf("%s/api/ipam/vlans/", e.baseurl)
@@ -232,6 +302,10 @@ func (e *NetboxHTTPClient) updateInterface(port model.NetboxInterfaceUpdateCreat
232302
if port.PortTypeUpdate == "virtual" {
233303
patchData.InterfaceType = "virtual"
234304
}
305+
if port.PortTypeUpdate == "bridge" {
306+
patchData.InterfaceType = "bridge"
307+
}
308+
235309
}
236310
if port.VlanMode != "" {
237311
patchData.Mode = port.VlanMode
@@ -248,6 +322,17 @@ func (e *NetboxHTTPClient) updateInterface(port model.NetboxInterfaceUpdateCreat
248322
}
249323
}
250324

325+
if len(port.Tags) != 0 {
326+
for _,tag := range port.Tags {
327+
if tag != strconv.Itoa(e.defaultTag.ID) {
328+
patchData.Tags = append(patchData.Tags, tag)
329+
}
330+
}
331+
patchData.Tags = append(patchData.Tags, strconv.Itoa(e.defaultTag.ID))
332+
} else {
333+
patchData.Tags = []string{strconv.Itoa(e.defaultTag.ID)}
334+
}
335+
251336
data, _ := json.Marshal(patchData)
252337
requestURL := fmt.Sprintf("%s/%s%s/", e.baseurl, "api/dcim/interfaces/", port.InterfaceId)
253338
_, err := TokenAuthHTTPPatch(requestURL, e.apikey, &e.client, data)
@@ -331,6 +416,8 @@ func (e *NetboxHTTPClient) createInterface(port model.NetboxInterfaceUpdateCreat
331416
}
332417
}
333418

419+
postData.Tags = []string{strconv.Itoa(e.defaultTag.ID)}
420+
334421
data, _ := json.Marshal(postData)
335422
requestURL := fmt.Sprintf("%s/%s", e.baseurl, "api/dcim/interfaces/")
336423
_, err := TokenAuthHTTPPost(requestURL, e.apikey, &e.client, data)

internal/model/DeviceInterface.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ type NetboxInterface struct {
3333
Label string `json:"label"`
3434
} `json:"type"`
3535
Enabled bool `json:"enabled"`
36-
Parent interface{} `json:"parent"`
36+
Parent struct{
37+
ID int `json:"id"`
38+
Name string `json:"name"`
39+
} `json:"parent"`
3740
Bridge struct {
3841
ID int `json:"id"`
3942
Name string `json:"name"`
@@ -91,8 +94,15 @@ type NetboxInterface struct {
9194
ConnectedEndpoints interface{} `json:"connected_endpoints"`
9295
ConnectedEndpointsType interface{} `json:"connected_endpoints_type"`
9396
ConnectedEndpointsReachable interface{} `json:"connected_endpoints_reachable"`
94-
Tags []interface{} `json:"tags"`
95-
CustomFields struct {
97+
Tags []struct {
98+
ID int `json:"id"`
99+
URL string `json:"url"`
100+
Display string `json:"display"`
101+
Name string `json:"name"`
102+
Slug string `json:"slug"`
103+
Color string `json:"color"`
104+
} `json:"tags"`
105+
CustomFields struct {
96106
} `json:"custom_fields"`
97107
Created time.Time `json:"created"`
98108
LastUpdated time.Time `json:"last_updated"`
@@ -221,6 +231,8 @@ type NetboxInterfaceUpdateCreate struct {
221231
VlanMode string
222232
VlanId string
223233
InterfaceId string
234+
Tags []string
235+
Matched bool
224236
}
225237

226238
type NetboxVlan struct {
@@ -261,3 +273,17 @@ type NetboxVlan struct {
261273
LastUpdated time.Time `json:"last_updated"`
262274
PrefixCount int `json:"prefix_count"`
263275
}
276+
277+
type NetboxTag struct {
278+
ID int `json:"id"`
279+
URL string `json:"url"`
280+
Display string `json:"display"`
281+
Name string `json:"name"`
282+
Slug string `json:"slug"`
283+
Color string `json:"color"`
284+
Description string `json:"description"`
285+
ObjectTypes []string `json:"object_types"`
286+
TaggedItems int `json:"tagged_items"`
287+
Created time.Time `json:"created"`
288+
LastUpdated time.Time `json:"last_updated"`
289+
}

internal/netboxparser/fortitonetbox.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ func processPort(port model.FortigateInterface, allMembers map[string]int, forti
3131
Name: port.Name,
3232
PortType: port.InterfaceType,
3333
InterfaceId: strconv.Itoa(netboxInterface.ID),
34+
Matched : true,
35+
}
36+
37+
if len(netboxInterface.Tags) != 0 {
38+
for _, tag := range netboxInterface.Tags {
39+
matched.Tags = append(matched.Tags, strconv.Itoa(tag.ID))
40+
}
3441
}
3542

3643
if port.InterfaceType == lagName && netboxInterface.Type.Value != "lag" {
@@ -72,8 +79,10 @@ func processPort(port model.FortigateInterface, allMembers map[string]int, forti
7279
}
7380
if port.InterfaceType == "vlan" {
7481
if port.Parent != "" {
75-
matched.Parent = port.Parent
76-
matched.ParentId = getParentID(matched.Parent, netboxDeviceInterfaces)
82+
matched.ParentId = getParentID(port.Parent, netboxDeviceInterfaces)
83+
if matched.ParentId != strconv.Itoa(netboxInterface.Parent.ID) {
84+
matched.Parent = port.Parent
85+
}
7786
}
7887

7988
if netboxInterface.Mode.Value != "access" {
@@ -97,7 +106,7 @@ func processPort(port model.FortigateInterface, allMembers map[string]int, forti
97106
}
98107
}
99108

100-
if matched == (model.NetboxInterfaceUpdateCreate{}) {
109+
if !matched.Matched {
101110
if port.InterfaceType == "physical" && port.Name != "modem" && !strings.HasPrefix(port.Name, "npu") {
102111
matched.Mode = "create"
103112
matched.Name = port.Name
@@ -146,7 +155,9 @@ func processPort(port model.FortigateInterface, allMembers map[string]int, forti
146155
}
147156
} else {
148157
if matched.Description != "" || matched.Status != "" || matched.PortTypeUpdate != "" || matched.Parent != "" || matched.VlanMode != "" {
149-
matched.Mode = "update"
158+
if !strings.HasPrefix(port.Parent, "npu") {
159+
matched.Mode = "update"
160+
}
150161
}
151162
}
152163
return matched

0 commit comments

Comments
 (0)