gnmi

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2025 License: MPL-2.0 Imports: 22 Imported by: 0

README

go-gnmi

GoDoc Go Report Card CI codecov License

A simple, fluent Go client library for interacting with network devices using the gNMI protocol (gRPC Network Management Interface).

Features

  • Simple API: Fluent, chainable API design
  • Lazy Connection: Non-blocking client initialization with automatic connection on first use
  • JSON Manipulation: Path-based JSON operations using gjson and sjson
  • Complete gNMI Support: Get, Set, and Capabilities operations
  • Robust Transport: Built on gnmic for reliable gRPC connectivity and gNMI protocol handling
  • Automatic Retry: Built-in retry logic with exponential backoff for transient errors
  • Thread-Safe: Concurrent read operations with synchronized write operations
  • Capability Discovery: Automatic capability negotiation and checking
  • Structured Logging: Configurable logging with automatic sensitive data redaction
  • TLS Security: TLS by default with certificate verification

Installation

go get github.com/netascode/go-gnmi

Requirements

  • Go 1.24 or later
  • Network device with gNMI support

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/netascode/go-gnmi"
)

func main() {
    // Create client
    client, err := gnmi.NewClient(
        "192.168.1.1:57400",
        gnmi.Username("admin"),
        gnmi.Password("secret"),
        gnmi.VerifyCertificate(false), // Use true in production
    )
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    // Get operation
    ctx := context.Background()
    res, err := client.Get(ctx, []string{"/interfaces/interface[name=GigabitEthernet0/0/0/0]/state"})
    if err != nil {
        log.Fatal(err)
    }

    // Parse response using gjson
    ifState := res.GetValue("notification.0.update.0.val").String()
    fmt.Println("Interface state:", ifState)
}

Usage

Client Creation

The client uses lazy connection - NewClient() validates configuration but doesn't establish a physical connection. The connection happens automatically on first use:

// Creates client without connecting (validates config only)
client, err := gnmi.NewClient(
    "device.example.com:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.TLS(true),
    gnmi.VerifyCertificate(true),
    gnmi.TLSCA("/path/to/ca.crt"),
    gnmi.MaxRetries(5),
    gnmi.OperationTimeout(120*time.Second),
)
if err != nil {
    log.Fatal(err)  // Configuration error
}
defer client.Close()

// Optional: Verify connection before operations
if err := client.Ping(ctx); err != nil {
    log.Fatal(err)  // Connection error
}

Get Operations

Retrieve configuration and state data:

ctx := context.Background()

// Get with paths
paths := []string{
    "/interfaces/interface[name=GigabitEthernet0/0/0/0]/config",
    "/interfaces/interface[name=GigabitEthernet0/0/0/0]/state",
}

res, err := client.Get(ctx, paths)
if err != nil {
    log.Fatal(err)
}

// Get with encoding
res, err := client.Get(ctx, paths, gnmi.GetEncoding("json_ietf"))

Set Operations

Update, replace, or delete configuration:

ctx := context.Background()

// Build JSON payload using Body builder
value, err := gnmi.Body{}.
    Set("config.description", "WAN Interface").
    Set("config.enabled", true).
    Set("config.mtu", 9000).String()
if err != nil {
    log.Fatal(err)
}

// Create set operations
ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0/0/0/0]/config", value),
    gnmi.Delete("/interfaces/interface[name=Gi0/0/0/1]/config"),
}

res, err = client.Set(ctx, ops)
if err != nil {
    log.Fatal(err)
}

Error Handling

The library automatically retries transient errors (service unavailable, rate limiting, timeout) with exponential backoff:

client, err := gnmi.NewClient(
    "192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.MaxRetries(3),                         // Maximum retry attempts
    gnmi.BackoffMinDelay(1*time.Second),        // Minimum 1 second delay
    gnmi.BackoffMaxDelay(60*time.Second),       // Maximum 60 second delay
    gnmi.BackoffDelayFactor(2.0),               // Exponential factor
)

// Detailed error information
res, err := client.Get(ctx, paths)
if err != nil {
    if gnmiErr, ok := err.(*gnmi.GnmiError); ok {
        log.Printf("Operation: %s", gnmiErr.Operation)
        log.Printf("Retries: %d", gnmiErr.Retries)
        log.Printf("Transient: %v", gnmiErr.IsTransient)
        for _, e := range gnmiErr.Errors {
            log.Printf("  Code %d: %s", e.Code, e.Message)
        }
    }
}

Capability Checking

// Check if server supports specific encoding
if client.HasCapability("json_ietf") {
    // Use json_ietf encoding
    res, err := client.Get(ctx, paths, gnmi.GetEncoding("json_ietf"))
}

// Get all server capabilities
caps := client.ServerCapabilities()
for _, cap := range caps {
    fmt.Println(cap)
}

Body Builder

Build JSON payloads with a fluent API:

value, err := gnmi.Body{}.
    Set("config.name", "GigabitEthernet0/0/0/0").
    Set("config.description", "WAN Interface").
    Set("config.enabled", true).
    Set("config.mtu", 9000).
    Set("config.ipv4.address", "192.168.1.1").
    Set("config.ipv4.prefix-length", 24).
    String()
if err != nil {
    log.Fatal(err)
}

ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0/0/0/0]", value),
}

Supported Operations

Operation Description
Get Retrieve configuration and state data from device
Set Update, replace, or delete configuration (supports atomic operations)
Capabilities Discover supported encodings, models, and gNMI version

Security

TLS Configuration

The library uses TLS by default with certificate verification enabled. For production:

client, err := gnmi.NewClient(
    "device.example.com:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.TLS(true),                      // Enable TLS (default: true)
    gnmi.VerifyCertificate(true),        // Verify certificates (default: true)
    gnmi.TLSCA("/path/to/ca.crt"),       // Specify CA certificate
    gnmi.TLSCert("/path/to/client.crt"), // Client certificate (optional)
    gnmi.TLSKey("/path/to/client.key"),  // Client key (optional)
)

