Skip to content

Commit be66aa4

Browse files
committed
DeviceSite, DeviceRegion and DeviceTags properties added to the version output
- filter by site_name allowed too
1 parent 0a595cf commit be66aa4

File tree

7 files changed

+93
-6
lines changed

7 files changed

+93
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ resource_types:
4343
check_every: never
4444
source:
4545
repository: registry.fqdn/org/concourse-netbox-resource
46-
tag: 0.1.0-20250619163905
46+
tag: 0.1.0
4747

4848
resources:
4949
- name: example.netbox
@@ -55,6 +55,7 @@ resources:
5555
token: "your-api-token"
5656
filter:
5757
site_name: ["site 1"]
58+
region_name: ["region 1"]
5859
tag: ["tag1"]
5960
role: ["server"]
6061
device_id: [123]

internal/app/check.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The output will be a JSON array of objects with their latest versions.
2525
},
2626
"filter": {
2727
"site_name": ["My Site"],
28+
"region_name": ["My Region"],
2829
"tag": ["my-tag"],
2930
"role": ["server"],
3031
"device_id": [123],

internal/concourse/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ type Version struct {
2727
DeviceId string `json:"device_id,omitempty"`
2828
DeviceName string `json:"device_name"`
2929
DeviceRole string `json:"device_role"`
30+
DeviceSite string `json:"device_site"`
31+
DeviceRegion string `json:"device_region,omitempty"`
32+
DeviceTags string `json:"device_tags,omitempty"`
3033
DeviceApiUrl string `json:"device_api_url,omitempty"`
3134
DeviceDisplayUrl string `json:"device_display_url,omitempty"`
3235
ConfigContext string `json:"config_context,omitempty"`

internal/filter/netbox.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package filter
22

33
type NetboxObject struct {
44
SiteName []string `json:"site_name,omitempty"`
5+
RegionName []string `json:"region_name,omitempty"`
56
Tag []string `json:"tag,omitempty"`
67
Role []string `json:"role,omitempty"`
78
DeviceId []int32 `json:"device_id,omitempty"`

internal/helper/tests.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ var (
9999
DeviceDisplayUrl string = "http://netbox.example.local/dcim/devices"
100100
DeviceRoleId int32 = 8
101101
DeviceRoleSlug string = "server"
102+
DeviceSiteId int32 = 1
103+
DeviceSiteSlug string = "test-site"
102104
)
103105

104106
func EnsureFolder(path string) error {

internal/netbox/config_context_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package netbox
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"testing"
@@ -27,13 +28,18 @@ func TestConfigContextParsing(t *testing.T) {
2728
role.SetId(helper.DeviceRoleId)
2829
role.SetSlug(helper.DeviceRoleSlug)
2930

31+
site := netbox.BriefSite{}
32+
site.SetId(helper.DeviceSiteId)
33+
site.SetSlug(helper.DeviceSiteSlug)
34+
3035
device.Id = helper.DeviceId
3136
device.Url = helper.DeviceApiUrl + fmt.Sprintf("/%d/", device.Id)
3237
device.Display = helper.DeviceName
3338
displayUrl := helper.DeviceDisplayUrl + fmt.Sprintf("/%d/", device.Id)
3439
device.DisplayUrl = &displayUrl
3540
device.LastUpdated = updatedTime
3641
device.Role = role
42+
device.Site = site
3743
device.SetConfigContext(helper.NetBoxConfigContextData)
3844

3945
// Test cases
@@ -71,7 +77,7 @@ func TestConfigContextParsing(t *testing.T) {
7177
}
7278

7379
currentDevice := *device
74-
result, err := populateDeviceDetails(helper.DeviceName, input, currentDevice)
80+
result, err := populateDeviceDetails(helper.DeviceName, input, currentDevice, context.Background())
7581
if err != nil {
7682
t.Fatalf("Error in populateDeviceDetails: %v", err)
7783
}

internal/netbox/query.go

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var (
2020
referenceTime time.Time
2121
output []concourse.Version
2222
err error
23+
siteRegionCache map[int32]string
2324
)
2425

2526
func Query(input concourse.Input, ctx context.Context) ([]concourse.Version, error) {
@@ -35,6 +36,12 @@ func Query(input concourse.Input, ctx context.Context) ([]concourse.Version, err
3536
return nil, fmt.Errorf("error during device query: %w", err)
3637
}
3738

39+
// Pre-fetch all site regions to avoid per-device API calls
40+
siteRegionCache, err = fetchSiteRegions(client, ctx)
41+
if err != nil {
42+
return nil, fmt.Errorf("error during site region query: %w", err)
43+
}
44+
3845
output, err = fetchDetailsFromDeviceList(input, deviceList, ctx)
3946
if err != nil {
4047
return nil, fmt.Errorf("error during device details query: %w", err)
@@ -47,6 +54,9 @@ func createDeviceQuery(client *netbox.APIClient, netboxFilter filter.NetboxObjec
4754
if len(netboxFilter.SiteName) > 0 {
4855
query = query.Site(netboxFilter.SiteName)
4956
}
57+
if len(netboxFilter.RegionName) > 0 {
58+
query = query.Region(netboxFilter.RegionName)
59+
}
5060
if len(netboxFilter.Tag) > 0 {
5161
query = query.Tag(netboxFilter.Tag)
5262
}
@@ -149,12 +159,12 @@ func fetchDetailsFromDeviceList(input concourse.Input, deviceList []netbox.Devic
149159
return nil, fmt.Errorf("error during server interface query: %w", err)
150160
}
151161

152-
output, err = populateInterfaceDetails(name, input, d, interfaceList)
162+
output, err = populateInterfaceDetails(name, input, d, interfaceList, ctx)
153163
if err != nil {
154164
return nil, fmt.Errorf("error during server interface details query: %w", err)
155165
}
156166
} else {
157-
output, err = populateDeviceDetails(name, input, d)
167+
output, err = populateDeviceDetails(name, input, d, ctx)
158168
if err != nil {
159169
return nil, fmt.Errorf("error during device details query: %w", err)
160170
}
@@ -167,7 +177,7 @@ func fetchDetailsFromDeviceList(input concourse.Input, deviceList []netbox.Devic
167177
return output, nil
168178
}
169179

170-
func populateInterfaceDetails(name string, input concourse.Input, device netbox.DeviceWithConfigContext, interfaceList []netbox.Interface) ([]concourse.Version, error) {
180+
func populateInterfaceDetails(name string, input concourse.Input, device netbox.DeviceWithConfigContext, interfaceList []netbox.Interface, ctx context.Context) ([]concourse.Version, error) {
171181
for _, iface := range interfaceList {
172182
lastUpdatedTime, referenceTime, err = getTimestamps(device, input)
173183
if err != nil {
@@ -195,13 +205,19 @@ func populateInterfaceDetails(name string, input concourse.Input, device netbox.
195205
interfaceDisplayUrl = *iface.DisplayUrl
196206
}
197207

208+
deviceRegion := getSiteRegion(device.Site.GetId())
209+
deviceTags := getDeviceTags(device)
210+
198211
output = append(output, concourse.Version{
199212
Id: fmt.Sprintf("%d", iface.Id),
200213
LastUpdated: lastUpdatedTime.Format(time.RFC3339),
201214
ObjectType: "interfaces",
202215
DeviceId: fmt.Sprintf("%d", device.Id),
203216
DeviceName: name,
204217
DeviceRole: device.Role.GetSlug(),
218+
DeviceSite: device.Site.GetSlug(),
219+
DeviceRegion: deviceRegion,
220+
DeviceTags: deviceTags,
205221
DeviceApiUrl: device.Url,
206222
DeviceDisplayUrl: deviceDisplayUrl,
207223
ConfigContext: configContext,
@@ -215,7 +231,7 @@ func populateInterfaceDetails(name string, input concourse.Input, device netbox.
215231
return output, nil
216232
}
217233

218-
func populateDeviceDetails(name string, input concourse.Input, device netbox.DeviceWithConfigContext) ([]concourse.Version, error) {
234+
func populateDeviceDetails(name string, input concourse.Input, device netbox.DeviceWithConfigContext, ctx context.Context) ([]concourse.Version, error) {
219235
lastUpdatedTime, referenceTime, err = getTimestamps(device, input)
220236
if err != nil {
221237
return nil, fmt.Errorf("error parsing netbox timestamps because of: %w", err)
@@ -237,12 +253,18 @@ func populateDeviceDetails(name string, input concourse.Input, device netbox.Dev
237253
displayUrl = *device.DisplayUrl
238254
}
239255

256+
deviceRegion := getSiteRegion(device.Site.GetId())
257+
deviceTags := getDeviceTags(device)
258+
240259
output = append(output, concourse.Version{
241260
Id: fmt.Sprintf("%d", device.Id),
242261
LastUpdated: lastUpdatedTime.Format(time.RFC3339),
243262
ObjectType: "devices",
244263
DeviceName: name,
245264
DeviceRole: device.Role.GetSlug(),
265+
DeviceSite: device.Site.GetSlug(),
266+
DeviceRegion: deviceRegion,
267+
DeviceTags: deviceTags,
246268
DeviceApiUrl: device.Url,
247269
DeviceDisplayUrl: displayUrl,
248270
ConfigContext: configContext,
@@ -298,3 +320,54 @@ func getTimestamps(device any, input concourse.Input) (*time.Time, time.Time, er
298320
}
299321
return lastUpdatedTime, referenceTime, nil
300322
}
323+
324+
func fetchSiteRegions(client *netbox.APIClient, ctx context.Context) (map[int32]string, error) {
325+
cache := make(map[int32]string)
326+
if client == nil {
327+
return cache, nil
328+
}
329+
330+
limit := int32(100)
331+
offset := int32(0)
332+
for {
333+
query := client.DcimAPI.DcimSitesList(ctx).Limit(limit).Offset(offset)
334+
siteResponse, _, err := query.Execute()
335+
if err != nil {
336+
return nil, fmt.Errorf("error during DcimSitesList query: %w", err)
337+
}
338+
for _, site := range siteResponse.Results {
339+
region := ""
340+
if site.Region.IsSet() && site.Region.Get() != nil {
341+
region = site.Region.Get().GetSlug()
342+
}
343+
cache[site.Id] = region
344+
}
345+
if !siteResponse.Next.IsSet() || siteResponse.Next.Get() == nil || *siteResponse.Next.Get() == "" || len(siteResponse.Results) == 0 {
346+
break
347+
}
348+
offset += limit
349+
}
350+
return cache, nil
351+
}
352+
353+
func getSiteRegion(siteId int32) string {
354+
if siteRegionCache == nil {
355+
return ""
356+
}
357+
if region, ok := siteRegionCache[siteId]; ok {
358+
return region
359+
}
360+
return ""
361+
}
362+
363+
func getDeviceTags(device netbox.DeviceWithConfigContext) string {
364+
tags := device.GetTags()
365+
if len(tags) == 0 {
366+
return ""
367+
}
368+
tagSlugs := make([]string, 0, len(tags))
369+
for _, tag := range tags {
370+
tagSlugs = append(tagSlugs, tag.GetSlug())
371+
}
372+
return strings.Join(tagSlugs, ",")
373+
}

0 commit comments

Comments
 (0)