helpers

package
v0.11.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AcquireNetconfLock added in v0.10.0

func AcquireNetconfLock(opMutex *sync.Mutex, reuseConnection bool, isWrite bool) bool

AcquireNetconfLock acquires the appropriate lock for a NETCONF operation.

Lock strategy based on reuseConnection and operation type: - When reuseConnection=false: Lock for ALL operations (serializes everything including Close) - When reuseConnection=true && isWrite=true: Lock for WRITE operations (Lock/EditConfig/Commit sequence) - When reuseConnection=true && isWrite=false: NO lock for READ operations (concurrent reads allowed)

This prevents issues: - When reuse disabled: Prevents concurrent Close() attempts on same connection - When reuse enabled: Serializes write sequences but allows concurrent reads

Returns true if lock was acquired, false if not acquired.

Usage:

locked := helpers.AcquireNetconfLock(opMutex, reuseConnection, isWrite)
if locked {
    defer opMutex.Unlock()
}
defer helpers.CloseNetconfConnection(ctx, client, reuseConnection)

func AppendFromXPath added in v0.10.0

func AppendFromXPath(body netconf.Body, xPath string, value any) netconf.Body

AppendFromXPath creates all elements in an XPath and appends a value to a list by using the ".-1" syntax. This is useful for adding multiple items to a list without keys. The function automatically appends ".-1" to the final element in the path.

Example:

body := netconf.Body{}
body = AppendFromXPath(body, "native/route-map/rule/match/ip/address", "10")
body = AppendFromXPath(body, "native/route-map/rule/match/ip/address", "20")
// Result: <native><route-map><rule><match><ip>
//           <address>10</address>
//           <address>20</address>
//         </ip></match></rule></route-map></native>

Note: This function is designed for simple list items without keys. For lists with keys, use SetFromXPath with predicates instead.

func CleanupRedundantRemoveOperations added in v0.10.0

func CleanupRedundantRemoveOperations(body netconf.Body) netconf.Body

CleanupRedundantRemoveOperations removes redundant operation="remove" attributes from child elements when a parent element already has operation="remove". This is called after the full NETCONF payload has been built to sanitize the XML before sending to the device.

Example:

Input:  <trap operation="remove"><severity operation="remove"></severity></trap>
Output: <trap operation="remove"></trap>

This prevents NETCONF errors like '"" is not a valid value' for empty child elements.

func CloseNetconfConnection added in v0.10.0

func CloseNetconfConnection(ctx context.Context, client *netconf.Client, reuseConnection bool)

CloseNetconfConnection safely closes a NETCONF connection if reuse is disabled.

IMPORTANT: This must be called with the operation mutex still held (deferred after AcquireNetconfLock) to prevent concurrent close attempts.

Usage:

defer helpers.CloseNetconfConnection(ctx, device.NetconfClient, device.ReuseConnection)

func Commit added in v0.10.0

func Commit(ctx context.Context, client *netconf.Client) error

Commit commits the candidate datastore to the running datastore

func Contains

func Contains(s []string, str string) bool

Contains checks if a string exists in a slice of strings.

func ConvertXPathToRestconfPath added in v0.10.0

func ConvertXPathToRestconfPath(xPath string) string

ConvertXPathToRestconfPath converts an XPath-style path to RESTCONF-style path. It retains placeholders (%v, %s, %d) and namespace prefixes. Values are URL-encoded as required by RESTCONF (except placeholders).

XPath uses: element[key=value] or element[%v=value] RESTCONF uses: element=value (URL-encoded)

Examples:

  • "interface[name=GigabitEthernet1]" → "interface=GigabitEthernet1"
  • "interface[%v=GigabitEthernet1]" → "interface=%v"
  • "interface[%v=%v]" → "interface=%v"
  • "vrf[name=VRF1][af-name=ipv4]" → "vrf=VRF1,ipv4"
  • "Cisco-IOS-XE-native:native/vrf[%v=%v]" → "Cisco-IOS-XE-native:native/vrf=%v"
  • "native/interface[name='GigabitEthernet1/0/1']" → "native/interface=GigabitEthernet1%2F0%2F1"
  • "route-map[name='test map']" → "route-map=test+map"

func EditConfig added in v0.10.0

func EditConfig(ctx context.Context, client *netconf.Client, body string, commit bool) error

