Skip to content

Commit 729ef5a

Browse files
committed
Add provider compatibility matrix with automated generation
This adds a comprehensive provider compatibility matrix showing which API types are supported by each network device provider (Cisco NX-OS, Cisco IOS-XR, and OpenConfig). The matrix is automatically generated using Go reflection to check interface implementation, ensuring accuracy without manual maintenance. The detailed compatibility matrix lives in docs/provider-compatibility.md, following Kubernetes-style documentation practices by separating reference documentation from the main README. The README contains a concise summary with a link to the full matrix. A new make target 'provider-matrix' regenerates the documentation by checking which provider interfaces (InterfaceProvider, BGPProvider, etc.) each provider actually implements. Key improvements over manual tracking: compile-time type safety, runtime accuracy through reflection, automatic detection of implementations, and minimal maintenance burden. The script correctly identifies 22 API types for NXOS, 1 for IOS-XR, and 1 for OpenConfig. Also includes updates from go-makefile-maker regeneration (improved golangci-lint rules, restored custom typos configuration) and fixes copyright year to 2026.
1 parent 43348c0 commit 729ef5a

File tree

9 files changed

+320
-2
lines changed

9 files changed

+320
-2
lines changed

.github/workflows/checks.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
with:
3838
version: latest
3939
- name: Delete pre-installed shellcheck
40-
run: sudo rm -f $(which shellcheck)
40+
run: sudo rm -f "$(which shellcheck)"
4141
- name: Run shellcheck
4242
run: make run-shellcheck
4343
- name: Dependency Licenses Review

.golangci.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,20 @@ linters:
136136
excludes:
137137
# gosec wants us to set a short ReadHeaderTimeout to avoid Slowloris attacks, but doing so would expose us to Keep-Alive race conditions (see https://iximiuz.com/en/posts/reverse-proxy-http-keep-alive-and-502s/
138138
- G112
139+
# if we put a password or token into a serialized payload, guess what, we probably did that on purpose
140+
- G117
141+
# this triggers on net/http.Request.ParseForm() and its callers, e.g. net/http.Request.FormValue(), complaining about potential memory exhaustion from unbounded form parsing;
142+
# but that is incorrect, ParseForm() by default never parses more than 10 MiB for this specific reason
143+
- G120
139144
# created file permissions are restricted by umask if necessary
140145
- G306
146+
# the following lints cause false-positives in many repositories, should be fixed with the next release. (see https://github.com/securego/gosec/issues/1500)
147+
- G701
148+
- G702
149+
- G703
150+
- G704
151+
- G705
152+
- G706
141153
govet:
142154
disable:
143155
- fieldalignment

.license-scan-overrides.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
{"name": "github.com/mattn/go-localereader", "licenceType": "MIT"}
77
{"name": "github.com/miekg/dns", "licenceType": "BSD-3-Clause"}
88
{"name": "github.com/pashagolub/pgxmock/v4", "licenceType": "BSD-3-Clause"}
9+
{"name": "github.com/pashagolub/pgxmock/v5", "licenceType": "BSD-3-Clause"}
910
{"name": "github.com/spdx/tools-golang", "licenceTextOverrideFile": "vendor/github.com/spdx/tools-golang/LICENSE.code"}
1011
{"name": "github.com/xeipuuv/gojsonpointer", "licenceType": "Apache-2.0"}
1112
{"name": "github.com/xeipuuv/gojsonreference", "licenceType": "Apache-2.0"}

