Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,6 @@ linters:
- builtin$
- examples$
- third_party$
- internal/provider/openconfig
formatters:
enable:
- gofmt
Expand All @@ -285,4 +284,3 @@ formatters:
- builtin$
- examples$
- third_party$
- internal/provider/openconfig
3 changes: 0 additions & 3 deletions Makefile.maker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ golang:

golangciLint:
createConfig: false
skipDirs:
- internal/provider/openconfig
timeout: 10m

shellCheck:
ignorePaths:
Expand Down
13 changes: 1 addition & 12 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ require (
github.com/onsi/gomega v1.42.0
github.com/openconfig/gnmi v0.14.1
github.com/openconfig/gnoi v0.8.0
github.com/openconfig/goyang v1.6.3
github.com/openconfig/ygnmi v0.15.0
github.com/openconfig/ygot v0.34.0
github.com/pin/tftp/v3 v3.2.0
github.com/sapcc/go-api-declarations v1.24.0
Expand Down Expand Up @@ -44,7 +42,6 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.10.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
Expand All @@ -66,7 +63,6 @@ require (
github.com/go-openapi/swag/typeutils v0.26.0 // indirect
github.com/go-openapi/swag/yamlutils v0.26.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gobuffalo/flect v1.0.3 // indirect
github.com/golang/glog v1.2.5 // indirect
github.com/google/cel-go v0.26.1 // indirect
github.com/google/gnostic-models v0.7.1 // indirect
Expand All @@ -76,13 +72,10 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openconfig/gocloser v0.0.0-20251119232641-34bca749fdb3 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/openconfig/goyang v1.6.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
Expand Down Expand Up @@ -121,18 +114,14 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.36.0 // indirect
k8s.io/apiserver v0.36.0 // indirect
k8s.io/code-generator v0.36.0 // indirect
k8s.io/component-base v0.36.0 // indirect
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect
k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect
k8s.io/streaming v0.36.0 // indirect
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
sigs.k8s.io/controller-tools v0.21.0 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect
Expand Down
78 changes: 3 additions & 75 deletions go.sum

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions internal/provider/openconfig/.gitignore

This file was deleted.

77 changes: 77 additions & 0 deletions internal/provider/openconfig/banner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package openconfig

import (
"context"
"encoding/json"
"fmt"

"github.com/ironcore-dev/network-operator/api/core/v1alpha1"
"github.com/ironcore-dev/network-operator/internal/apistatus"
"github.com/ironcore-dev/network-operator/internal/provider"
"github.com/ironcore-dev/network-operator/internal/transport/gnmiext"
)

var _ provider.BannerProvider = (*Provider)(nil)

func (p *Provider) EnsureBanner(ctx context.Context, req *provider.EnsureBannerRequest) error {
t, err := toBannerType(req.Type)
if err != nil {
return err
}
return p.client.Update(ctx, &Banner{Type: t, Message: req.Message})
}

func (p *Provider) DeleteBanner(ctx context.Context, req *provider.DeleteBannerRequest) error {
t, err := toBannerType(req.Type)
if err != nil {
return err
}
return p.client.Delete(ctx, &Banner{Type: t})
}

// BannerType represents the OpenConfig banner leaf type.
type BannerType string

const (
BannerTypeLogin BannerType = "login-banner"
BannerTypeMOTD BannerType = "motd-banner"
)

func toBannerType(t v1alpha1.BannerType) (BannerType, error) {
switch t {
case v1alpha1.BannerTypePreLogin:
return BannerTypeLogin, nil
case v1alpha1.BannerTypePostLogin:
return BannerTypeMOTD, nil
default:
return "", apistatus.NewUnsupportedFieldError(apistatus.FieldViolation{
Field: "spec.type",
Description: fmt.Sprintf("unsupported banner type %q", t),
})
}
}

// Compile-time assertions.
var _ gnmiext.DataElement = (*Banner)(nil)

// Banner targets a single banner leaf in the system config.
type Banner struct {
// Type is used only for XPath construction.
Type BannerType `json:"-"`
Message string `json:"-"`
}

func (b *Banner) XPath() string {
return fmt.Sprintf("openconfig:system/config/%s", b.Type)
}

func (b *Banner) MarshalJSON() ([]byte, error) {
return json.Marshal(b.Message)
}

func (b *Banner) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &b.Message)
}
173 changes: 173 additions & 0 deletions internal/provider/openconfig/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package openconfig

import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"time"

"github.com/ironcore-dev/network-operator/internal/provider"
"github.com/ironcore-dev/network-operator/internal/transport/gnmiext"
)

var _ provider.DeviceProvider = (*Provider)(nil)

func (p *Provider) GetDeviceInfo(ctx context.Context) (*provider.DeviceInfo, error) {
sys := new(SystemState)
chassis := new(ChassisComponentState)
if err := p.client.GetState(ctx, sys, chassis); err != nil {
return nil, fmt.Errorf("failed to get device info: %w", err)
}

return &provider.DeviceInfo{
Hostname: sys.Hostname,
Manufacturer: chassis.MfgName,
Model: chassis.ModelName,
SerialNumber: sanitize(chassis.SerialNo),
FirmwareVersion: sys.SoftwareVersion,
}, nil
}

func (p *Provider) GetLastRebootTime(ctx context.Context) (time.Time, error) {
sys := new(SystemState)
if err := p.client.GetState(ctx, sys); err != nil {
return time.Time{}, fmt.Errorf("failed to get last reboot time: %w", err)
}
if sys.BootTime == "" {
return time.Time{}, nil
}
ns, err := strconv.ParseInt(sys.BootTime, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("failed to parse boot-time: %w", err)
}
return time.Unix(0, ns), nil
}

