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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ endif
# scaffolded by default. However, you might want to replace it to use other
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker
# TARGET_PLATFORM defines the target platform for the manager image building.
TARGET_PLATFORM ?= linux/amd64

# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
Expand Down Expand Up @@ -45,7 +47,7 @@ GO_PACKAGE_NAME_GOLANGCI_LINT := golangci-lint
install-$(GO_PACKAGE_NAME_GOLANGCI_LINT):
@if [ ! -x "$(GOBIN)/$(GO_PACKAGE_NAME_GOLANGCI_LINT)" ]; then \
echo "Installing $(GO_PACKAGE_NAME_GOLANGCI_LINT)..." ; \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.60.3 ; \
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest ; \
else \
echo "$(GO_PACKAGE_NAME_GOLANGCI_LINT) is installed" ; \
fi
Expand Down Expand Up @@ -101,7 +103,7 @@ vet: ## Run go vet against code.

.PHONY: lint
lint: install-$(GO_PACKAGE_NAME_GOLANGCI_LINT) ## Run golangci-lint against code.
golangci-lint run --config tools/.golangci.yaml ./...
$(GOBIN)/golangci-lint run --config tools/.golangci.yaml ./...

.PHONY: vulncheck
vulncheck: install-$(GO_PACKAGE_NAME_GOVULNCHECK) ## Run govulncheck against code.
Expand Down Expand Up @@ -129,7 +131,7 @@ run: manifests generate fmt vet ## Run a controller from your host.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: ## Build docker image with the manager.
$(CONTAINER_TOOL) build -t ${IMG} .
$(CONTAINER_TOOL) build --platform $(TARGET_PLATFORM) -t ${IMG} .

.PHONY: docker-build-local
docker-build-local: ## Build docker image with the manager.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

**Disclaimer:** This project is currently under development and may change rapidly, including breaking changes. Use with caution in production environments.

NetBox Operator extends the Kubernetes API by allowing users to manage NetBox resources – such as IP addresses and prefixes – directly through Kubernetes. This integration brings Kubernetes-native features like reconciliation, ensuring that network configurations are maintained automatically, thereby improving both efficiency and reliability.
NetBox Operator extends the Kubernetes API by allowing users to manage NetBox resources – such as IP addresses, prefixes, and VLANs – directly through Kubernetes. This integration brings Kubernetes-native features like reconciliation, ensuring that network configurations are maintained automatically, thereby improving both efficiency and reliability.

## The Claim Model
The NetBox Operator implements a "Claim Model" which is also used in the Kubernetes PersistentVolumeClaims (PVCs).
In this case, instead of disk storage, NetBox Operator dynamically allocates network resources (Prefixes and IP Addresses) based on claims submitted via custom resources.

### Purpose
This model ensures a declarative management of IP addressing and subnet allocation, with full NetBox integration.
The users will create claims (PrefixClaims & IPAddressClaims), and the NetBox Operator will resolve them into actual Prefixes and IPAddresses within a designated parent prefix.
The users will create claims (PrefixClaims, IPAddressClaims & VLANClaims), and the NetBox Operator will resolve them into actual Prefixes, IPAddresses and VLANs within a designated parent prefix or site.

![Figure 1: NetBox Operator High-Level Architecture](docs/netbox-operator-high-level-architecture.drawio.svg)

Expand Down Expand Up @@ -53,7 +53,7 @@ To optionally access the NetBox UI:

## Testing NetBox Operator using samples

In the folder `config/samples/` you can find example manifests to create IpAddress, IpAddressClaim, Prefix, and PrefixClaim resources. Apply them to the cluster with `kubectl apply -f <file-name>` and use your favorite Kubernetes tools to display.
In the folder `config/samples/` you can find example manifests to create IpAddress, IpAddressClaim, Prefix, PrefixClaim, and VLANClaim resources. Apply them to the cluster with `kubectl apply -f <file-name>` and use your favorite Kubernetes tools to display.

Example of assigning a Prefix using PrefixClaim:

Expand Down
124 changes: 124 additions & 0 deletions api/v1/vlan_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Copyright 2024 Swisscom (Schweiz) AG.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// VlanSpec defines the desired state of Vlan
type VlanSpec struct {
// The unique VLAN ID (VID)
//+kubebuilder:validation:Required
//+kubebuilder:validation:Minimum=1
//+kubebuilder:validation:Maximum=4094
//+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vlanId' is immutable"
VlanId int `json:"vlanId"`

// The desired name for the VLAN in NetBox
//+kubebuilder:validation:Required
Name string `json:"name"`

// The NetBox Site where this VLAN should exist
//+kubebuilder:validation:Required
Site string `json:"site"`

// The NetBox VLANGroup where this VLAN should be organized
//+optional
VlanGroup string `json:"vlanGroup,omitempty"`

// Description that should be added to the resource in NetBox
//+optional
Description string `json:"description,omitempty"`

// Comment that should be added to the resource in NetBox
//+optional
Comments string `json:"comments,omitempty"`

// The NetBox Custom Fields that should be added to the resource in NetBox
//+optional
CustomFields map[string]string `json:"customFields,omitempty"`

// Defines whether the Resource should be preserved in NetBox when the
// Kubernetes Resource is deleted.
//+optional
PreserveInNetbox bool `json:"preserveInNetbox,omitempty"`
}