EditConfig edits the configuration on the device If the server supports the candidate capability, it will edit the configuration in the candidate datastore and commit it to the running datastore if commit is true. If the server does not support the candidate capability, it will edit the configuration in the running datastore.

IMPORTANT: When connection reuse is enabled, callers MUST serialize calls to EditConfig using an application-level mutex that also covers ManageNetconfConnection(). This prevents concurrent goroutines from attempting to acquire NETCONF datastore locks simultaneously on the same session.

Parameters:

  • ctx: context.Context
  • client: *netconf.Client
  • body: string
  • commit: bool

func FormatNetconfError added in v0.10.0

func FormatNetconfError(err error) string

FormatNetconfError extracts detailed error information from a NETCONF error

func GetFromXPath added in v0.10.0

func GetFromXPath(res xmldot.Result, xPath string) xmldot.Result

GetFromXPath converts an XPath expression to a xmldot path and retrieves the result. Uses manual filtering to correctly handle both single and multiple elements with predicates. Supports the same XPath formats as SetFromXPath:

  • Single: /interface[name='GigabitEthernet1']
  • Multiple predicates: /interface[name='GigabitEthernet1'][vrf='VRF1']
  • Combined with 'and': /interface[name='GigabitEthernet1' and vrf='VRF1']
  • Values with slashes: /interface[name='GigabitEthernet1/0/1']
  • Nested paths: /native/interface[name='Gi1']/ip/address

Example: /native/interface[name='Gi1']/ip/address Processes path segments, validates predicates, and constructs the final xmldot path

func GetInt64List

func GetInt64List(result []gjson.Result) types.List

GetInt64List converts a slice of gjson.Result to a Terraform types.List of int64.

func GetInt64ListXML added in v0.10.0

func GetInt64ListXML(result []xmldot.Result) types.List

GetInt64ListXML converts a slice of xmldot.Result to a Terraform types.List of int64.

func GetInt64Set added in v0.8.0

func GetInt64Set(result []gjson.Result) types.Set

GetInt64Set converts a slice of gjson.Result to a Terraform types.Set of int64.

func GetInt64SetXML added in v0.10.0

func GetInt64SetXML(result []xmldot.Result) types.Set

GetInt64SetXML converts a slice of xmldot.Result to a Terraform types.Set of int64.

func GetStringList

func GetStringList(result []gjson.Result) types.List

GetStringList converts a slice of gjson.Result to a Terraform types.List of strings.

func GetStringListXML added in v0.10.0

func GetStringListXML(result []xmldot.Result) types.List

GetStringListXML converts a slice of xmldot.Result to a Terraform types.List of strings.

func GetStringSet added in v0.8.0

func GetStringSet(result []gjson.Result) types.Set

GetStringSet converts a slice of gjson.Result to a Terraform types.Set of strings.

func GetStringSetXML added in v0.10.0

func GetStringSetXML(result []xmldot.Result) types.Set

GetStringSetXML converts a slice of xmldot.Result to a Terraform types.Set of strings.

func GetValueSlice

func GetValueSlice(result []gjson.Result) []attr.Value

GetValueSlice converts a slice of gjson.Result to a slice of Terraform attr.Value.

func GetXpathFilter added in v0.10.0

func GetXpathFilter(xPath string) netconf.Filter

GetXpathFilter creates a NETCONF XPath filter with namespace prefixes removed. It processes the XPath expression to strip namespace prefixes from both element names and predicate key names, preserving the path structure.

Supports the same XPath formats as SetFromXPath and GetFromXPath:

  • Paths with namespace prefixes: /Cisco-IOS-XE-native:native/interface
  • Single predicates: /native/interface[name='GigabitEthernet1']
  • Multiple predicates: /native/interface[name='Gi1'][vrf='VRF1']
  • Nested paths: /native/interface[name='Gi1']/ip/address
  • Values with slashes: /native/interface[name='GigabitEthernet1/0/1']
  • Predicates with namespace prefixes: /Cisco-IOS-XE-native:interface[Cisco-IOS-XE-native:name='Gi1']

Example transformations:

Input:  "/Cisco-IOS-XE-native:native/aaa"
Output: netconf.Filter{Type: "xpath", Content: "/native/aaa"}

Input:  "/Cisco-IOS-XE-native:native/interface[Cisco-IOS-XE-native:name='Gi1']/Cisco-IOS-XE-native:ip"
Output: netconf.Filter{Type: "xpath", Content: "/native/interface[name='Gi1']/ip"}

