|
| 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