⚠️ WARNING: Disabling TLS or certificate verification makes connections vulnerable to eavesdropping and Man-in-the-Middle attacks. Only use VerifyCertificate(false) in isolated testing environments.

Documentation

Examples

See the examples directory for library usage examples:

  • basic - Client creation, Get, Set operations, response parsing
  • concurrent - Thread-safe concurrent operations
  • capabilities - Capability discovery and checking
  • logging - Logger configuration and log levels

Testing

# Run tests
make test

# Run tests with coverage
make coverage

# Run linters
make lint

# Run all checks
make verify

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Acknowledgments

This library is built on top of gnmic for the gNMI transport layer. Gnmic provides robust gRPC connectivity and gNMI protocol handling, allowing go-gnmi to focus on providing a simple, idiomatic Go API.

License

Mozilla Public License Version 2.0 - see LICENSE for details.

Documentation

Overview

Package gnmi provides a simple, fluent API for interacting with network devices using the gNMI protocol (gRPC Network Management Interface).

The library provides a high-level client interface that handles connection management, JSON manipulation, error handling with automatic retry logic, and thread-safe operations.

Connection Behavior

The client uses lazy connection pattern - NewClient() validates configuration but does not establish a physical connection. The connection is established automatically on the first RPC call.

Use Ping() to explicitly verify connectivity before operations if needed.

Quick Start

Create a client and perform basic operations:

client, err := gnmi.NewClient(
    "192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.TLS(true),
)
if err != nil {
    log.Fatal(err)  // Configuration error
}
defer client.Close()

// Optional: verify connection explicitly
if err := client.Ping(ctx); err != nil {
    log.Fatal(err)  // Connection error
}

// Get operation with paths (auto-connects if needed)
ctx := context.Background()
paths := []string{"/interfaces/interface[name=GigabitEthernet0/0/0/0]/state"}
res, err := client.Get(ctx, paths)
if err != nil {
    log.Fatal(err)
}

// Parse response using gjson
value := res.GetValue("notification.0.update.0.val").String()
fmt.Println("Value:", value)

JSON Manipulation

Use the Body builder for constructing JSON payloads:

body := gnmi.Body{}.
    Set("config.name", "GigabitEthernet0/0/0/0").
    Set("config.description", "WAN Interface").
    Set("config.enabled", true).
    Set("config.mtu", 9000)

value, err := body.String()
if err != nil {
    log.Fatal(err)
}

ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0/0/0/0]/config", value),
}
res, err = client.Set(ctx, ops)

Error Handling

The library automatically retries transient errors with exponential backoff:

client, err := gnmi.NewClient(
    "192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.MaxRetries(5),
    gnmi.BackoffMinDelay(1*time.Second),
    gnmi.BackoffMaxDelay(60*time.Second),
)

Thread Safety

Read operations (Get, Capabilities) are thread-safe and can be called concurrently. Write operations (Set) are synchronized with a mutex.

Supported Operations

  • Get: Retrieve configuration and state data
  • Set: Update, replace, or delete configuration
  • Capabilities: Discover supported encodings and gNMI version

References

Index

Examples

Constants

View Source
const (
	DefaultPort               = 57400
	DefaultMaxRetries         = 3
	DefaultBackoffMinDelay    = 1 * time.Second
	DefaultBackoffMaxDelay    = 60 * time.Second
	DefaultBackoffDelayFactor = 2
	DefaultConnectTimeout     = 30 * time.Second
	DefaultOperationTimeout   = 15 * time.Second // Matches embedded client behavior
	DefaultUseTLS             = true
	DefaultVerifyCertificate  = true
	DefaultPrettyPrintLogs    = true
)

Default client configuration values

View Source
const (
	MaxJSONSizeForLogging = 1 * 1024 * 1024 // 1MB limit to prevent ReDoS attacks
	MaxSensitiveFields    = 1000            // Max redaction operations to prevent DoS
)

Security limits for JSON processing and logging

View Source
const (
	JSONTooLargeMessage     = "[JSON TOO LARGE FOR LOGGING]"
	JSONTooManySensitiveMsg = "[JSON CONTAINS TOO MANY SENSITIVE FIELDS]"
)

Logging message constants

View Source
const (
	// EncodingJSON uses standard JSON encoding
	EncodingJSON = "json"

	// EncodingJSONIETF uses JSON encoding with IETF conventions (default)
	// This is the recommended encoding for most use cases
	EncodingJSONIETF = "json_ietf"

	// EncodingProto uses Protocol Buffer encoding
	EncodingProto = "proto"

	// EncodingASCII uses ASCII encoding
	EncodingASCII = "ascii"

	// EncodingBytes uses raw byte encoding
	EncodingBytes = "bytes"
)

Encoding constants for gNMI operations

View Source
const (
	// MaxValueSize is the maximum size for a single value in bytes (10MB)
	MaxValueSize = 10 * 1024 * 1024

	// MaxPathLength is the maximum length for a gNMI path (1024 characters)
	MaxPathLength = 1024
)

Input validation constants

View Source
const MaxLogValueLength = 1024

MaxLogValueLength limits the length of log values to prevent log injection and excessive log file growth. Values longer than this are truncated.

Variables

View Source
var TransientErrors = []TransientError{

	{Code: uint32(codes.Unavailable)},

	{Code: uint32(codes.ResourceExhausted)},

	{Code: uint32(codes.DeadlineExceeded)},

	{Code: uint32(codes.Aborted)},
}

TransientErrors defines the list of gRPC status codes that should trigger automatic retry