.typos.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# SPDX-FileCopyrightText: 2026 SAP SE
2+
#
23
# SPDX-License-Identifier: Apache-2.0
4+
35
[default]
46
extend-ignore-re = [
57
"Cisco-IOS-XR.*"

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ docs: install-crd-ref-docs
189189
-e 's/#xrcisconetworkingmetalironcoredevv1alpha1/#xr-cisco-networking-metal-ironcore-dev-v1alpha1/g' \
190190
docs/api-reference/index.md
191191

192+
# Generate provider implementation matrix
193+
provider-matrix: FORCE
194+
@printf "\e[1;36m>> go run ./hack/generate-provider-matrix.go > docs/provider-compatibility.md\e[0m\n"
195+
@go run ./hack/generate-provider-matrix.go > docs/provider-compatibility.md
196+
192197
install-goimports: FORCE
193198
@if ! hash goimports 2>/dev/null; then printf "\e[1;36m>> Installing goimports (this may take a while)...\e[0m\n"; go install golang.org/x/tools/cmd/goimports@latest; fi
194199

@@ -310,7 +315,7 @@ license-headers: FORCE install-addlicense
310315
@printf "\e[1;36m>> addlicense (for license headers on source code files)\e[0m\n"
311316
@printf "%s\0" $(patsubst $(shell awk '$$1 == "module" {print $$2}' go.mod)%,.%/*.go,$(shell go list ./...)) | $(XARGS) -0 -I{} bash -c 'year="$$(grep 'Copyright' {} | head -n1 | grep -E -o '"'"'[0-9]{4}(-[0-9]{4})?'"'"')"; if [[ -z "$$year" ]]; then year=$$(date +%Y); fi; gawk -i inplace '"'"'{if (display) {print} else {!/^\/\*/ && !/^\*/}}; {if (!display && $$0 ~ /^(package |$$)/) {display=1} else { }}'"'"' {}; addlicense -c "SAP SE or an SAP affiliate company and IronCore contributors" -s=only -y "$$year" -- {}; $(SED) -i '"'"'1s+// Copyright +// SPDX-FileCopyrightText: +'"'"' {}; '
312317
@printf "\e[1;36m>> reuse annotate (for license headers on other files)\e[0m\n"
313-
@reuse lint -j | jq -r '.non_compliant.missing_licensing_info[]' | grep -vw vendor | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company and IronCore contributors' -l Apache-2.0 --skip-unrecognised
318+
@reuse lint -j | jq -r '.non_compliant.missing_licensing_info[]' | sed '/\<vendor\>/d' | $(XARGS) reuse annotate -c 'SAP SE or an SAP affiliate company and IronCore contributors' -l Apache-2.0 --skip-unrecognised
314319
@printf "\e[1;36m>> reuse download --all\e[0m\n"
315320
@reuse download --all
316321
@printf "\e[1;35mPlease review the changes. If *.license files were generated, consider instructing go-makefile-maker to add overrides to REUSE.toml instead.\e[0m\n"

