cfgmgmt

package
v1.0.7 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

README

internal/cfgmgmt

Configuration management: scope tree (folders, devices, parameter / CLI / service objects), typed variables, service types, and CLI render/preview.

How to design a service type (catalog type, CLI objects under _catalog/cli, parameter objects, instantiate in the tree): docs/cfgmgmt-service-design.md.

Tree architecture (kinds, wrap policy, migration): docs/cfgmgmt-tree-objects.md.

Documentation

Index

Constants

View Source
const (
	// DefaultELINEMTU/DefaultELINEControlWord are hardcoded for v1 rather
	// than service fields — matches the EOS fixture.
	DefaultELINEMTU         = 9100
	DefaultELINEControlWord = true

	FieldVLAN                 = "vlan"
	FieldSubinterfaceNetboxID = "subinterface_netbox_id"
	FieldTerminationNetboxID  = "termination_netbox_id"
)

Variables

This section is empty.

Functions

func AssertPacksHaveCLITwins added in v1.0.5

func AssertPacksHaveCLITwins(db *gorm.DB) error

AssertPacksHaveCLITwins errors if platform_packs still exists and any row has no kind=cli translator for the same (service_type_id, platform). MigrateDatabase must not DROP those tables until every pack was copied.

func AssertTemplatesHaveCLITwins added in v1.0.5

func AssertTemplatesHaveCLITwins(db *gorm.DB) error

AssertTemplatesHaveCLITwins errors if config_templates still exists and any row has no baseline kind=cli twin (same name, parent = scope_id or global if null, platform-compatible). Translation CLI does not count.

func AttachDevice

func AttachDevice(db *gorm.DB, parentID, deviceID uint) (*models.ConfigScope, error)

AttachDevice creates or updates a device-kind node under parentID and ensures a child interface node exists for each inventory interface.

func AttachService added in v1.0.5

func AttachService(db *gorm.DB, parentID, serviceRowID uint) (*models.ConfigScope, error)

AttachService places an existing typed CN/CI row in the tree and projects endpoint children. Lime rows are allowed. VL/VI/LF/LI are rejected.

func CLIObjectChecksum added in v1.0.5

func CLIObjectChecksum(platform, payloadKind string, ctx *models.CLIContext, feats []models.ConfigCLIFeature) string

CLIObjectChecksum is the canonical seed hash for a translation CLI object:

sha256(platform + "\n" + payload_kind + "\n" + canonicalJSON(context) + "\n" +
  for each feature in sort_order:
    name + "\nremove_at_root=" + bool + "\nadd\n" + AddCommands +
    "\nupdate\n" + UpdateCommands + "\nremove\n" + RemoveCommands + "\n")

Empty UpdateCommands still contributes "\nupdate\n". canonicalJSON uses deterministic key order for pattern/enter/exit/captures; nil context is "null" so setting enter counts as an operator edit.

func CanonicalServiceParentID added in v1.0.5

func CanonicalServiceParentID(db *gorm.DB, from *models.ConfigScope) (uint, error)

CanonicalServiceParentID is the parent for a new canonical node started from from. Interface/device create uses the device's folder/site/location parent when that parent is organizational, otherwise _services.

func CompileContextPattern added in v1.0.5

func CompileContextPattern(pattern string) (*regexp.Regexp, error)

CompileContextPattern turns the GUI placeholder language into an anchored RE2 regex. Tokens are whitespace-separated; `<ident>` becomes a named `\S+` capture. Empty and `global` mean the configure root (no wrap).

func CreateCLIFeature added in v1.0.5

func CreateCLIFeature(db *gorm.DB, scopeID uint, feat *models.ConfigCLIFeature) (*models.ConfigCLIFeature, error)

func CreateScope

func CreateScope(db *gorm.DB, s *models.ConfigScope) (*models.ConfigScope, error)

func CreateServiceFromTree added in v1.0.5

func CreateServiceFromTree(db *gorm.DB, parentID uint, dto *models.ServiceDTO) (*models.ConfigScope, error)

CreateServiceFromTree creates a CN/CI inventory row and a canonical kind=service scope with zero endpoints.

func CreateServiceRecord added in v1.0.5

func CreateServiceRecord(tx *gorm.DB, dto *models.ServiceDTO) (*models.Service, error)

CreateServiceRecord inserts a services row using the same numbering and field-copy rules as ApiServiceCreate. The caller must validate category and service type first and run this inside a transaction when allocating the next <type><5-digit> id.

func DeleteAssignment

func DeleteAssignment(db *gorm.DB, id uint) error