These errors are typically caused by temporary conditions such as:

  • Service unavailable (server temporarily down, overloaded)
  • Resource exhausted (rate limiting, quota exceeded)
  • Deadline exceeded (timeout, slow network)
  • Aborted (transaction aborted, try again)

NOTE: codes.Internal is intentionally excluded from this list. While some Internal errors may be transient, codes.Internal is a catch-all error code that includes many permanent failures (bugs, invalid state, etc.). Blindly retrying Internal errors can mask real problems and waste resources. If specific Internal errors are known to be transient, they should be detected and handled explicitly rather than retrying all Internal errors.

Based on gRPC status codes from google.golang.org/grpc/codes

ValidEncodings contains the list of valid encoding values

Functions

func BackoffDelayFactor

func BackoffDelayFactor(factor float64) func(*Client)

BackoffDelayFactor sets the backoff multiplication factor (default: 2.0)

func BackoffMaxDelay

func BackoffMaxDelay(duration time.Duration) func(*Client)

BackoffMaxDelay sets the maximum backoff delay (default: 60s)

func BackoffMinDelay

func BackoffMinDelay(duration time.Duration) func(*Client)

BackoffMinDelay sets the minimum backoff delay (default: 1s)

func ConnectTimeout

func ConnectTimeout(duration time.Duration) func(*Client)

ConnectTimeout sets the connection timeout (default: 30s)

func GetEncoding

func GetEncoding(encoding string) func(*Req)

GetEncoding returns a request modifier that sets the encoding for Get operations.

Valid encodings: json, json_ietf (default), proto, ascii, bytes

This encoding overrides the default encoding (json_ietf) for this specific request. Note that the device must support the specified encoding.

Common encodings:

  • json_ietf: JSON with IETF conventions (recommended, default)
  • json: Standard JSON encoding
  • proto: Protocol Buffer encoding (binary)
  • ascii: ASCII text encoding
  • bytes: Raw byte encoding

The modifier validates the encoding at request time. If an invalid encoding is provided, the operation will fail with an error.

Example:

// Get with Protocol Buffer encoding
res, err := client.Get(ctx, []string{"/system/config"},
    gnmi.GetEncoding("proto"))

// Get with standard JSON
res, err := client.Get(ctx, []string{"/interfaces"},
    gnmi.GetEncoding("json"))

// Combined with timeout
res, err := client.Get(ctx, []string{"/interfaces"},
    gnmi.Timeout(30*time.Second),
    gnmi.GetEncoding("json_ietf"))

func MaxRetries

func MaxRetries(retries int) func(*Client)

MaxRetries sets the maximum number of retry attempts for transient errors (default: 3)

func OperationTimeout

func OperationTimeout(duration time.Duration) func(*Client)

OperationTimeout sets the operation timeout (default: 15s)

func Password

func Password(password string) func(*Client)

Password sets the password for gNMI authentication

func Port

func Port(port int) func(*Client)

Port sets the gNMI port (default: 57400)

func SetEncoding

func SetEncoding(encoding string) func(*SetOperation)

SetEncoding returns a modifier that sets the encoding for individual Set operations.

Valid encodings: json, json_ietf (default), proto, ascii, bytes

This modifier is used with Update() and Replace() operations to specify the encoding for individual operations within a Set request. Each operation can have a different encoding.

Common encodings:

  • json_ietf: JSON with IETF conventions (recommended, default)
  • json: Standard JSON encoding
  • proto: Protocol Buffer encoding (binary)
  • ascii: ASCII text encoding
  • bytes: Raw byte encoding

Example:

// Set with Protocol Buffer encoding
ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0]/config", protoBytes,
        gnmi.SetEncoding("proto")),
}

// Set with standard JSON (non-IETF)
ops := []gnmi.SetOperation{
    gnmi.Replace("/system/config", jsonData,
        gnmi.SetEncoding("json")),
}

// Default encoding (json_ietf) - no encoding parameter needed
ops := []gnmi.SetOperation{
    gnmi.Update("/system/hostname", `{"hostname": "router1"}`),
}

func TLS

func TLS(enabled bool) func(*Client)

TLS enables or disables TLS (default: true)

WARNING: Disabling TLS makes the connection vulnerable to eavesdropping and Man-in-the-Middle attacks. Only use this in isolated testing environments where security is not a concern.

func TLSCA

func TLSCA(caPath string) func(*Client)

TLSCA sets the TLS CA certificate file path for server verification

The CA certificate will be loaded and validated when the connection is established. If the CA file cannot be read, an error will be returned during connection.

func TLSCert

func TLSCert(certPath string) func(*Client)

TLSCert sets the TLS certificate file path for authentication

The certificate will be loaded and validated when the connection is established. If the certificate file cannot be read, an error will be returned during connection.

func TLSKey

func TLSKey(keyPath string) func(*Client)

TLSKey sets the TLS private key file path for authentication

The key will be loaded and validated when the connection is established. If the key file cannot be read, an error will be returned during connection.

func Timeout

func Timeout(duration time.Duration) func(*Req)

Timeout returns a request modifier that sets a custom timeout for the operation.

This timeout takes precedence over the context deadline and client's OperationTimeout. Use this to set operation-specific timeouts that differ from the client's default.

The timeout priority model is:

  1. Request-specific timeout (this modifier) - highest priority
  2. Context deadline (if already set) - medium priority
  3. Client.OperationTimeout - fallback default

Example:

// Get with 30 second timeout
res, err := client.Get(ctx, []string{"/interfaces"},
    gnmi.Timeout(30*time.Second))

// Set with 2 minute timeout for long-running operation
res, err := client.Set(ctx, ops,
    gnmi.Timeout(2*time.Minute))

func Username

func Username(username string) func(*Client)

Username sets the username for gNMI authentication

func ValidateEncoding

func ValidateEncoding(enc string) error

ValidateEncoding checks if the encoding is valid