Input:  "/native/interface[name='GigabitEthernet1/0/1']"
Output: netconf.Filter{Type: "xpath", Content: "/native/interface[name='GigabitEthernet1/0/1']"}

func IsFlagImporting added in v0.5.8

func IsFlagImporting(ctx context.Context, req resource.ReadRequest) (bool, diag.Diagnostics)

func IsGetConfigResponseEmpty added in v0.10.1

func IsGetConfigResponseEmpty(res *netconf.Res) bool

IsGetConfigResponseEmpty checks if a GetConfig response has an empty <data> element. Returns true if the response contains <data></data> with no child elements, indicating that the requested configuration does not exist on the device.

This is useful for determining if a resource exists before attempting to parse its attributes, particularly during Read operations or import.

IMPORTANT: This should typically be combined with IsListPath() to only treat empty responses as "not found" for list items:

if helpers.IsGetConfigResponseEmpty(&res) && helpers.IsListPath(state.getXPath()) {
    // List item does not exist
    resp.State.RemoveResource(ctx)
    return
}

Parameters:

  • res: The NETCONF response from GetConfig operation

Returns:

  • true if the data element is empty (no child elements)
  • false if the data element contains configuration

Example usage:

res, err := device.NetconfClient.GetConfig(ctx, "running", filter)
if err != nil {
    return err
}
if helpers.IsGetConfigResponseEmpty(&res) && helpers.IsListPath(state.getXPath()) {
    // Resource does not exist (list item)
    resp.State.RemoveResource(ctx)
    return
}
// Parse the configuration
state.fromBodyXML(ctx, res.Res)

func IsListPath added in v0.10.1

func IsListPath(xPath string) bool

IsListPath checks if an XPath represents a list item (ends with a predicate). List items have predicates like [name='value'] or [name=value] at the end of their XPath, while containers/singletons don't end with predicates.

This is useful for determining if an empty GetConfig response should be interpreted as "resource not found" (for list items) vs other semantics.

Parameters:

  • xPath: The XPath to check

Returns:

  • true if the path ends with a predicate (is a list item)
  • false if the path does not end with a predicate (is a container/singleton)