func DeleteCLIFeature added in v1.0.5

func DeleteCLIFeature(db *gorm.DB, id uint) error

func DeleteScope

func DeleteScope(db *gorm.DB, id uint) error

func DescendantScopes

func DescendantScopes(db *gorm.DB, rootID uint) ([]models.ConfigScope, error)

func DetachDevice added in v1.0.5

func DetachDevice(db *gorm.DB, id uint) error

DetachDevice removes a device scope and its config descendants. kind=service children are reparented. The DCIM device row is never deleted.

func DetachServiceByRowID added in v1.0.5

func DetachServiceByRowID(db *gorm.DB, serviceRowID uint) error

DetachServiceByRowID drops the canonical node and its config descendants. Inventory rows are left in place. No-op if the service is not in the tree.

func DropPackAndTemplateTables added in v1.0.5

func DropPackAndTemplateTables(db *gorm.DB) error

DropPackAndTemplateTables removes leftover platform_packs and config_templates. Call AssertPacksHaveCLITwins and AssertTemplatesHaveCLITwins first.

func EncodeEndpointFields added in v1.0.3

func EncodeEndpointFields(vlan int, subNetboxID, termNetboxID uint) json.RawMessage

EncodeEndpointFields stores vlan plus optional NetBox ids on an endpoint.

func EndpointIdentity added in v1.0.5

func EndpointIdentity(ep models.ServiceEndpoint) string

EndpointIdentity is the stable key for projection and virtual service_ref nodes. Replace-all allocates new service_endpoints.id values, so the table primary key cannot be used.

func EndpointRolesForCount added in v1.0.3

func EndpointRolesForCount(st *models.ServiceType, n int) []string

EndpointRolesForCount expands st's roles into n role names for reverse- import: bounded roles are filled in order, then any unlimited role (Max==0) absorbs the rest. ELINE a/b → ["a","b"]; ELAN endpoint → n copies.

func FieldUint added in v1.0.3

func FieldUint(m map[string]any, key string) uint

func GetCLIFeature added in v1.0.5

func GetCLIFeature(db *gorm.DB, id uint) (*models.ConfigCLIFeature, error)

func GetScope

func GetScope(db *gorm.DB, id uint) (*models.ConfigScope, error)

func InventoryMaps added in v1.0.3

func InventoryMaps(types []models.ServiceType) map[string]string

InventoryMaps returns sync_source → netbox_type for types that have both set. When two types share a source, the first in name order wins.

func IsPhysicalInterfaceType added in v1.0.3

func IsPhysicalInterfaceType(t string) bool

func IsSROS

func IsSROS(platform string) bool

func ListAssignments

func ListAssignments(db *gorm.DB, scopeID uint) ([]models.ConfigAssignment, error)

func ListCLIFeatures added in v1.0.5

func ListCLIFeatures(db *gorm.DB, scopeID uint) ([]models.ConfigCLIFeature, error)

func ListEndpoints

func ListEndpoints(db *gorm.DB, serviceID uint) ([]models.ServiceEndpoint, error)

func ListScopes

func ListScopes(db *gorm.DB) ([]models.ConfigScope, error)

func ListServiceTypes added in v1.0.3

func ListServiceTypes(db *gorm.DB) ([]models.ServiceType, error)

func LookupCLIObject added in v1.0.5

func LookupCLIObject(db *gorm.DB, typeName, platform string) (*models.ConfigScope, error)

LookupCLIObject returns the kind=cli translator for a service type name and platform. sros-md falls back to sros when no dedicated object exists.

func LookupServiceType

func LookupServiceType(db *gorm.DB, name string) (*models.ServiceType, error)

LookupServiceType finds a service type by name.

func MissingCLIObjectMessage added in v1.0.5

func MissingCLIObjectMessage(typeName, platform string) string

MissingCLIObjectMessage is the preview/push error when no translation CLI object exists for type+platform.

func MoveScope added in v1.0.5

func MoveScope(db *gorm.DB, id, parentID uint, sortOrder *int) (*models.ConfigScope, error)

MoveScope reparents id under parentID. sortOrder nil means last sibling.

func NetboxIDsFromFields added in v1.0.3

func NetboxIDsFromFields(raw json.RawMessage) (sub, term uint)

func NormalizePlatform

func NormalizePlatform(p string) string

func RedactAssignmentSecrets

func RedactAssignmentSecrets(db *gorm.DB, rows []models.ConfigAssignment) error

func RedactVariableSecrets

func RedactVariableSecrets(def *models.ConfigVariableDef)

func Render

func Render(db *gorm.DB, body, define string, data any) ([]string, error)