Returns an error if the encoding is not one of the supported values.

Example:

if err := gnmi.ValidateEncoding("json_ietf"); err != nil {
    log.Fatal(err)
}

func VerifyCertificate

func VerifyCertificate(verify bool) func(*Client)

VerifyCertificate enables or disables TLS certificate verification (default: true)

WARNING: Disabling certificate verification makes the connection vulnerable to Man-in-the-Middle attacks. Only use this in testing environments where security is not a concern.

Example:

client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.VerifyCertificate(false))  // Insecure, use only for testing

func WithLogger

func WithLogger(logger Logger) func(*Client)

WithLogger configures a custom logger for the client

By default, the client uses NoOpLogger which discards all log messages. Use this option to enable logging with DefaultLogger or a custom logger.

All JSON content logged at Debug level is automatically redacted to remove sensitive data (passwords, secrets, keys, tokens).

Example (DefaultLogger):

logger := gnmi.NewDefaultLogger(gnmi.LogLevelInfo)
client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.WithLogger(logger))

Example (Custom Logger):

type SlogAdapter struct {
    logger *slog.Logger
}

func (s *SlogAdapter) Debug(ctx context.Context, msg string, keysAndValues ...interface{}) {
    s.logger.DebugContext(ctx, msg, keysAndValues...)
}
// ... implement Info, Warn, Error (all with ctx context.Context as first parameter)

client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.WithLogger(&SlogAdapter{logger: slog.Default()}))

func WithPrettyPrintLogs

func WithPrettyPrintLogs(enabled bool) func(*Client)

WithPrettyPrintLogs enables/disables JSON pretty printing in logs

When enabled, JSON content in debug logs is formatted for better readability. When disabled (default), raw JSON is logged without formatting.

This only affects Debug-level log output. Disabling pretty printing can improve performance when high-frequency operations are logged.

Default: disabled (false)

Example:

logger := gnmi.NewDefaultLogger(gnmi.LogLevelDebug)
client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.WithLogger(logger),
    gnmi.WithPrettyPrintLogs(true))  // Enable formatting for readability

Types

type Body

type Body struct {
	// contains filtered or unexported fields
}

Body provides a fluent interface for building JSON configurations using sjson for path-based manipulation.

The Body builder tracks errors internally to enable method chaining while providing error checking through String() or Err() methods.

Example:

body := gnmi.Body{}.
    Set("config.name", "GigabitEthernet0/0/0/0").
    Set("config.description", "WAN Interface").
    Set("config.enabled", true).
    Set("config.mtu", 9000)

value, err := body.String()
if err != nil {
    log.Fatal(err)
}

ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0/0/0/0]/config", value),
}

func (Body) Bytes

func (b Body) Bytes() ([]byte, error)

Bytes returns the JSON byte slice representation and any error encountered during building

This is useful when you need []byte instead of string for efficiency.

Example:

body := gnmi.Body{}.Set("name", "eth0")
jsonBytes, err := body.Bytes()
if err != nil {
    log.Fatal(err)
}

func (Body) Delete

func (b Body) Delete(path string) Body

Delete removes a value at the specified JSON path and returns a new Body

The path uses dot notation for nested fields (e.g., "config.description").

If an error occurs, the error is stored and returned by String() or Err().

Example:

body := gnmi.Body{}.
    Set("name", "eth0").
    Set("description", "temp").
    Delete("description")  // Remove description field
json, err := body.String()

Returns the Body for method chaining.

func (Body) Err

func (b Body) Err() error

Err returns any error that occurred during the building process

This method allows checking for errors without retrieving the string value.

Example:

body := gnmi.Body{}.Set("config.hostname", "router1")
if err := body.Err(); err != nil {
    log.Fatal(err)
}

func (Body) Res

func (b Body) Res() string

Res returns the JSON string for further processing with gjson

This allows you to query the built JSON using gjson's Get function. If an error occurred during building, this returns an empty string. Use Err() or String() to check for errors.

Example:

body := gnmi.Body{}.Set("config.hostname", "router1")
if body.Err() == nil {
    json := body.Res()
    hostname := gjson.Get(json, "config.hostname").String()
}

Returns the JSON string that can be queried with gjson.Get.

func (Body) Set

func (b Body) Set(path string, value any) Body

Set sets a value at the specified JSON path and returns a new Body

The path uses dot notation for nested fields (e.g., "config.name"). The value can be any type that sjson supports (string, number, bool, etc.).

If an error occurs, the error is stored and returned by String() or Err(). Once an error occurs, all subsequent operations are no-ops that preserve the error.

Example:

body := gnmi.Body{}.
    Set("config.name", "eth0").
    Set("config.enabled", true).
    Set("config.mtu", 1500)
json, err := body.String()

Returns the Body for method chaining.

func (Body) String

func (b Body) String() (string, error)

String returns the JSON string representation and any error encountered during building

This method returns both the JSON string and any error that occurred during the building process. If an error occurred during any Set/Delete operation, the error will be returned here.

Example:

body := gnmi.Body{}.Set("config.hostname", "router1")
json, err := body.String()
if err != nil {
    log.Fatal(err)
}

type CapabilitiesRes

type CapabilitiesRes struct {
	// Version is the gNMI service version
	Version string

	// Capabilities lists supported capabilities
	Capabilities []string

	// Models contains supported data models
	Models []*gnmi.ModelData

	// OK indicates if the operation succeeded
	OK bool

	// Errors contains any error information
	Errors []ErrorModel
}

CapabilitiesRes represents a gNMI Capabilities response

type Client

type Client struct {

	// Connection parameters
	Target string
	Port   int

	// TLS options
	UseTLS             bool
	VerifyCertificate  bool
	InsecureSkipVerify bool // Alias for !VerifyCertificate

	// Timeout configuration
	ConnectTimeout   time.Duration
	OperationTimeout time.Duration

	// Retry configuration
	MaxRetries         int
	BackoffMinDelay    time.Duration
	BackoffMaxDelay    time.Duration
	BackoffDelayFactor float64
	// contains filtered or unexported fields
}