Makefile.maker.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,8 @@ verbatim: |
232232
-e 's/#xecisconetworkingmetalironcoredevv1alpha1/#xe-cisco-networking-metal-ironcore-dev-v1alpha1/g' \
233233
-e 's/#xrcisconetworkingmetalironcoredevv1alpha1/#xr-cisco-networking-metal-ironcore-dev-v1alpha1/g' \
234234
docs/api-reference/index.md
235+
236+
# Generate provider implementation matrix
237+
provider-matrix: FORCE
238+
@printf "\e[1;36m>> go run ./hack/generate-provider-matrix.go > docs/provider-compatibility.md\e[0m\n"
239+
@go run ./hack/generate-provider-matrix.go > docs/provider-compatibility.md

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ Users can just run kubectl apply -f <URL for YAML BUNDLE> to install the project
9898
kubectl apply -f https://raw.githubusercontent.com/<org>/network-operator/<tag or branch>/dist/install.yaml
9999
```
100100

101+
## Supported Providers
102+
103+
network-operator supports multiple network device providers with varying levels of feature support:
104+
105+
- **Cisco NX-OS** - Full support (23+ API types including BGP, OSPF, ISIS, EVPN, VLANs, and more)
106+
- **Cisco IOS-XR** - Limited support (Interface configuration)
107+
- **OpenConfig** - Limited support (Interface configuration via vendor-neutral OpenConfig YANG models)
108+
109+
For a detailed compatibility matrix showing which API types are supported by each provider, see [Provider Compatibility Matrix](docs/provider-compatibility.md).
110+
101111
## Support, Feedback, Contributing
102112

103113
This project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](https://github.com/ironcore-dev/network-operator/issues). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).

docs/provider-compatibility.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!--
2+
SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
3+
SPDX-License-Identifier: Apache-2.0
4+
-->
5+
6+
# Provider Compatibility Matrix
7+
8+
This document provides a detailed overview of which API types are supported by each network device provider.
9+
10+
<!-- To regenerate this matrix, run: make provider-matrix -->
11+
12+
## Cisco NX-OS
13+
14+
Cisco NX-OS provides comprehensive support for network configuration through the network-operator.
15+
16+
**Status**: Full support (22 API types)
17+
18+
## Cisco IOS-XR
19+
20+
Cisco IOS-XR support is currently in early development.
21+
22+
**Status**: Limited support (1 API type)
23+
24+
## OpenConfig
25+
26+
OpenConfig provides a vendor-neutral configuration interface using standard OpenConfig YANG models.
27+
28+
**Status**: Limited support (1 API type)
29+
30+
## Detailed Compatibility Matrix
31+
32+
| API Type | NXOS | IOS-XR | OpenConfig |
33+
|----------|--------|--------|--------|
34+
| ACL | ✔️ |||
35+
| Banner | ✔️ |||
36+
| BGP | ✔️ |||
37+
| BGP Peer | ✔️ |||
38+
| Certificate | ✔️ |||
39+
| DNS | ✔️ |||
40+
| EVPN Instance | ✔️ |||
41+
| Interface | ✔️ | ✔️ | ✔️ |
42+
| ISIS | ✔️ |||
43+
| LLDP | ✔️ |||
44+
| Management Access | ✔️ |||
45+
| NTP | ✔️ |||
46+
| NVE | ✔️ |||
47+
| OSPF | ✔️ |||
48+
| PIM | ✔️ |||
49+
| Prefix Set | ✔️ |||
50+
| Routing Policy | ✔️ |||
51+
| SNMP | ✔️ |||
52+
| Syslog | ✔️ |||
53+
| User | ✔️ |||
54+
| VLAN | ✔️ |||
55+
| VRF | ✔️ |||
56+
57+
**Legend:**
58+
- ✔️ Implemented
59+
- — Not implemented
60+
61+
## Contributing
62+
63+
To add support for a new API type or provider:
64+
65+
1. Define the provider interface in `internal/provider/`
66+
2. Implement the interface methods in your provider package
67+
3. Run `make provider-matrix` to regenerate this document
68+
4. Submit a pull request with your changes
69+
70+
The matrix is automatically generated by checking which provider interfaces
71+
each provider implements, ensuring accuracy and eliminating manual maintenance.

hack/generate-provider-matrix.go

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package main
5+
6+
import (
7+
"fmt"
8+
"reflect"
9+
"sort"
10+
"strings"
11+
12+
"github.com/ironcore-dev/network-operator/internal/provider"
13+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/iosxr"
14+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/nxos"
15+
"github.com/ironcore-dev/network-operator/internal/provider/openconfig"
16+
)
17+
18+
// providerInfo holds information about a provider
19+
type providerInfo struct {
20+
name string
21+
instance provider.Provider
22+
}
23+
24+
// interfaceMap maps API type names to their provider interface types.
25+
//
26+
// Note: This map must list interfaces explicitly because Go's reflection system
27+
// requires compile-time type references. While we could use AST parsing to discover
28+
// interface names, we still need this map to get reflect.Type values.
29+
//
30+
// When adding a new API type:
31+
// 1. Define the interface in internal/provider/ (e.g., type FooProvider interface)
32+
// 2. Add it to this map
33+
// 3. The script will automatically detect implementations
34+
//
35+
// This approach provides:
36+
// - Compile-time safety (won't compile if interface doesn't exist)
37+
// - Runtime accuracy (checks actual interface implementation)
38+
// - Minimal maintenance (only update when adding new API types, not implementations)
39+
var interfaceMap = map[string]reflect.Type{
40+
"acl": reflect.TypeOf((*provider.ACLProvider)(nil)).Elem(),
41+
"banner": reflect.TypeOf((*provider.BannerProvider)(nil)).Elem(),
42+
"bgp": reflect.TypeOf((*provider.BGPProvider)(nil)).Elem(),
43+
"bgp_peer": reflect.TypeOf((*provider.BGPPeerProvider)(nil)).Elem(),
44+
"certificate": reflect.TypeOf((*provider.CertificateProvider)(nil)).Elem(),
45+
"dns": reflect.TypeOf((*provider.DNSProvider)(nil)).Elem(),
46+
"evpninstance": reflect.TypeOf((*provider.EVPNInstanceProvider)(nil)).Elem(),
47+
"interface": reflect.TypeOf((*provider.InterfaceProvider)(nil)).Elem(),
48+
"isis": reflect.TypeOf((*provider.ISISProvider)(nil)).Elem(),
49+
"lldp": reflect.TypeOf((*provider.LLDPProvider)(nil)).Elem(),
50+
"managementaccess": reflect.TypeOf((*provider.ManagementAccessProvider)(nil)).Elem(),
51+
"ntp": reflect.TypeOf((*provider.NTPProvider)(nil)).Elem(),
52+
"nve": reflect.TypeOf((*provider.NVEProvider)(nil)).Elem(),
53+
"ospf": reflect.TypeOf((*provider.OSPFProvider)(nil)).Elem(),
54+
"pim": reflect.TypeOf((*provider.PIMProvider)(nil)).Elem(),
55+
"prefixset": reflect.TypeOf((*provider.PrefixSetProvider)(nil)).Elem(),
56+
"routingpolicy": reflect.TypeOf((*provider.RoutingPolicyProvider)(nil)).Elem(),
57+
"snmp": reflect.TypeOf((*provider.SNMPProvider)(nil)).Elem(),
58+
"syslog": reflect.TypeOf((*provider.SyslogProvider)(nil)).Elem(),
59+
"user": reflect.TypeOf((*provider.UserProvider)(nil)).Elem(),
60+
"vlan": reflect.TypeOf((*provider.VLANProvider)(nil)).Elem(),
61+
"vrf": reflect.TypeOf((*provider.VRFProvider)(nil)).Elem(),
62+
}
63+
64+
func main() {
65+
// Initialize providers
66+
providers := []providerInfo{
67+
{name: "NXOS", instance: nxos.NewProvider()},
68+
{name: "IOS-XR", instance: iosxr.NewProvider()},
69+
{name: "OpenConfig", instance: openconfig.NewProvider()},
70+
}
71+
72+
// Get sorted list of API types
73+
apiTypes := make([]string, 0, len(interfaceMap))
74+
for apiType := range interfaceMap {
75+
apiTypes = append(apiTypes, apiType)
76+
}
77+
sort.Strings(apiTypes)
78+
79+
// Check implementations for each provider
80+
implementations := make(map[string]map[string]bool)
81+
counts := make(map[string]int)
82+
83+
for _, prov := range providers {
84+
implementations[prov.name] = make(map[string]bool)
85+
providerType := reflect.TypeOf(prov.instance)
86+
87+
for _, apiType := range apiTypes {
88+
interfaceType := interfaceMap[apiType]
89+
implements := providerType.Implements(interfaceType)
90+
implementations[prov.name][apiType] = implements
91+
92+
if implements {
93+
counts[prov.name]++
94+
}
95+
}
96+
}
97+
98+
// Generate markdown documentation
99+
fmt.Println("<!--")
100+
fmt.Println("SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors")
101+
fmt.Println("SPDX-License-Identifier: Apache-2.0")
102+
fmt.Println("-->")
103+
fmt.Println()
104+
fmt.Println("# Provider Compatibility Matrix")
105+
fmt.Println()
106+
fmt.Println("This document provides a detailed overview of which API types are supported by each network device provider.")
107+
fmt.Println()
108+
fmt.Println("<!-- To regenerate this matrix, run: make provider-matrix -->")
109+
fmt.Println()
110+
fmt.Println("## Cisco NX-OS")
111+
fmt.Println()
112+
fmt.Println("Cisco NX-OS provides comprehensive support for network configuration through the network-operator.")
113+
fmt.Println()
114+
fmt.Printf("**Status**: Full support (%d API types)\n", counts["NXOS"])
115+
fmt.Println()
116+
fmt.Println("## Cisco IOS-XR")
117+
fmt.Println()
118+
fmt.Println("Cisco IOS-XR support is currently in early development.")
119+
fmt.Println()
120+
fmt.Printf("**Status**: Limited support (%d API type)\n", counts["IOS-XR"])
121+
fmt.Println()
122+
fmt.Println("## OpenConfig")
123+
fmt.Println()
124+
fmt.Println("OpenConfig provides a vendor-neutral configuration interface using standard OpenConfig YANG models.")
125+
fmt.Println()
126+
fmt.Printf("**Status**: Limited support (%d API type)\n", counts["OpenConfig"])
127+
fmt.Println()
128+
fmt.Println("## Detailed Compatibility Matrix")
129+
fmt.Println()
130+
131+
// Build table header
132+
fmt.Print("| API Type |")
133+
for _, prov := range providers {
134+
fmt.Printf(" %s |", prov.name)
135+
}
136+
fmt.Println()
137+
138+
fmt.Print("|----------|")
139+
for range providers {
140+
fmt.Print("--------|")
141+
}
142+
fmt.Println()
143+
144+
// Build table rows
145+
for _, apiType := range apiTypes {
146+
displayName := formatDisplayName(apiType)
147+
fmt.Printf("| %s |", displayName)
148+
149+
for _, prov := range providers {
150+
if implementations[prov.name][apiType] {
151+
fmt.Print(" ✔️ |")
152+
} else {
153+
fmt.Print(" — |")
154+
}
155+
}
156+
fmt.Println()
157+
}
158+
159+
fmt.Println()
160+
fmt.Println("**Legend:**")
161+
fmt.Println("- ✔️ Implemented")
162+
fmt.Println("- — Not implemented")
163+
fmt.Println()
164+
fmt.Println("## Contributing")
165+
fmt.Println()
166+
fmt.Println("To add support for a new API type or provider:")
167+
fmt.Println()
168+
fmt.Println("1. Define the provider interface in `internal/provider/`")
169+
fmt.Println("2. Implement the interface methods in your provider package")
170+
fmt.Println("3. Run `make provider-matrix` to regenerate this document")
171+
fmt.Println("4. Submit a pull request with your changes")
172+
fmt.Println()
173+
fmt.Println("The matrix is automatically generated by checking which provider interfaces")
174+
fmt.Println("each provider implements, ensuring accuracy and eliminating manual maintenance.")
175+
}
176+
177+
// formatDisplayName converts snake_case to space-separated Title Case for display
178+
func formatDisplayName(typeName string) string {
179+
// Handle special cases for better display
180+
special := map[string]string{
181+
"acl": "ACL",
182+
"bgp": "BGP",
183+
"bgp_peer": "BGP Peer",
184+
"dns": "DNS",
185+
"evpninstance": "EVPN Instance",
186+
"isis": "ISIS",
187+
"lldp": "LLDP",
188+
"ntp": "NTP",
189+
"nve": "NVE",
190+
"ospf": "OSPF",
191+
"pim": "PIM",
192+
"snmp": "SNMP",
193+
"vlan": "VLAN",
194+
"vrf": "VRF",
195+
"prefixset": "Prefix Set",
196+
"routingpolicy": "Routing Policy",
197+
"managementaccess": "Management Access",
198+
}
199+
200+
if display, ok := special[typeName]; ok {
201+
return display
202+
}
203+
204+
// Default: convert to Title Case with spaces
205+
parts := strings.Split(typeName, "_")
206+
for i, part := range parts {
207+
if len(part) > 0 {
208+
parts[i] = strings.ToUpper(part[:1]) + part[1:]
209+
}
210+
}
211+
return strings.Join(parts, " ")
212+
}

0 commit comments

Comments
 (0)