Render executes Go text/template body (or a named define) against data. FuncMap is limited: include (named ConfigMacro), join. No file/HTTP/shell.

func RenderCLIFeature added in v1.0.5

func RenderCLIFeature(db *gorm.DB, ctx *models.CLIContext, feat *models.ConfigCLIFeature, data any) ([]string, error)

RenderCLIFeature executes remove then add for one feature and applies the opt-in wrap policy. UpdateCommands is unused in v1.

func RenderCLIObject added in v1.0.5

func RenderCLIObject(db *gorm.DB, obj *models.ConfigScope, data any) ([]string, error)

func RenderCLIObjectRemove added in v1.0.5

func RenderCLIObjectRemove(db *gorm.DB, obj *models.ConfigScope, data any) ([]string, error)

RenderCLIObjectRemove renders only feature remove blobs (wrapped per policy).

func RenderCLITranslation added in v1.0.5

func RenderCLITranslation(db *gorm.DB, obj *models.ConfigScope, data any, includeRemove bool) ([]string, error)

RenderCLITranslation renders a service-translation CLI object. includeRemove is true for the first endpoint on a device (cleanup once) and false after.

func ReplaceEndpoints

func ReplaceEndpoints(db *gorm.DB, serviceID uint, eps []models.ServiceEndpoint) error

func RequireCLIObject added in v1.0.5

func RequireCLIObject(obj *models.ConfigScope) error

RequireCLIObject gates push on the payload_kind column (not JSON).

func Resolve

func Resolve(db *gorm.DB, interfaceID uint, varName string) (any, *models.ConfigScope, error)

Resolve walks from the interface's start scope to root and returns the first assignment of varName on an enabled parameter child. Falls back to the def's DefaultValue. Assignments on the walked organizational scope itself are ignored.

func ResolveMap

func ResolveMap(db *gorm.DB, interfaceID uint) (map[string]any, error)

ResolveMap is ResolveAll as name → value, skipping vars that failed.

func ResolveMapForDevice

func ResolveMapForDevice(db *gorm.DB, deviceID uint) (map[string]any, error)

ResolveMapForDevice resolves variables from the device scope (not a particular interface), filtered by the device's platform.

func RootScope

func RootScope(db *gorm.DB) (*models.ConfigScope, error)

RootScope returns the global root folder.

func SDPIDFromNeighbor

func SDPIDFromNeighbor(neighborIP string) (int, error)

SDPIDFromNeighbor is the SR OS shared SDP ID (last IPv4 octet).

func SecretDefaultUnchanged

func SecretDefaultUnchanged(raw []byte) bool

SecretDefaultUnchanged reports that a write should keep the stored secret default: the client omitted it, sent JSON null, or echoed the redaction placeholder.

func Seed

func Seed(db *gorm.DB) error

Seed creates the global root scope, reserved _catalog/_services folders, built-in service types, and ELINE translation CLI objects when they are missing. Leftover platform_packs / config_templates rows (from before those tables were dropped) are copied onto CLI objects first so operator edits survive; checksum-matching ELINE rows are then refreshed from the embed files under internal/drivers/templates. Assignments on non-parameter scopes are copied onto a reserved parameters child and the originals are deleted. Typed CN/CI services without a tree node are placed under _services.

func ServiceTypeExists

func ServiceTypeExists(db *gorm.DB, name string) (bool, error)

func StartScope

func StartScope(db *gorm.DB, interfaceID uint) (*models.ConfigScope, error)

StartScope for an interface: interface node, else device node, else global.

func TypeCheck

func TypeCheck(def *models.ConfigVariableDef, v any) (any, error)

TypeCheck coerces and validates v against def. Type, constraints, and (for vlan) the 1–4094 range. List entries and map keys/values are checked recursively when constraints.items / keys / values are set.

func TypeCheckRaw

func TypeCheckRaw(def *models.ConfigVariableDef, raw json.RawMessage) (any, error)

func TypeForNetboxKind added in v1.0.3

func TypeForNetboxKind(types []models.ServiceType, netboxType string) *models.ServiceType

TypeForNetboxKind returns the first service type whose NetboxType matches.

func UpdateCLIFeature added in v1.0.5

func UpdateCLIFeature(db *gorm.DB, id uint, dto *models.ConfigCLIFeatureDTO) (*models.ConfigCLIFeature, error)

func UpdateScope

func UpdateScope(db *gorm.DB, id uint, patch *models.ConfigScopeDTO) (*models.ConfigScope, error)

func UpsertAssignment