Client represents a gNMI client connection to a network device

func NewClient

func NewClient(target string, opts ...func(*Client)) (*Client, error)

NewClient creates a new gNMI client with the specified target and options

The client creates a gnmic target configuration but does NOT establish a physical connection immediately. The connection is established automatically on the first RPC call (lazy connection). Use Ping() to explicitly verify connectivity if needed.

Example:

client, err := gnmi.NewClient(
    "192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.TLS(true),
    gnmi.VerifyCertificate(false),
    gnmi.MaxRetries(5),
)
if err != nil {
    log.Fatal(err)  // Configuration error
}
defer client.Close()

// Optional: verify connection explicitly
if err := client.Ping(ctx); err != nil {
    log.Fatal(err)  // Connection error
}

// Auto-connect on first use
res, err := client.Get(ctx, paths)

Returns a configured Client or an error if configuration validation fails.

func (*Client) Backoff

func (c *Client) Backoff(attempt int) time.Duration

Backoff calculates the backoff delay for retry attempt using exponential backoff with jitter

The formula is: delay = min(minDelay * (factor ^ attempt) + jitter, maxDelay) where jitter is a cryptographically secure random value in [0, delay * 0.1].

Security Note: Uses crypto/rand for jitter to prevent timing attack predictability. If crypto/rand fails, falls back to timestamp-based jitter to prevent thundering herd. Timestamp-based jitter is not cryptographically secure but provides sufficient randomness for retry dispersal.

Parameters:

  • attempt: The retry attempt number (0-indexed)

Returns the duration to wait before retrying.

func (*Client) Capabilities

func (c *Client) Capabilities(ctx context.Context) (CapabilitiesRes, error)

Capabilities retrieves the gNMI server capabilities

This operation performs a gNMI Capabilities RPC to discover:

  • gNMI service version
  • Supported encodings (json, json_ietf, proto, etc.)
  • Supported data models

The capabilities are stored in the client for later reference. Use HasCapability() to check for specific capabilities.

Example:

ctx := context.Background()
res, err := client.Capabilities(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("gNMI version: %s\n", res.Version)
for _, cap := range res.Capabilities {
    fmt.Printf("Encoding: %s\n", cap)
}

func (*Client) Close

func (c *Client) Close() error

Close closes the gNMI session and cleans up resources (terminal operation).

This method is TERMINAL - the client cannot be reused after calling Close(). All resources are released and the target configuration is destroyed.

Use Disconnect() instead if you want to temporarily close the connection while preserving the ability to reconnect later.

This method is typically used in defer statements to ensure proper cleanup:

Example:

client, err := gnmi.NewClient("192.168.1.1:57400", opts...)
if err != nil {
    log.Fatal(err)
}
defer client.Close() // Ensure cleanup on function exit

// Use client...
res, err := client.Get(ctx, paths)

Thread-safe: safe to call multiple times (subsequent calls are no-ops).

func (*Client) Disconnect

func (c *Client) Disconnect() error

Disconnect closes the gRPC connection but preserves client configuration.

Unlike Close(), this method allows the client to be reused - subsequent operations will automatically reconnect. This is useful for:

  • Connection pooling with idle timeout disconnection
  • Long-running applications that need to release connections temporarily
  • Testing reconnection logic

The client maintains its target configuration and will reconnect on the next RPC call via the ensureConnected() mechanism.

Example - Disconnect and reuse pattern:

client, _ := gnmi.NewClient("192.168.1.1:57400", opts...)
defer client.Close()

// Use client
_, err := client.Get(ctx, paths)

// Release connection temporarily
client.Disconnect()

// Automatically reconnects on next use
_, err = client.Get(ctx, paths)

Example - Connection pooling with idle timeout:

// Disconnect idle connections periodically
ticker := time.NewTicker(5 * time.Minute)
go func() {
    for range ticker.C {
        client.Disconnect() // Release idle connection
    }
}()

Example - Long-running application pattern:

// Disconnect during maintenance window
client.Disconnect()
performMaintenance()
// Reconnects automatically when needed
client.Get(ctx, paths)

Thread-safe: safe for concurrent use with other client methods.

func (*Client) Get

func (c *Client) Get(ctx context.Context, paths []string, mods ...func(*Req)) (GetRes, error)

Get performs a gNMI Get operation to retrieve data from the device

Get supports querying multiple paths in a single request. The paths parameter must be a non-empty slice of gNMI path strings. The encoding can be specified via request modifiers, defaulting to json_ietf.

The operation uses RLock for concurrent read access, allowing multiple Get operations to run in parallel. Context timeout follows priority:

  1. Request-specific timeout (via Timeout modifier)
  2. Context deadline (if already set)
  3. Client.OperationTimeout (fallback default)

Example:

ctx := context.Background()
paths := []string{
    "/interfaces/interface[name=GigabitEthernet0/0/0/0]/state",
    "/system/config/hostname",
}
res, err := client.Get(ctx, paths)
if err != nil {
    log.Fatal(err)
}
for _, notif := range res.Notifications {
    fmt.Printf("Path: %s\n", notif.Prefix)
}

Returns GetRes with notifications, timestamp, OK status, and any errors.

func (*Client) HasCapability

func (c *Client) HasCapability(capability string) bool

HasCapability checks if the server supports a specific capability

Example:

if client.HasCapability("gnmi-1.0") {
    // Use gNMI 1.0 features
}

func (*Client) HasCredentials

func (c *Client) HasCredentials() bool

HasCredentials returns true if credentials are configured

This method only indicates if credentials exist without exposing the actual values.

Example:

if client.HasCredentials() {
    fmt.Println("Client is configured with credentials")
}

func (*Client) Ping

func (c *Client) Ping(ctx context.Context) error

Ping verifies connectivity by performing a Capabilities RPC

This method establishes a connection if not already connected and performs a gNMI Capabilities request to verify the server is reachable and responsive. This is useful when you want to explicitly verify connectivity before performing other operations.

Example:

client, err := gnmi.NewClient("192.168.1.1:57400", opts...)
if err != nil {
    log.Fatal(err)  // Configuration error
}
defer client.Close()

// Verify connection before proceeding
if err := client.Ping(ctx); err != nil {
    log.Fatal(err)  // Connection error
}

// Now confident connection works
res, err := client.Get(ctx, paths)

func (*Client) ServerCapabilities

func (c *Client) ServerCapabilities() []string

ServerCapabilities returns the list of capabilities supported by the server

Returns a copy of the capabilities slice to prevent external modification.

Example:

caps := client.ServerCapabilities()
for _, cap := range caps {
    fmt.Println(cap)
}

func (*Client) Set

func (c *Client) Set(ctx context.Context, ops []SetOperation, mods ...func(*Req)) (SetRes, error)

Set performs a gNMI Set operation to configure the device

Set supports multiple update, replace, and delete operations in a single request. The ops parameter must be a non-empty slice of SetOperation structs created via the Update(), Replace(), or Delete() helper functions.

The operation uses Lock for exclusive write access, serializing all Set operations to prevent concurrent writes. Context timeout follows priority:

  1. Request-specific timeout (via Timeout modifier)
  2. Context deadline (if already set)
  3. Client.OperationTimeout (fallback default)

Example:

ctx := context.Background()
ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0/0/0/0]/config/description",
        `{"description": "WAN Interface"}`),
    gnmi.Replace("/interfaces/interface[name=Gi0/0/0/0]/config/enabled",
        `{"enabled": true}`),
    gnmi.Delete("/interfaces/interface[name=Gi0/0/0/1]/config"),
}
res, err := client.Set(ctx, ops)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Set operation successful: %v\n", res.OK)

