Skip to content

Commit f46fc5d

Browse files
Rewrite cisco-nxos-gnmi provider w/o ygot
Instead of using ygot for generating the Go Code according to the Device YANG Model, we use handcrafted Go types in combination with gnmiext/v2 to configure the NX-OS devices. Fixes #9 Fixes #23
1 parent b939d57 commit f46fc5d

File tree

265 files changed

+5194
-743905
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

265 files changed

+5194
-743905
lines changed

go.mod

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ go 1.25
44

55
require (
66
github.com/felix-kaestner/copy v0.0.0-20240226161622-4773371af039
7+
github.com/go-crypt/crypt v0.4.7-0.20251003122429-450f8065d00b
78
github.com/go-logr/logr v1.4.2
8-
github.com/google/go-cmp v0.7.0
99
github.com/onsi/ginkgo/v2 v2.23.3
1010
github.com/onsi/gomega v1.36.3
1111
github.com/openconfig/gnmi v0.14.1
@@ -14,6 +14,7 @@ require (
1414
github.com/openconfig/ygnmi v0.13.1-0.20250924235719-646562b5d0c3
1515
github.com/openconfig/ygot v0.32.0
1616
github.com/sapcc/go-api-declarations v1.16.0
17+
github.com/tidwall/gjson v1.18.0
1718
go.uber.org/automaxprocs v1.6.0
1819
go.uber.org/zap v1.27.0
1920
golang.org/x/crypto v0.36.0
@@ -43,6 +44,7 @@ require (
4344
github.com/felixge/httpsnoop v1.0.4 // indirect
4445
github.com/fsnotify/fsnotify v1.8.0 // indirect
4546
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
47+
github.com/go-crypt/x v0.4.8 // indirect
4648
github.com/go-logr/stdr v1.2.2 // indirect
4749
github.com/go-logr/zapr v1.3.0 // indirect
4850
github.com/go-openapi/jsonpointer v0.21.0 // indirect
@@ -55,6 +57,7 @@ require (
5557
github.com/google/btree v1.1.3 // indirect
5658
github.com/google/cel-go v0.22.0 // indirect
5759
github.com/google/gnostic-models v0.6.9 // indirect
60+
github.com/google/go-cmp v0.7.0 // indirect
5861
github.com/google/gofuzz v1.2.0 // indirect
5962
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
6063
github.com/google/uuid v1.6.0 // indirect
@@ -76,6 +79,8 @@ require (
7679
github.com/spf13/cobra v1.9.1 // indirect
7780
github.com/spf13/pflag v1.0.6 // indirect
7881
github.com/stoewer/go-strcase v1.3.0 // indirect
82+
github.com/tidwall/match v1.1.1 // indirect
83+
github.com/tidwall/pretty v1.2.0 // indirect
7984
github.com/x448/float16 v0.8.4 // indirect
8085
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
8186
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
@@ -91,7 +96,7 @@ require (
9196
golang.org/x/net v0.38.0 // indirect
9297
golang.org/x/oauth2 v0.25.0 // indirect
9398
golang.org/x/sync v0.12.0 // indirect
94-
golang.org/x/sys v0.31.0 // indirect
99+
golang.org/x/sys v0.36.0 // indirect
95100
golang.org/x/term v0.30.0 // indirect
96101
golang.org/x/text v0.23.0 // indirect
97102
golang.org/x/time v0.8.0 // indirect

go.sum

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
3232
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
3333
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
3434
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
35+
github.com/go-crypt/crypt v0.4.7-0.20251003122429-450f8065d00b h1:5uuOD51gU9ZJkrpxhRmq0DJrKctprg4LKGQ/lDWRK8Q=
36+
github.com/go-crypt/crypt v0.4.7-0.20251003122429-450f8065d00b/go.mod h1:Ts3T2ORhE0nxel6/2mlQNZTZn7dcxRdtnSJjoDqUkbs=
37+
github.com/go-crypt/x v0.4.8 h1:Cob6IxrSfWTc+MG8CBbNHBM4UqrBgEZDoK5t/SG4oZ4=
38+
github.com/go-crypt/x v0.4.8/go.mod h1:ozw9N4MYuLKhR5x2REs1e4T/nrEAkbuVkcsh/HbYksg=
3539
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
3640
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
3741
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -150,8 +154,14 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
150154
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
151155
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
152156
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
153-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
154-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
157+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
158+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
159+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
160+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
161+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
162+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
163+
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
164+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
155165
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
156166
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
157167
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -209,8 +219,8 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
209219
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
210220
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
211221
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
212-
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
213-
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
222+
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
223+
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
214224
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
215225
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
216226
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

internal/provider/cisco/gnmiext/v2/client.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func (c *Client) get(ctx context.Context, dt gpb.GetRequest_DataType, conf ...Co
244244
// configuration. Otherwise, a full replacement is done.
245245
// If the current configuration equals the desired configuration, the operation
246246
// is skipped.
247-
func (c *Client) set(ctx context.Context, patch bool, conf ...Configurable) (err error) {
247+
func (c *Client) set(ctx context.Context, patch bool, conf ...Configurable) error {
248248
if len(conf) == 0 {
249249
return nil
250250
}
@@ -255,12 +255,13 @@ func (c *Client) set(ctx context.Context, patch bool, conf ...Configurable) (err
255255
return err
256256
}
257257
got := cp.Deep(cf)
258-
if err := c.GetConfig(ctx, got); err != nil && !errors.Is(err, ErrNil) {
258+
err = c.GetConfig(ctx, got)
259+
if err != nil && !errors.Is(err, ErrNil) {
259260
return fmt.Errorf("gnmiext: failed to retrieve current config for %s: %w", cf.XPath(), err)
260261
}
261262
// If the current configuration is equal to the desired configuration, skip the update.
262263
// This avoids unnecessary updates and potential disruptions.
263-
if reflect.DeepEqual(cf, got) {
264+
if err == nil && reflect.DeepEqual(cf, got) {
264265
c.logger.V(1).Info("Configuration is already up-to-date", "path", cf.XPath())
265266
continue
266267
}
@@ -311,6 +312,20 @@ func (c *Client) Marshal(v any) (b []byte, err error) {
311312
// If the destination implements the [Marshaler] interface, it will be unmarshaled using that.
312313
// Otherwise, [json.Unmarshal] is used.
313314
func (c *Client) Unmarshal(b []byte, dst any) (err error) {
315+
// NOTE: If you query for list elements on Cisco NX-OS, the encoded payload
316+
// will be the wrapped in an array (even if only one element is requested), i.e.
317+
//
318+
// [
319+
// {
320+
// ...
321+
// }
322+
// ]
323+
_, ok := dst.(interface {
324+
IsListItem()
325+
})
326+
if ok && b[0] == '[' && b[len(b)-1] == ']' {
327+
b = b[1 : len(b)-1]
328+
}
314329
if um, ok := dst.(Marshaler); ok {
315330
if err := um.UnmarshalYANG(c.capabilities, b); err != nil {
316331
return fmt.Errorf("gnmiext: failed to unmarshal value: %w", err)

internal/provider/cisco/nxos/.gitignore

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package nxos
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/ironcore-dev/network-operator/api/v1alpha1"
10+
"github.com/ironcore-dev/network-operator/internal/provider/cisco/gnmiext/v2"
11+
)
12+
13+
var _ gnmiext.Configurable = (*ACL)(nil)
14+
15+
// ACL represents an IPv4 or IPv6 access control list, depending on the rules it contains.
16+
// It can only contain either IPv4 or IPv6 rules, never both. It's name must be unique
17+
// across IPv4 and IPv6 access lists.
18+
type ACL struct {
19+
// Name is the name of the ACL. This name must be unique across IPv4 and IPv6 access lists.
20+
Name string `json:"name"`
21+
// SeqItems contains the list of ACE entries in the ACL.
22+
SeqItems struct {
23+
ACEList []*ACLEntry `json:"ACE-list,omitzero"`
24+
} `json:"seq-items,omitzero"`
25+
// Is6 indicates whether this is an IPv6 ACL. This field is not serialized to JSON
26+
// and is only used internally to determine the correct XPath for the ACL.
27+
Is6 bool `json:"-"`
28+
}
29+
30+
func (a *ACL) IsListItem() {}
31+
32+
func (a *ACL) XPath() string {
33+
if a.Is6 {
34+
return "System/acl-items/ipv6-items/name-items/ACL-list[name=" + a.Name + "]"
35+
}
36+
return "System/acl-items/ipv4-items/name-items/ACL-list[name=" + a.Name + "]"
37+
}
38+
39+
type ACLEntry struct {
40+
SeqNum int32 `json:"seqNum"`
41+
Action Action `json:"action"`
42+
Protocol Protocol `json:"protocol"`
43+
Remark string `json:"remark,omitempty"`
44+
SrcPrefix string `json:"srcPrefix"`
45+
SrcPrefixLength int `json:"srcPrefixLength,omitempty"`
46+
DstPrefix string `json:"dstPrefix"`
47+
DstPrefixLength int `json:"dstPrefixLength,omitempty"`
48+
}
49+
50+
type Action string
51+
52+
const (
53+
ActionPermit Action = "permit"
54+
ActionDeny Action = "deny"
55+
)
56+
57+
func ActionFrom(act v1alpha1.ACLAction) (Action, error) {
58+
switch act {
59+
case v1alpha1.ActionPermit:
60+
return ActionPermit, nil
61+
case v1alpha1.ActionDeny:
62+
return ActionDeny, nil
63+
default:
64+
var zero Action
65+
return zero, fmt.Errorf("acl: unsupported action %q", act)
66+
}
67+
}
68+
69+
type Protocol uint8
70+
71+
const (
72+
ProtocolIP Protocol = 0
73+
ProtocolICMP Protocol = 1
74+
ProtocolTCP Protocol = 6
75+
ProtocolUDP Protocol = 17
76+
ProtocolOSPF Protocol = 89
77+
ProtocolPIM Protocol = 103
78+
)
79+
80+
func ProtocolFrom(proto v1alpha1.Protocol) Protocol {
81+
switch proto {
82+
case v1alpha1.ProtocolIP:
83+
return ProtocolIP
84+
case v1alpha1.ProtocolICMP:
85+
return ProtocolICMP
86+
case v1alpha1.ProtocolTCP:
87+
return ProtocolTCP
88+
case v1alpha1.ProtocolUDP:
89+
return ProtocolUDP
90+
case v1alpha1.ProtocolOSPF:
91+
return ProtocolOSPF
92+
case v1alpha1.ProtocolPIM:
93+
return ProtocolPIM
94+
default:
95+
return 0 // unknown protocol - default to 0 == "ip"
96+
}
97+
}

internal/provider/cisco/nxos/acl/acl.go

Lines changed: 0 additions & 156 deletions
This file was deleted.

0 commit comments

Comments
 (0)