func UpsertAssignment(db *gorm.DB, defID, scopeID uint, value []byte) (*models.ConfigAssignment, error)

func VLANFromFields added in v1.0.3

func VLANFromFields(raw json.RawMessage) int

func ValidPayloadKind added in v1.0.5

func ValidPayloadKind(k string) bool

func ValidScopeKind

func ValidScopeKind(k string) bool

func ValidVarType

func ValidVarType(t string) bool

func ValidateConstraints

func ValidateConstraints(typ string, raw json.RawMessage) error

ValidateConstraints checks nested items/keys/values type names and that list/map-only fields are not set on scalar types.

func ValidateELINEShape added in v1.0.3

func ValidateELINEShape(db *gorm.DB, eps []models.ServiceEndpoint) error

ValidateELINEShape enforces physical ports and that A/B are not the same device+interface. Call after ValidateEndpoints.

func ValidateEndpoints

func ValidateEndpoints(db *gorm.DB, st *models.ServiceType, eps []models.ServiceEndpoint) error

ValidateEndpoints checks endpoints against the service type's EndpointRoles and that each device/interface pair exists in inventory.

func ValidateVariableDef

func ValidateVariableDef(def *models.ConfigVariableDef) error

ValidateVariableDef checks type, constraints, and default_value.

func WalkParents

func WalkParents(db *gorm.DB, start *models.ConfigScope) ([]models.ConfigScope, error)

WalkParents returns start then each ancestor up to root. A cycle is an error.

func WouldCycle

func WouldCycle(db *gorm.DB, nodeID uint, newParentID uint) (bool, error)

WouldCycle reports whether setting node's parent to newParentID would loop.

Types

type BaselineRenderData added in v1.0.5

type BaselineRenderData struct {
	Name       string
	Device     DCIMDevice
	Interface  DCIMInterface
	LocalIface string
	Vars       map[string]any
}

BaselineRenderData is the template context for baseline (non-service) CLI objects. Interface/LocalIface are set only when the object's parent is an interface; they are zero at device/folder level.

type DCIMDevice

type DCIMDevice struct {
	ID           uint   `json:"id"`
	Name         string `json:"name"`
	Platform     string `json:"platform"`
	Site         string `json:"site"`
	Role         string `json:"role"`
	ModelName    string `json:"model_name"`
	Manufacturer string `json:"manufacturer"`
	Status       string `json:"status"`
	PrimaryIPv4  string `json:"primary_ipv4"`
	PrimaryIPv6  string `json:"primary_ipv6"`
}

DCIMDevice is the read-only inventory fragment templates may use.

func DCIMFromDevice

func DCIMFromDevice(d *models.Device) DCIMDevice

type DCIMInterface

type DCIMInterface struct {
	ID           uint     `json:"id"`
	Name         string   `json:"name"`
	Description  string   `json:"description"`
	Enabled      bool     `json:"enabled"`
	Type         string   `json:"type"`
	UntaggedVLAN int      `json:"untagged_vlan"`
	TaggedVLANs  []int    `json:"tagged_vlans"`
	Addresses    []string `json:"addresses"`
}

DCIMInterface is the read-only interface fragment templates may use.

func DCIMFromInterface

func DCIMFromInterface(iface *models.Interface) DCIMInterface

type DeviceRender

type DeviceRender struct {
	DeviceID uint             `json:"device_id"`
	Name     string           `json:"name"`
	Platform string           `json:"platform"`
	Sources  []RenderedSource `json:"sources"`
}

func RenderDevice

func RenderDevice(db *gorm.DB, deviceID uint) (*DeviceRender, error)

RenderDevice returns baseline CLI + terminating services for a device. It does not talk to the device.

type ELINERemote

type ELINERemote struct {
	NeighborIP   string
	PseudowireID int
	MTU          int
	ControlWord  bool
	DeviceName   string
	RemoteIface  string
	RemoteVLAN   int
}

type ELINEStale

type ELINEStale struct {
	Iface string
	VLAN  int
}

type GenericRenderData

type GenericRenderData struct {
	Name               string
	Description        string
	ServiceNumericID   int
	Fields             map[string]any
	Endpoint           map[string]any
	Vars               map[string]any
	Device             DCIMDevice
	Interface          DCIMInterface
	LocalIface         string
	LocalVLAN          int
	Role               string
	PeerLocalIface     string
	PeerLocalVLAN      int
	Remote             *ELINERemote
	StaleSubinterfaces []ELINEStale
	SDPID              int
}

GenericRenderData is the template context for service-translation CLI objects. ELINE uses the extra Peer/Remote/SDPID/Stale fields; other types leave them zero.