Examples:

  • "/native/interface/Vlan[name=10]" → true (ends with predicate)
  • "/native/interface/GigabitEthernet[name='1/0/1']" → true (ends with predicate)
  • "/native/router/bgp[id=65000]/neighbor" → false (has predicate but doesn't end with one)
  • "/native/clock" → false (container)
  • "/native/hostname" → false (singleton)

func LastElement

func LastElement(path string) string

LastElement returns the last element of a YANG path with its namespace prefix. Example: "Cisco-IOS-XE-native:native/interface/GigabitEthernet=1" -> "Cisco-IOS-XE-native:GigabitEthernet"

func Must added in v0.5.8

func Must[T any](v T, err error) T

Must panics if err is not nil, otherwise returns the value v. Useful for must-succeed operations in initialization code.

func RemoveEmptyStrings added in v0.7.0

func RemoveEmptyStrings(s []string) []string

RemoveEmptyStrings filters out empty strings from a slice.

func RemoveFromXPath added in v0.10.0

func RemoveFromXPath(body netconf.Body, xPath string) netconf.Body

RemoveFromXPath creates all elements in an XPath with an operation="remove" attribute on the last element for NETCONF delete operations. Supports the same XPath formats as SetFromXPath.

Example: /native/interface[name='Gi1']/ip/address Creates: <native><interface><name>Gi1</name><ip><address operation="remove"/></ip></interface></native>

func SaveConfig added in v0.10.0

func SaveConfig(ctx context.Context, client *netconf.Client) error

SaveConfig saves the running configuration to startup configuration. This is equivalent to 'copy running-config startup-config' in the CLI. Uses the cisco-ia:save-config RPC operation.

func SaveConfigRestconf added in v0.10.1

func SaveConfigRestconf(client *restconf.Client) error

SaveConfigRestconf saves the running configuration to startup configuration via RESTCONF. This is equivalent to 'copy running-config startup-config' in the CLI. Uses the cisco-ia:save-config RPC operation via RESTCONF POST.

Parameters:

  • client: The RESTCONF client to use for the operation

Returns:

  • error if the save operation fails, nil otherwise

Example usage:

if err := helpers.SaveConfigRestconf(device.RestconfClient); err != nil {
    return fmt.Errorf("failed to save config: %s", err)
}

func SetFlagImporting added in v0.5.8

func SetFlagImporting(ctx context.Context, importing bool, sk SetKeyer, respDiags *diag.Diagnostics)

SetFlagImporting checks the respDiags and if they are error-free it sets the `importing` as a private flag inside SetKeyer. It appends its own results to respDiags.

The caller must include in respDiags the result of state modification in the first place, to ensure consistency. The SetKeyer is something like resp.Private.

func SetFromXPath added in v0.10.0

func SetFromXPath(body netconf.Body, xPath string, value any) netconf.Body

SetFromXPath creates all elements in an XPath, including keys and namespaces, and optionally sets a value at the final path location. Supports single and composite keys:

  • Single: /interface[name='GigabitEthernet1']
  • Multiple predicates: /interface[name='GigabitEthernet1'][vrf='VRF1']
  • Combined with 'and': /interface[name='GigabitEthernet1' and vrf='VRF1']
  • Values with slashes: /interface[name='GigabitEthernet1/0/1']

If value is nil or empty string, only the structure is created without setting a value. If value is non-empty, it's set at the final path location.

Multi-Root Support: SetFromXPath properly handles multiple root-level sibling elements (e.g., both <deny> and <permit> at the same level). The underlying xmldot library automatically detects when different root paths are used and creates sibling elements instead of nesting them.

Example (multi-root XML):

body := netconf.Body{}
body = SetFromXPath(body, "sequence", "10")
body = SetFromXPath(body, "deny/std-ace/prefix", "10.0.0.0")
body = SetFromXPath(body, "permit/std-ace/prefix", "192.168.0.0")
// Result: <sequence>10</sequence><deny>...</deny><permit>...</permit>

func SetRawFromXPath added in v0.10.0

func SetRawFromXPath(body netconf.Body, xPath string, value string) netconf.Body

SetRawFromXPath creates all elements in an XPath, including keys and namespace declarations, then inserts raw XML content at the final path location. This is useful when you have pre-formatted XML that needs to be inserted as child elements.

The value parameter should contain raw XML content (child elements, attributes, etc.) that will be parsed and inserted at the target path. The content is wrapped in the final element tag specified by the xPath.

Multi-root Support: When called multiple times with the same path, this function appends the new XML content as an additional sibling element, creating a multi-root XML fragment at the parent level. The underlying xmldot library automatically handles multi-root fragments, making this safe for creating multiple sibling elements (e.g., multiple <interface> elements in a list).

Example (single call):

xPath: /Cisco-IOS-XE-native:native/interface[name='Gi1']
value: "<description>Management</description><shutdown/>"
Result: <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
          <interface>
            <name>Gi1</name>
            <description>Management</description>
            <shutdown/>
          </interface>
        </native>

Example (multiple calls - multi-root):

First call:  SetRawFromXPath(body, "/native/interface", "<name>Gi1</name>")
Second call: SetRawFromXPath(body, "/native/interface", "<name>Gi2</name>")
Result: <native>
          <interface><name>Gi1</name></interface>
          <interface><name>Gi2</name></interface>
        </native>

Unlike SetFromXPath, this function:

  • Adds xmlns declarations for namespace prefixes in the path (via buildXPathStructure)
  • Inserts the value as raw XML (parsed as child elements) rather than as text content
  • Uses body.SetRaw() instead of body.Set() for XML insertion
  • Supports appending multiple elements at the same path (multi-root fragments)

Types

type AttributeDescription

type AttributeDescription struct {
	String string
}

func NewAttributeDescription

func NewAttributeDescription(s string) *AttributeDescription

func (*AttributeDescription) AddDefaultValueDescription

func (d *AttributeDescription) AddDefaultValueDescription(defaultValue string) *AttributeDescription

func (*AttributeDescription) AddFloatRangeDescription

func (d *AttributeDescription) AddFloatRangeDescription(min, max float64) *AttributeDescription

func (*AttributeDescription) AddIntegerRangeDescription

func (d *AttributeDescription) AddIntegerRangeDescription(min, max int64) *AttributeDescription

func (*AttributeDescription) AddStringEnumDescription

func (d *AttributeDescription) AddStringEnumDescription(values ...string) *AttributeDescription

type KeyValue added in v0.10.0

type KeyValue struct {
	Key   string
	Value string
}

KeyValue represents a key-value pair with preserved order

type SetKeyer added in v0.5.8

type SetKeyer interface {
	SetKey(ctx context.Context, key string, value []byte) diag.Diagnostics
}

SetKeyer is something like ReadResponse.Private or ImportStateResponse.Private.

type TflogAdapter added in v0.10.0

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

TflogAdapter adapts go-netconf's Logger interface to Terraform's tflog package.

This adapter bridges the gap between go-netconf's logging interface and Terraform's context-based logging. It leverages go-netconf's context-aware Logger interface, which passes context as the first parameter to all logging methods.

The adapter automatically creates the "netconf" subsystem on first use, eliminating the need for manual subsystem creation in provider code.

The adapter stores a device identifier (host or device name) that is included in all log messages, making it easy to correlate logs when operating on multiple devices in parallel.

Thread-safety: This adapter is safe for concurrent use. Context is passed per log call via the Logger interface, and the device identifier is immutable after creation.

Usage in provider initialization:

logger := helpers.NewTflogAdapter("192.168.1.1")
client, err := netconf.NewClient(host,
    netconf.Username(username),
    netconf.Password(password),
    netconf.WithLogger(logger),
)

Usage in resource operations:

// Context is automatically propagated through go-netconf's Logger interface
_, err := device.NetconfClient.GetConfig(ctx, "running", filter)
// Logs will automatically include device identifier and use the correct context and netconf subsystem

func NewTflogAdapter added in v0.10.0

func NewTflogAdapter(deviceID string) *TflogAdapter

NewTflogAdapter creates a new Terraform logging adapter with device identification.

The adapter automatically receives context from go-netconf's Logger interface on each logging call, ensuring proper context propagation without manual management.

The deviceID parameter should be a unique identifier for the device (e.g., hostname, IP address, or device name from configuration). This identifier is included in all log messages to enable correlation when operating on multiple devices in parallel.

Parameters:

  • deviceID: Unique identifier for the device (e.g., "192.168.1.1" or "core-switch-1")

Returns:

  • *TflogAdapter: A new adapter configured for the specified device

func (*TflogAdapter) Debug added in v0.10.0

func (t *TflogAdapter) Debug(ctx context.Context, msg string, keysAndValues ...any)

Debug logs a debug message with structured key-value pairs to tflog.SubsystemDebug.

Debug logs are typically used for detailed operational information useful for troubleshooting and development.

Context is provided by go-netconf's Logger interface, ensuring automatic propagation of trace IDs, request IDs, and deadlines.

Logs are written to the "netconf" subsystem for proper organization and filtering. The subsystem is automatically created if it doesn't exist.

The device identifier is automatically included in the log fields for correlation.

func (*TflogAdapter) Error added in v0.10.0

func (t *TflogAdapter) Error(ctx context.Context, msg string, keysAndValues ...any)

Error logs an error message with structured key-value pairs to tflog.SubsystemError.

Errors indicate serious problems that prevent successful operation.

Context is provided by go-netconf's Logger interface, ensuring automatic propagation of trace IDs, request IDs, and deadlines.

Logs are written to the "netconf" subsystem for proper organization and filtering. The subsystem is automatically created if it doesn't exist.

The device identifier is automatically included in the log fields for correlation.

func (*TflogAdapter) Info added in v0.10.0

func (t *TflogAdapter) Info(ctx context.Context, msg string, keysAndValues ...any)

Info logs an informational message with structured key-value pairs to tflog.SubsystemInfo.

Info logs represent normal operational messages that highlight progress or state changes.

Context is provided by go-netconf's Logger interface, ensuring automatic propagation of trace IDs, request IDs, and deadlines.

Logs are written to the "netconf" subsystem for proper organization and filtering. The subsystem is automatically created if it doesn't exist.

The device identifier is automatically included in the log fields for correlation.

func (*TflogAdapter) Warn added in v0.10.0

func (t *TflogAdapter) Warn(ctx context.Context, msg string, keysAndValues ...any)

Warn logs a warning message with structured key-value pairs to tflog.SubsystemWarn.

Warnings indicate potentially harmful situations that don't prevent operation but should be addressed.

Context is provided by go-netconf's Logger interface, ensuring automatic propagation of trace IDs, request IDs, and deadlines.

Logs are written to the "netconf" subsystem for proper organization and filtering. The subsystem is automatically created if it doesn't exist.

The device identifier is automatically included in the log fields for correlation.

Jump to

Keyboard shortcuts

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