Returns SetRes with response, timestamp, OK status, and any errors.

type DefaultLogger

type DefaultLogger struct {
	// contains filtered or unexported fields
}

DefaultLogger wraps Go's standard log package with configurable log level

Log output format: [LEVEL] message key1=value1 key2=value2

Context Parameter Usage:

DefaultLogger does NOT use the context parameter. It is provided to satisfy the Logger interface and enable integration with context-aware logging frameworks.

Custom logger implementations SHOULD use the context to extract trace correlation data such as:

  • Request ID / Trace ID for distributed tracing
  • User ID or tenant ID for multi-tenant applications
  • Deadline information for timeout debugging

Example of context-aware logging in a custom logger:

func (s *SlogAdapter) Debug(ctx context.Context, msg string, keysAndValues ...any) {
    // Extract trace ID from context
    if traceID := ctx.Value("trace_id"); traceID != nil {
        keysAndValues = append(keysAndValues, "trace_id", traceID)
    }
    s.logger.DebugContext(ctx, msg, keysAndValues...)
}

Example:

logger := gnmi.NewDefaultLogger(gnmi.LogLevelDebug)
client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.WithLogger(logger))

func NewDefaultLogger

func NewDefaultLogger(level LogLevel) *DefaultLogger

NewDefaultLogger creates a DefaultLogger with the specified log level

func (*DefaultLogger) Debug

func (l *DefaultLogger) Debug(ctx context.Context, msg string, keysAndValues ...any)

Debug logs a debug message with structured key-value pairs

func (*DefaultLogger) Error

func (l *DefaultLogger) Error(ctx context.Context, msg string, keysAndValues ...any)

Error logs an error message with structured key-value pairs

func (*DefaultLogger) Info

func (l *DefaultLogger) Info(ctx context.Context, msg string, keysAndValues ...any)

Info logs an informational message with structured key-value pairs

func (*DefaultLogger) Warn

func (l *DefaultLogger) Warn(ctx context.Context, msg string, keysAndValues ...any)

Warn logs a warning message with structured key-value pairs

type ErrorModel

type ErrorModel struct {
	// Code is the gRPC status code
	Code uint32

	// Message is the error message
	Message string

	// Details contains additional error information
	Details string
}

ErrorModel represents a gNMI error with gRPC status code

type GetRes

type GetRes struct {
	// Notifications contains the gNMI notification messages
	Notifications []*gnmi.Notification

	// Timestamp is the response timestamp (nanoseconds since Unix epoch)
	Timestamp int64

	// OK indicates if the operation succeeded
	OK bool

	// Errors contains any error information
	Errors []ErrorModel
}

GetRes represents a gNMI Get response

func (GetRes) GetValue

func (r GetRes) GetValue(path string) gjson.Result

GetValue retrieves a value from the response notifications using a gjson path. The path follows gjson syntax for querying JSON structures.

Example paths:

  • "notification.0.timestamp" - Get notification timestamp
  • "notification.0.update.0.path.elem.0.name" - Get path element name
  • "notification.0.update.0.val.Value.JsonIetfVal" - Get JSON IETF value (base64 encoded)

Note: The JSON structure uses protobuf JSON marshaling conventions where field names are lowercase and TypedValue.Value is nested with capitalized names.

Returns gjson.Result which can be converted to specific types:

  • result.String() for string values
  • result.Int() for integer values
  • result.Bool() for boolean values
  • result.Array() for array values

Example:

res, err := client.Get(ctx, []string{"/interfaces/interface[name=Gi0/0/0/0]/state"})
if err != nil {
    log.Fatal(err)
}
timestamp := res.GetValue("notification.0.timestamp").Int()
pathName := res.GetValue("notification.0.update.0.path.elem.0.name").String()
Example