func GenericData

func GenericData(db *gorm.DB, svc *models.Service, ep *models.ServiceEndpoint, device *models.Device, iface *models.Interface) (*GenericRenderData, error)

type MatrixRow

type MatrixRow struct {
	InterfaceID   uint   `json:"interface_id"`
	InterfaceName string `json:"interface_name"`
	DeviceID      uint   `json:"device_id"`
	DeviceName    string `json:"device_name"`
	ScopeID       *uint  `json:"scope_id,omitempty"`
	Value         any    `json:"value"`
	SourceID      *uint  `json:"source_id,omitempty"`
	SourceName    string `json:"source_name,omitempty"`
	FromDefault   bool   `json:"from_default"`
	Error         string `json:"error,omitempty"`
}

MatrixRows are interfaces under a scope subtree, with one variable resolved.

func Matrix

func Matrix(db *gorm.DB, scopeID uint, varName string) ([]MatrixRow, error)

type RenderedSource

type RenderedSource struct {
	Source      string   `json:"source"`
	Kind        string   `json:"kind"` // cli | service
	Platform    string   `json:"platform"`
	PayloadKind string   `json:"payload_kind"`
	Commands    []string `json:"commands"`
	Error       string   `json:"error,omitempty"`
}

RenderedSource is one CLI object or service's rendered CLI (or other payload).

func RenderService

func RenderService(db *gorm.DB, serviceID uint) ([]RenderedSource, error)

RenderService renders every saved endpoint of a service (preview, no device I/O).

func RenderServiceEndpoints added in v1.0.5

func RenderServiceEndpoints(db *gorm.DB, serviceID uint, eps []models.ServiceEndpoint, fields json.RawMessage) ([]RenderedSource, error)

RenderServiceEndpoints renders the given endpoints against a saved service (preview, no device I/O). Incomplete rows (no device or interface) are skipped. fields, when non-empty, overlays Service.Fields for the render.

type ResolvedVar

type ResolvedVar struct {
	Name        string
	Value       any
	Source      *models.ConfigScope
	FromDefault bool
	Secret      bool
	Required    bool
	Type        string
	Err         error
}

ResolvedVar is one variable's winning value and the scope it came from.

func RedactSecrets

func RedactSecrets(vars []ResolvedVar) []ResolvedVar

func ResolveAll

func ResolveAll(db *gorm.DB, interfaceID uint) ([]ResolvedVar, error)

ResolveAll returns every variable def resolved at interfaceID. Required vars with no value are included with Err set rather than failing the batch.

type ScopeTreeData

type ScopeTreeData struct {
	ID            uint                      `json:"id"`
	Kind          string                    `json:"kind"`
	ParentID      *uint                     `json:"parent_id,omitempty"`
	SiteID        *uint                     `json:"site_id,omitempty"`
	DeviceID      *uint                     `json:"device_id,omitempty"`
	InterfaceID   *uint                     `json:"interface_id,omitempty"`
	ServiceID     *uint                     `json:"service_id,omitempty"`
	ServiceTypeID *uint                     `json:"service_type_id,omitempty"`
	Platform      string                    `json:"platform,omitempty"`
	PayloadKind   string                    `json:"payload_kind,omitempty"`
	Enabled       bool                      `json:"enabled"`
	SortOrder     int                       `json:"sort_order"`
	Payload       models.ConfigScopePayload `json:"payload"`
	CanonicalID   uint                      `json:"canonical_id,omitempty"`
	ServiceRowID  uint                      `json:"service_row_id,omitempty"`
	ServiceLabel  string                    `json:"service_label,omitempty"`
	Role          string                    `json:"role,omitempty"`
	Disc          string                    `json:"disc,omitempty"`
	Identity      string                    `json:"identity,omitempty"`
}

type ScopeTreeNode

type ScopeTreeNode struct {
	Key      string          `json:"key"`
	Title    string          `json:"title"`
	Type     string          `json:"type"`
	Data     ScopeTreeData   `json:"data"`
	Children []ScopeTreeNode `json:"children,omitempty"`
}

ScopeTreeNode is one nested scope for GET /api/config/scopes/tree.

func ScopeTree

func ScopeTree(db *gorm.DB) ([]ScopeTreeNode, error)

type StatusError

type StatusError struct {
	Status  int
	Message string
}

StatusError is an application-level failure with an HTTP-ish status so handlers don't have to string-match error text.

func AsStatusError

func AsStatusError(err error) *StatusError

func (*StatusError) Error

func (e *StatusError) Error() string

Jump to

Keyboard shortcuts

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