func (p *Provider) ListPorts(ctx context.Context) ([]provider.DevicePort, error) {
ports := new(PhysicalInterfaces)
if err := p.client.GetState(ctx, ports); err != nil {
return nil, fmt.Errorf("failed to list ports: %w", err)
}

dp := make([]provider.DevicePort, 0, len(ports.Interface))
for _, iface := range ports.Interface {
if iface.State == nil || iface.State.Type != InterfaceTypeEthernetCsmacd {
continue
}
port := provider.DevicePort{
ID: iface.Name,
Type: iface.PortSpeed().Short(),
}
dp = append(dp, port)
}

return dp, nil
}

// Compile-time assertions.
var (
_ gnmiext.DataElement = (*SystemState)(nil)
_ gnmiext.DataElement = (*ChassisComponentState)(nil)
_ gnmiext.DataElement = (*PhysicalInterfaces)(nil)
)

// SystemState maps the openconfig system state container.
type SystemState struct {
Hostname string `json:"hostname,omitempty"`
BootTime string `json:"boot-time,omitempty"`
SoftwareVersion string `json:"software-version,omitempty"`
}

func (*SystemState) XPath() string {
return "openconfig:system/state"
}

// ChassisComponentState maps the openconfig platform Chassis component state.
type ChassisComponentState struct {
MfgName string `json:"mfg-name,omitempty"`
ModelName string `json:"model-name,omitempty"`
SerialNo string `json:"serial-no,omitempty"`
}

func (*ChassisComponentState) XPath() string {
return "openconfig:components/component[name=Chassis]/state"
}

// PhysicalInterfaces retrieves the interface list for port discovery.
type PhysicalInterfaces struct {
Interface []*PhysicalInterfaceEntry `json:"interface,omitempty"`
}

func (*PhysicalInterfaces) XPath() string {
return "openconfig:interfaces"
}

// PhysicalInterfaceEntry represents a single interface for port listing.
type PhysicalInterfaceEntry struct {
Name string `json:"name"`
State *PhysicalInterfaceState `json:"state,omitempty"`
Ethernet *PhysicalInterfaceEthernet `json:"openconfig-if-ethernet:ethernet,omitempty"`
}

// PortSpeed returns the ethernet port speed, or [EthernetSpeedUnknown] if unavailable.
func (e *PhysicalInterfaceEntry) PortSpeed() EthernetSpeed {
if e.Ethernet != nil && e.Ethernet.State != nil {
return e.Ethernet.State.PortSpeed
}
return EthernetSpeedUnknown
}

// PhysicalInterfaceState holds the state container for port discovery.
type PhysicalInterfaceState struct {
Type InterfaceType `json:"type,omitempty"`
}

// PhysicalInterfaceEthernet holds ethernet state for port speed discovery.
type PhysicalInterfaceEthernet struct {
State *PhysicalEthernetState `json:"state,omitempty"`
}

// PhysicalEthernetState holds ethernet state fields.
type PhysicalEthernetState struct {
PortSpeed EthernetSpeed `json:"port-speed,omitempty"`
}

// EthernetSpeed represents the YANG identity for ethernet port speed.
type EthernetSpeed string

// Short returns the speed without the module prefix (e.g. "25GB").
func (s EthernetSpeed) Short() string {
return strings.TrimPrefix(string(s), "openconfig-if-ethernet:SPEED_")
}

const (
EthernetSpeed10MB EthernetSpeed = "openconfig-if-ethernet:SPEED_10MB"
EthernetSpeed100MB EthernetSpeed = "openconfig-if-ethernet:SPEED_100MB"
EthernetSpeed1GB EthernetSpeed = "openconfig-if-ethernet:SPEED_1GB"
EthernetSpeed2500MB EthernetSpeed = "openconfig-if-ethernet:SPEED_2500MB"
EthernetSpeed5GB EthernetSpeed = "openconfig-if-ethernet:SPEED_5GB"
EthernetSpeed10GB EthernetSpeed = "openconfig-if-ethernet:SPEED_10GB"
EthernetSpeed25GB EthernetSpeed = "openconfig-if-ethernet:SPEED_25GB"
EthernetSpeed40GB EthernetSpeed = "openconfig-if-ethernet:SPEED_40GB"
EthernetSpeed50GB EthernetSpeed = "openconfig-if-ethernet:SPEED_50GB"
EthernetSpeed100GB EthernetSpeed = "openconfig-if-ethernet:SPEED_100GB"
EthernetSpeed200GB EthernetSpeed = "openconfig-if-ethernet:SPEED_200GB"
EthernetSpeed400GB EthernetSpeed = "openconfig-if-ethernet:SPEED_400GB"
EthernetSpeed600GB EthernetSpeed = "openconfig-if-ethernet:SPEED_600GB"
EthernetSpeed800GB EthernetSpeed = "openconfig-if-ethernet:SPEED_800GB"
EthernetSpeed1600GB EthernetSpeed = "openconfig-if-ethernet:SPEED_1600GB"
EthernetSpeedUnknown EthernetSpeed = "openconfig-if-ethernet:SPEED_UNKNOWN"
)

var invalidLabelChars = regexp.MustCompile(`[^A-Za-z0-9_.\-]`)

func sanitize(s string) string {
s = invalidLabelChars.ReplaceAllString(s, "-")
s = strings.Trim(s, "-_.")
return s[:min(len(s), 63)]
}
6 changes: 6 additions & 0 deletions internal/provider/openconfig/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

// Package openconfig implements provider support for OpenConfig-compliant devices
// using gNMI (e.g. Nokia SR Linux, Arista EOS).
package openconfig
Loading
Loading