// VlanStatus defines the observed state of Vlan
type VlanStatus struct {
// The NetBox internal database ID of the created/managed VLAN
//+optional
VlanId int64 `json:"id,omitempty"`

// The URL to the VLAN object in the NetBox UI
//+optional
VlanUrl string `json:"url,omitempty"`

// Conditions represent the latest available observations of an object's state
//+optional
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:storageversion
//+kubebuilder:printcolumn:name="VLAN ID",type=integer,JSONPath=`.spec.vlanId`
//+kubebuilder:printcolumn:name="NetBox ID",type=integer,JSONPath=`.status.id`
//+kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
//+kubebuilder:resource:shortName=vl

// Vlan is the Schema for the vlans API
type Vlan struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec VlanSpec `json:"spec,omitempty"`
Status VlanStatus `json:"status,omitempty"`
}

func (v *Vlan) Conditions() *[]metav1.Condition {
return &v.Status.Conditions
}

//+kubebuilder:object:root=true

// VlanList contains a list of Vlan
type VlanList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Vlan `json:"items"`
}

func init() {
register(&Vlan{}, &VlanList{})
}

var ConditionVlanReadyTrue = metav1.Condition{
Type: "Ready",
Status: "True",
Reason: "VlanSynchronized",
Message: "VLAN was synchronized with NetBox",
}

var ConditionVlanReadyFalse = metav1.Condition{
Type: "Ready",
Status: "False",
Reason: "VlanSyncFailed",
Message: "Failed to synchronize VLAN with NetBox",
}
137 changes: 137 additions & 0 deletions api/v1/vlanclaim_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Copyright 2024 Swisscom (Schweiz) AG.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// VLANClaimSpec defines the desired state of VLANClaim
type VLANClaimSpec struct {
// The unique VLAN ID (VID) for the NetBox VLAN. If not provided, the operator will claim an available VID.
//+optional
//+kubebuilder:validation:Minimum=1
//+kubebuilder:validation:Maximum=4094
VlanId int `json:"vlanId,omitempty"`

// The desired name for the VLAN in NetBox. If not provided, the operator will generate one.
//+optional
Name string `json:"name,omitempty"`

// The NetBox Site where this VLAN should exist
//+kubebuilder:validation:Required
Site string `json:"site"`

// The NetBox VLANGroup where this VLAN should be organized. Required if vlanId is not provided.
//+optional
VlanGroup string `json:"vlanGroup,omitempty"`

// Description that should be added to the resource in NetBox
//+optional
Description string `json:"description,omitempty"`

// Comment that should be added to the resource in NetBox
//+optional
Comments string `json:"comments,omitempty"`

// The NetBox Custom Fields that should be added to the resource in NetBox
//+optional
CustomFields map[string]string `json:"customFields,omitempty"`

// Defines whether the Resource should be preserved in NetBox when the
// Kubernetes Resource is deleted.
//+optional
PreserveInNetbox bool `json:"preserveInNetbox,omitempty"`
}

// VLANClaimStatus defines the observed state of VLANClaim
type VLANClaimStatus struct {
// The assigned VLAN ID (VID)
//+optional
VlanId int `json:"vlanId,omitempty"`

// The name of the Vlan CR created by the VLANClaim Controller
//+optional
VlanName string `json:"vlanName,omitempty"`

// Conditions represent the latest available observations of an object's state
//+optional
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:storageversion
//+kubebuilder:printcolumn:name="VID",type=integer,JSONPath=`.status.vlanId`
//+kubebuilder:printcolumn:name="Vlan Name",type=string,JSONPath=`.status.vlanName`
//+kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
//+kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
//+kubebuilder:resource:shortName=vlc

// VLANClaim is the Schema for the vlanclaims API
type VLANClaim struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec VLANClaimSpec `json:"spec,omitempty"`
Status VLANClaimStatus `json:"status,omitempty"`
}

func (v *VLANClaim) Conditions() *[]metav1.Condition {
return &v.Status.Conditions
}

//+kubebuilder:object:root=true

// VLANClaimList contains a list of VLANClaim
type VLANClaimList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []VLANClaim `json:"items"`
}

func init() {
register(&VLANClaim{}, &VLANClaimList{})
}

var ConditionVlanClaimReadyTrue = metav1.Condition{
Type: "Ready",
Status: "True",
Reason: "VlanResourceReady",
Message: "VLAN Resource is ready",
}

var ConditionVlanClaimReadyFalse = metav1.Condition{
Type: "Ready",
Status: "False",
Reason: "VlanResourceNotReady",
Message: "VLAN Resource is not ready",
}

var ConditionVlanAssignedTrue = metav1.Condition{
Type: "VlanAssigned",
Status: "True",
Reason: "VlanCRCreated",
Message: "VLAN VID assigned and Vlan CR created",
}

var ConditionVlanAssignedFalse = metav1.Condition{
Type: "VlanAssigned",
Status: "False",
Reason: "VlanCRNotCreated",
Message: "Failed to assign VID or create Vlan CR",
}
Loading
Loading