Example test demonstrating the usage pattern

// This example shows how to use GetValue with gjson paths
notification := &gnmi.Notification{
	Timestamp: 1234567890,
	Update: []*gnmi.Update{
		{
			Path: &gnmi.Path{
				Elem: []*gnmi.PathElem{
					{Name: "system"},
					{Name: "config"},
				},
			},
			Val: &gnmi.TypedValue{
				Value: &gnmi.TypedValue_JsonIetfVal{
					JsonIetfVal: []byte(`{"hostname": "router1"}`),
				},
			},
		},
	},
}

res := GetRes{
	Notifications: []*gnmi.Notification{notification},
	Timestamp:     1234567890,
	OK:            true,
}

// Get the JSON value
jsonValue := res.GetValue("notification.0.update.0.val.json_ietf_val").String()
_ = jsonValue // Use the value

func (GetRes) JSON

func (r GetRes) JSON() string

JSON returns the response notifications as a formatted JSON string. This is useful for debugging, logging, or custom parsing. Returns an empty string if marshaling fails.

Example:

res, err := client.Get(ctx, []string{"/system/config"})
if err != nil {
    log.Fatal(err)
}
fmt.Println(res.JSON()) // Print full response as JSON

type GnmiError

type GnmiError struct {
	// Operation name that failed
	Operation string

	// Errors from gNMI error details
	Errors []ErrorModel

	// Human-readable error message
	Message string

	// InternalMsg contains detailed error information for internal logging
	InternalMsg string

	// Number of retry attempts made
	Retries int

	// IsTransient indicates if the error is transient and was retried
	IsTransient bool
}

GnmiError represents a structured gNMI error with operation context

func (*GnmiError) DetailedError

func (e *GnmiError) DetailedError() string

DetailedError returns the full error message including internal details

This should only be used in secure logging contexts where sensitive information disclosure is acceptable (e.g., server-side logs, debug output).

Example:

if err != nil {
    if gnmiErr, ok := err.(*GnmiError); ok {
        log.Debug(gnmiErr.DetailedError()) // internal logging
        return gnmiErr.Error() // client-facing error
    }
}

func (*GnmiError) Error

func (e *GnmiError) Error() string

Error implements the error interface

type LogLevel

type LogLevel int

LogLevel represents the severity threshold for logging

const (
	// LogLevelDebug enables all log levels (most verbose)
	LogLevelDebug LogLevel = iota

	// LogLevelInfo enables Info, Warn, and Error logs
	LogLevelInfo

	// LogLevelWarn enables Warn and Error logs
	LogLevelWarn

	// LogLevelError enables only Error logs
	LogLevelError

	// LogLevelNone disables all logging
	LogLevelNone
)

func (LogLevel) String

func (l LogLevel) String() string

String returns the string representation of a LogLevel

type Logger

type Logger interface {
	Debug(ctx context.Context, msg string, keysAndValues ...any)
	Info(ctx context.Context, msg string, keysAndValues ...any)
	Warn(ctx context.Context, msg string, keysAndValues ...any)
	Error(ctx context.Context, msg string, keysAndValues ...any)
}

Logger interface for pluggable logging support

All methods receive a context.Context as the first parameter, enabling integration with context-based logging frameworks and distributed tracing.

Implementations should use structured logging with key-value pairs. The go-gnmi library provides two implementations:

  • DefaultLogger: Wraps Go's standard log package with configurable log level
  • NoOpLogger: Zero-overhead logging when disabled (default)

Context Usage Guidelines:

The context parameter enables trace correlation and debugging:

  • Use context.Background() for utility methods and internal operations
  • Propagate the user's context from API calls (Get, Set, Subscribe, etc.)
  • Extract trace IDs, request IDs, or tenant IDs for correlation
  • Check context deadline to log timeout information

When to use context.Background():

  • Internal utility methods (validation, formatting, sanitization)
  • Constructor methods (NewClient)
  • Configuration processing
  • Static operations that don't involve user requests

When to propagate user context:

  • gNMI operations (Get, Set, Subscribe, Capabilities)
  • Network operations (Connect, retry logic)
  • Request/response processing
  • Any operation initiated by a user API call

Example custom logger integration:

type SlogAdapter struct {
    logger *slog.Logger
}

func (s *SlogAdapter) Debug(ctx context.Context, msg string, keysAndValues ...any) {
    s.logger.DebugContext(ctx, msg, keysAndValues...)
}
// ... implement other methods

client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"),
    gnmi.WithLogger(&SlogAdapter{logger: slog.Default()}))

type NoOpLogger

type NoOpLogger struct{}

NoOpLogger is a no-operation logger that discards all log messages

This logger provides zero overhead when logging is disabled. All methods are no-ops and will be optimized away by the compiler.

This is the default logger used by go-gnmi when no custom logger is configured.

Example:

// Logging is disabled by default (uses NoOpLogger)
client, _ := gnmi.NewClient("192.168.1.1:57400",
    gnmi.Username("admin"),
    gnmi.Password("secret"))

func (*NoOpLogger) Debug

func (n *NoOpLogger) Debug(_ context.Context, _ string, _ ...any)

Debug discards the log message

func (*NoOpLogger) Error

func (n *NoOpLogger) Error(_ context.Context, _ string, _ ...any)

Error discards the log message

func (*NoOpLogger) Info

func (n *NoOpLogger) Info(_ context.Context, _ string, _ ...any)

Info discards the log message

func (*NoOpLogger) Warn

func (n *NoOpLogger) Warn(_ context.Context, _ string, _ ...any)

Warn discards the log message

type Req

type Req struct {
	// Encoding specifies the data encoding
	// Valid values: json, json_ietf (default), proto, ascii, bytes
	Encoding string

	// Timeout is the request-specific timeout
	// Overrides client default timeout if set
	Timeout time.Duration
}

Req represents a gNMI request modifier

This struct is used to apply request-specific options via functional modifiers. Operation parameters (paths, operations) are passed directly to methods.

Example:

// Get with custom encoding and timeout
res, err := client.Get(ctx, paths,
    gnmi.Encoding("proto"),
    gnmi.Timeout(30*time.Second))

type SetOperation

type SetOperation struct {
	// OperationType specifies the operation type (update, replace, delete)
	OperationType SetOperationType

	// Path is the gNMI path
	Path string

	// Value is the JSON value for Update/Replace operations
	// Empty for Delete operations
	Value string

	// Encoding specifies the value encoding
	// Valid values: json, json_ietf (default), proto, ascii, bytes
	Encoding string
}

SetOperation represents a single gNMI Set operation (Update, Replace, or Delete)

func Delete

func Delete(path string) SetOperation

Delete creates a SetOperation for deleting a path

Delete operations remove configuration at the specified path.

Parameters:

  • path: gNMI path string

Example:

op := gnmi.Delete("/interfaces/interface[name=Gi0/0/0/1]/config")

func Replace

func Replace(path, value string, opts ...func(*SetOperation)) SetOperation

Replace creates a SetOperation for replacing a path with a value

Replace operations remove existing configuration at the path before applying the new value. Use Replace when you need to ensure no old config remains.

The encoding defaults to json_ietf. Use the SetEncoding() modifier to specify a different encoding (json, proto, ascii, bytes).

Parameters:

  • path: gNMI path string
  • value: JSON-encoded value string
  • opts: optional modifiers (SetEncoding, etc.)

Example:

// Default encoding (json_ietf)
op := gnmi.Replace("/interfaces/interface[name=Gi0/0/0/0]/config",
    `{"mtu": 9000}`)

// Explicit encoding
op := gnmi.Replace("/system/config", jsonData,
    gnmi.SetEncoding("json"))

func Update

func Update(path, value string, opts ...func(*SetOperation)) SetOperation

Update creates a SetOperation for updating a path with a value

Update operations modify existing configuration, creating it if it doesn't exist. This is the most common Set operation type.

The encoding defaults to json_ietf. Use the SetEncoding() modifier to specify a different encoding (json, proto, ascii, bytes).

Parameters:

  • path: gNMI path string (e.g., "/interfaces/interface[name=Gi0/0/0/0]/config")
  • value: JSON-encoded value string
  • opts: optional modifiers (SetEncoding, etc.)

Example:

// Default encoding (json_ietf)
op := gnmi.Update("/system/config/hostname", `{"hostname": "router1"}`)

// Explicit encoding
op := gnmi.Update("/interfaces/interface[name=Gi0]/config", protoBytes,
    gnmi.SetEncoding("proto"))

type SetOperationType

type SetOperationType string

SetOperationType represents the type of Set operation

const (
	// OperationUpdate modifies existing configuration, creating it if it doesn't exist
	OperationUpdate SetOperationType = "update"

	// OperationReplace removes existing configuration before applying new value
	OperationReplace SetOperationType = "replace"

	// OperationDelete removes configuration at the specified path
	OperationDelete SetOperationType = "delete"
)

type SetRes

type SetRes struct {
	// Response is the gNMI SetResponse
	Response *gnmi.SetResponse

	// Timestamp is the response timestamp (nanoseconds since Unix epoch)
	Timestamp int64

	// OK indicates if the operation succeeded
	OK bool

	// Errors contains any error information
	Errors []ErrorModel
}

SetRes represents a gNMI Set response

func (SetRes) GetValue

func (r SetRes) GetValue(path string) gjson.Result

GetValue retrieves a value from the SetResponse using a gjson path. The path follows gjson syntax for querying JSON structures.

Example paths:

  • "response.0.op" - Get operation type (UPDATE, REPLACE, DELETE)
  • "response.0.path" - Get path string
  • "timestamp" - Get response timestamp

Returns gjson.Result which can be converted to specific types:

  • result.String() for string values
  • result.Int() for integer values

Example:

ops := []gnmi.SetOperation{
    gnmi.Update("/interfaces/interface[name=Gi0/0/0/0]/config/mtu", `{"mtu": 9000}`),
}
res, err := client.Set(ctx, ops)
if err != nil {
    log.Fatal(err)
}
op := res.GetValue("response.0.op").String()

func (SetRes) JSON

func (r SetRes) JSON() string

JSON returns the SetResponse as a formatted JSON string. This is useful for debugging, logging, or custom parsing. Returns an empty string if marshaling fails.

Example:

ops := []gnmi.SetOperation{
    gnmi.Update("/system/config/hostname", `{"hostname": "router1"}`),
}
res, err := client.Set(ctx, ops)
if err != nil {
    log.Fatal(err)
}
fmt.Println(res.JSON()) // Print full response as JSON

type TransientError

type TransientError struct {
	// Code is the gRPC status code to match
	Code uint32
}

TransientError defines patterns for detecting transient errors that should be retried

Directories

Path Synopsis
examples
basic command
Package main demonstrates basic go-gnmi API usage.
Package main demonstrates basic go-gnmi API usage.
capabilities command
Package main demonstrates gNMI capability discovery.
Package main demonstrates gNMI capability discovery.
concurrent command
Package main demonstrates concurrent operations with go-gnmi.
Package main demonstrates concurrent operations with go-gnmi.
disconnect command
Package main demonstrates the use of Disconnect() for connection pooling and temporary connection release patterns in go-gnmi.
Package main demonstrates the use of Disconnect() for connection pooling and temporary connection release patterns in go-gnmi.
logging command
Package main demonstrates logging configuration in go-gnmi.
Package main demonstrates logging configuration in go-gnmi.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL