From 86011f44cc88910fd9b8e2a5a17d349ce32880cc Mon Sep 17 00:00:00 2001 From: Rikard Danielsson Date: Thu, 12 Feb 2026 14:36:04 +0100 Subject: [PATCH] Add VRF field support for all IPAM resource types --- api/v1/ipaddress_types.go | 6 + api/v1/ipaddressclaim_types.go | 6 + api/v1/iprange_types.go | 6 + api/v1/iprangeclaim_types.go | 6 + api/v1/prefix_types.go | 6 + api/v1/prefixclaim_types.go | 6 + .../crd/bases/netbox.dev_ipaddressclaims.yaml | 9 + config/crd/bases/netbox.dev_ipaddresses.yaml | 9 + .../crd/bases/netbox.dev_iprangeclaims.yaml | 9 + config/crd/bases/netbox.dev_ipranges.yaml | 9 + config/crd/bases/netbox.dev_prefixclaims.yaml | 9 + config/crd/bases/netbox.dev_prefixes.yaml | 9 + config/samples/kustomization.yaml | 6 + config/samples/netbox_v1_ipaddress_vrf.yaml | 15 ++ .../samples/netbox_v1_ipaddressclaim_vrf.yaml | 15 ++ config/samples/netbox_v1_iprange_vrf.yaml | 16 ++ .../samples/netbox_v1_iprangeclaim_vrf.yaml | 16 ++ config/samples/netbox_v1_prefix_vrf.yaml | 16 ++ config/samples/netbox_v1_prefixclaim_vrf.yaml | 16 ++ gen/mock_interfaces/netbox_mocks.go | 20 ++ internal/controller/ipaddress_controller.go | 1 + .../controller/ipaddressclaim_controller.go | 1 + internal/controller/ipaddressclaim_helpers.go | 5 +- internal/controller/iprange_controller.go | 1 + .../controller/iprangeclaim_controller.go | 1 + internal/controller/iprangeclaim_helpers.go | 5 +- internal/controller/prefix_controller.go | 1 + internal/controller/prefixclaim_controller.go | 1 + internal/controller/prefixclaim_helpers.go | 5 +- kind/load-local-data-job/main.py | 85 ++++++++ pkg/netbox/api/ip_address.go | 8 + pkg/netbox/api/ip_address_claim.go | 8 + pkg/netbox/api/ip_address_claim_test.go | 61 ++++++ pkg/netbox/api/ip_range.go | 8 + pkg/netbox/api/ip_range_claim.go | 8 + pkg/netbox/api/ip_range_claim_test.go | 62 ++++++ pkg/netbox/api/prefix.go | 19 ++ pkg/netbox/api/prefix_claim.go | 20 +- pkg/netbox/api/prefix_claim_test.go | 205 ++++++++++++++++++ pkg/netbox/api/vrf.go | 40 ++++ pkg/netbox/api/vrf_test.go | 101 +++++++++ pkg/netbox/interfaces/netbox.go | 2 + pkg/netbox/models/ipam.go | 6 + .../chainsaw-test.yaml | 111 ++++++++++ .../netbox_v1_ipaddressclaim-update.yaml | 12 + .../netbox_v1_ipaddressclaim.yaml | 12 + .../chainsaw-test.yaml | 45 ++++ .../netbox_v1_iprangeclaim.yaml | 13 ++ .../chainsaw-test.yaml | 121 +++++++++++ .../netbox_v1_iprangeclaim-update.yaml | 13 ++ .../netbox_v1_iprangeclaim.yaml | 13 ++ .../chainsaw-test.yaml | 78 +++++++ .../netbox_v1_prefixclaim.yaml | 19 ++ .../chainsaw-test.yaml | 85 ++++++++ .../netbox_v1_prefixclaim-update.yaml | 13 ++ .../netbox_v1_prefixclaim.yaml | 13 ++ 56 files changed, 1408 insertions(+), 4 deletions(-) create mode 100644 config/samples/netbox_v1_ipaddress_vrf.yaml create mode 100644 config/samples/netbox_v1_ipaddressclaim_vrf.yaml create mode 100644 config/samples/netbox_v1_iprange_vrf.yaml create mode 100644 config/samples/netbox_v1_iprangeclaim_vrf.yaml create mode 100644 config/samples/netbox_v1_prefix_vrf.yaml create mode 100644 config/samples/netbox_v1_prefixclaim_vrf.yaml create mode 100644 pkg/netbox/api/vrf.go create mode 100644 pkg/netbox/api/vrf_test.go create mode 100644 tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/chainsaw-test.yaml create mode 100644 tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim-update.yaml create mode 100644 tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim.yaml create mode 100644 tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/chainsaw-test.yaml create mode 100644 tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/netbox_v1_iprangeclaim.yaml create mode 100644 tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/chainsaw-test.yaml create mode 100644 tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim-update.yaml create mode 100644 tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim.yaml create mode 100644 tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/chainsaw-test.yaml create mode 100644 tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/netbox_v1_prefixclaim.yaml create mode 100644 tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/chainsaw-test.yaml create mode 100644 tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim-update.yaml create mode 100644 tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim.yaml diff --git a/api/v1/ipaddress_types.go b/api/v1/ipaddress_types.go index 0c6a9b71..3cc6b763 100644 --- a/api/v1/ipaddress_types.go +++ b/api/v1/ipaddress_types.go @@ -36,6 +36,12 @@ type IpAddressSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + // Field is immutable, not required + // Example: "blue" or "red" + //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vrf' is immutable" + Vrf string `json:"vrf,omitempty"` + // The NetBox Custom Fields that should be added to the resource in NetBox. // Note that currently only Text Type is supported (GitHub #129) // More info on NetBox Custom Fields: diff --git a/api/v1/ipaddressclaim_types.go b/api/v1/ipaddressclaim_types.go index f50933b6..a89a2025 100644 --- a/api/v1/ipaddressclaim_types.go +++ b/api/v1/ipaddressclaim_types.go @@ -36,6 +36,12 @@ type IpAddressClaimSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + // Field is immutable, not required + // Example: "blue" or "red" + //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vrf' is immutable" + Vrf string `json:"vrf,omitempty"` + // The NetBox Custom Fields that should be added to the resource in NetBox. // Note that currently only Text Type is supported (GitHub #129) // More info on NetBox Custom Fields: diff --git a/api/v1/iprange_types.go b/api/v1/iprange_types.go index 2ad4e164..e95898e9 100644 --- a/api/v1/iprange_types.go +++ b/api/v1/iprange_types.go @@ -44,6 +44,12 @@ type IpRangeSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + // Field is immutable, not required + // Example: "blue" or "red" + //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vrf' is immutable" + Vrf string `json:"vrf,omitempty"` + // The NetBox Custom Fields that should be added to the resource in NetBox. // Note that currently only Text Type is supported (GitHub #129) // More info on NetBox Custom Fields: diff --git a/api/v1/iprangeclaim_types.go b/api/v1/iprangeclaim_types.go index ad3c0946..870d7ccf 100644 --- a/api/v1/iprangeclaim_types.go +++ b/api/v1/iprangeclaim_types.go @@ -47,6 +47,12 @@ type IpRangeClaimSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + // Field is immutable, not required + // Example: "blue" or "red" + //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vrf' is immutable" + Vrf string `json:"vrf,omitempty"` + // The NetBox Custom Fields that should be added to the resource in NetBox. // Note that currently only Text Type is supported (GitHub #129) // More info on NetBox Custom Fields: diff --git a/api/v1/prefix_types.go b/api/v1/prefix_types.go index baad5af4..3e4b594c 100644 --- a/api/v1/prefix_types.go +++ b/api/v1/prefix_types.go @@ -42,6 +42,12 @@ type PrefixSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + // Field is immutable, not required + // Example: "blue" or "red" + //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vrf' is immutable" + Vrf string `json:"vrf,omitempty"` + // The NetBox Custom Fields that should be added to the resource in NetBox. // Note that currently only Text Type is supported (GitHub #129) // More info on NetBox Custom Fields: diff --git a/api/v1/prefixclaim_types.go b/api/v1/prefixclaim_types.go index 4c7716d1..f790f83f 100644 --- a/api/v1/prefixclaim_types.go +++ b/api/v1/prefixclaim_types.go @@ -61,6 +61,12 @@ type PrefixClaimSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + // Field is immutable, not required + // Example: "blue" or "red" + //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'vrf' is immutable" + Vrf string `json:"vrf,omitempty"` + // Description that should be added to the resource in NetBox // Field is mutable, not required Description string `json:"description,omitempty"` diff --git a/config/crd/bases/netbox.dev_ipaddressclaims.yaml b/config/crd/bases/netbox.dev_ipaddressclaims.yaml index 225db11e..f7695ff6 100644 --- a/config/crd/bases/netbox.dev_ipaddressclaims.yaml +++ b/config/crd/bases/netbox.dev_ipaddressclaims.yaml @@ -114,6 +114,15 @@ spec: x-kubernetes-validations: - message: Field 'tenant' is immutable rule: self == oldSelf + vrf: + description: |- + The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + Field is immutable, not required + Example: "blue" or "red" + type: string + x-kubernetes-validations: + - message: Field 'vrf' is immutable + rule: self == oldSelf required: - parentPrefix type: object diff --git a/config/crd/bases/netbox.dev_ipaddresses.yaml b/config/crd/bases/netbox.dev_ipaddresses.yaml index 80e60752..270cab8e 100644 --- a/config/crd/bases/netbox.dev_ipaddresses.yaml +++ b/config/crd/bases/netbox.dev_ipaddresses.yaml @@ -113,6 +113,15 @@ spec: x-kubernetes-validations: - message: Field 'tenant' is immutable rule: self == oldSelf + vrf: + description: |- + The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + Field is immutable, not required + Example: "blue" or "red" + type: string + x-kubernetes-validations: + - message: Field 'vrf' is immutable + rule: self == oldSelf required: - ipAddress type: object diff --git a/config/crd/bases/netbox.dev_iprangeclaims.yaml b/config/crd/bases/netbox.dev_iprangeclaims.yaml index 248fa1cb..769d02c2 100644 --- a/config/crd/bases/netbox.dev_iprangeclaims.yaml +++ b/config/crd/bases/netbox.dev_iprangeclaims.yaml @@ -127,6 +127,15 @@ spec: x-kubernetes-validations: - message: Field 'tenant' is immutable rule: self == oldSelf + vrf: + description: |- + The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + Field is immutable, not required + Example: "blue" or "red" + type: string + x-kubernetes-validations: + - message: Field 'vrf' is immutable + rule: self == oldSelf required: - parentPrefix - size diff --git a/config/crd/bases/netbox.dev_ipranges.yaml b/config/crd/bases/netbox.dev_ipranges.yaml index c716bb15..b2f12a56 100644 --- a/config/crd/bases/netbox.dev_ipranges.yaml +++ b/config/crd/bases/netbox.dev_ipranges.yaml @@ -126,6 +126,15 @@ spec: x-kubernetes-validations: - message: Field 'tenant' is immutable rule: self == oldSelf + vrf: + description: |- + The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + Field is immutable, not required + Example: "blue" or "red" + type: string + x-kubernetes-validations: + - message: Field 'vrf' is immutable + rule: self == oldSelf required: - endAddress - startAddress diff --git a/config/crd/bases/netbox.dev_prefixclaims.yaml b/config/crd/bases/netbox.dev_prefixclaims.yaml index de89e54f..462d3174 100644 --- a/config/crd/bases/netbox.dev_prefixclaims.yaml +++ b/config/crd/bases/netbox.dev_prefixclaims.yaml @@ -149,6 +149,15 @@ spec: x-kubernetes-validations: - message: Field 'tenant' is immutable rule: self == oldSelf + vrf: + description: |- + The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + Field is immutable, not required + Example: "blue" or "red" + type: string + x-kubernetes-validations: + - message: Field 'vrf' is immutable + rule: self == oldSelf required: - prefixLength type: object diff --git a/config/crd/bases/netbox.dev_prefixes.yaml b/config/crd/bases/netbox.dev_prefixes.yaml index fba59720..dff348ca 100644 --- a/config/crd/bases/netbox.dev_prefixes.yaml +++ b/config/crd/bases/netbox.dev_prefixes.yaml @@ -121,6 +121,15 @@ spec: x-kubernetes-validations: - message: Field 'tenant' is immutable rule: self == oldSelf + vrf: + description: |- + The NetBox VRF to be assigned to this resource in NetBox. Use the `name` value instead of the `id` value + Field is immutable, not required + Example: "blue" or "red" + type: string + x-kubernetes-validations: + - message: Field 'vrf' is immutable + rule: self == oldSelf required: - prefix type: object diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 674e3bc8..aa5b9dc9 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -9,4 +9,10 @@ resources: - netbox_v1_prefixclaim_parentprefixselector.yaml - netbox_v1_iprangeclaim.yaml - netbox_v1_iprange.yaml + - netbox_v1_ipaddress_vrf.yaml + - netbox_v1_ipaddressclaim_vrf.yaml + - netbox_v1_prefix_vrf.yaml + - netbox_v1_prefixclaim_vrf.yaml + - netbox_v1_iprange_vrf.yaml + - netbox_v1_iprangeclaim_vrf.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/netbox_v1_ipaddress_vrf.yaml b/config/samples/netbox_v1_ipaddress_vrf.yaml new file mode 100644 index 00000000..0e9ea0fd --- /dev/null +++ b/config/samples/netbox_v1_ipaddress_vrf.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpAddress +metadata: + labels: + app.kubernetes.io/name: netbox-operator + app.kubernetes.io/managed-by: kustomize + name: ipaddress-vrf-sample +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: true + ipAddress: "4.0.0.100/32" diff --git a/config/samples/netbox_v1_ipaddressclaim_vrf.yaml b/config/samples/netbox_v1_ipaddressclaim_vrf.yaml new file mode 100644 index 00000000..86c99356 --- /dev/null +++ b/config/samples/netbox_v1_ipaddressclaim_vrf.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpAddressClaim +metadata: + labels: + app.kubernetes.io/name: netbox-operator + app.kubernetes.io/managed-by: kustomize + name: ipaddressclaim-vrf-sample +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: true + parentPrefix: "4.0.0.0/24" diff --git a/config/samples/netbox_v1_iprange_vrf.yaml b/config/samples/netbox_v1_iprange_vrf.yaml new file mode 100644 index 00000000..78d81167 --- /dev/null +++ b/config/samples/netbox_v1_iprange_vrf.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpRange +metadata: + labels: + app.kubernetes.io/name: netbox-operator + app.kubernetes.io/managed-by: kustomize + name: iprange-vrf-sample +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: true + startAddress: "4.1.0.200/32" + endAddress: "4.1.0.202/32" diff --git a/config/samples/netbox_v1_iprangeclaim_vrf.yaml b/config/samples/netbox_v1_iprangeclaim_vrf.yaml new file mode 100644 index 00000000..b9f0993b --- /dev/null +++ b/config/samples/netbox_v1_iprangeclaim_vrf.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpRangeClaim +metadata: + labels: + app.kubernetes.io/name: netbox-operator + app.kubernetes.io/managed-by: kustomize + name: iprangeclaim-vrf-sample +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: true + parentPrefix: "4.1.0.0/24" + size: 3 diff --git a/config/samples/netbox_v1_prefix_vrf.yaml b/config/samples/netbox_v1_prefix_vrf.yaml new file mode 100644 index 00000000..7dee41e1 --- /dev/null +++ b/config/samples/netbox_v1_prefix_vrf.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: netbox.dev/v1 +kind: Prefix +metadata: + labels: + app.kubernetes.io/name: netbox-operator + app.kubernetes.io/managed-by: kustomize + name: prefix-vrf-sample +spec: + tenant: "MY_TENANT" +# vrf: "MY_VRF" + vrf: "xxx" + description: "some description" + comments: "your comments" + preserveInNetbox: true + prefix: "4.2.0.0/28" diff --git a/config/samples/netbox_v1_prefixclaim_vrf.yaml b/config/samples/netbox_v1_prefixclaim_vrf.yaml new file mode 100644 index 00000000..91775386 --- /dev/null +++ b/config/samples/netbox_v1_prefixclaim_vrf.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: netbox.dev/v1 +kind: PrefixClaim +metadata: + labels: + app.kubernetes.io/name: netbox-operator + app.kubernetes.io/managed-by: kustomize + name: prefixclaim-vrf-sample +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: true + parentPrefix: "4.2.0.0/24" + prefixLength: "/28" diff --git a/gen/mock_interfaces/netbox_mocks.go b/gen/mock_interfaces/netbox_mocks.go index d49403aa..cca4eaf5 100644 --- a/gen/mock_interfaces/netbox_mocks.go +++ b/gen/mock_interfaces/netbox_mocks.go @@ -344,6 +344,26 @@ func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesUpdate(params, authInfo any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesUpdate", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesUpdate), varargs...) } +// IpamVrfsList mocks base method. +func (m *MockIpamInterface) IpamVrfsList(params *ipam.IpamVrfsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamVrfsListOK, error) { + m.ctrl.T.Helper() + varargs := []any{params, authInfo} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "IpamVrfsList", varargs...) + ret0, _ := ret[0].(*ipam.IpamVrfsListOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IpamVrfsList indicates an expected call of IpamVrfsList. +func (mr *MockIpamInterfaceMockRecorder) IpamVrfsList(params, authInfo any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{params, authInfo}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamVrfsList", reflect.TypeOf((*MockIpamInterface)(nil).IpamVrfsList), varargs...) +} + // MockTenancyInterface is a mock of TenancyInterface interface. type MockTenancyInterface struct { ctrl *gomock.Controller diff --git a/internal/controller/ipaddress_controller.go b/internal/controller/ipaddress_controller.go index 03f032d4..f066e0b2 100644 --- a/internal/controller/ipaddress_controller.go +++ b/internal/controller/ipaddress_controller.go @@ -287,6 +287,7 @@ func generateNetboxIpAddressModelFromIpAddressSpec(spec *netboxv1.IpAddressSpec, Custom: netboxCustomFields, Description: req.NamespacedName.String() + " // " + spec.Description, Tenant: spec.Tenant, + Vrf: spec.Vrf, }, }, nil } diff --git a/internal/controller/ipaddressclaim_controller.go b/internal/controller/ipaddressclaim_controller.go index 2045efe4..2a1253e8 100644 --- a/internal/controller/ipaddressclaim_controller.go +++ b/internal/controller/ipaddressclaim_controller.go @@ -142,6 +142,7 @@ func (r *IpAddressClaimReconciler) Reconcile(ctx context.Context, req ctrl.Reque ParentPrefix: o.Spec.ParentPrefix, Metadata: &models.NetboxMetadata{ Tenant: o.Spec.Tenant, + Vrf: o.Spec.Vrf, }, }) if err != nil { diff --git a/internal/controller/ipaddressclaim_helpers.go b/internal/controller/ipaddressclaim_helpers.go index 0552d9ac..f932b331 100644 --- a/internal/controller/ipaddressclaim_helpers.go +++ b/internal/controller/ipaddressclaim_helpers.go @@ -55,6 +55,7 @@ func generateIpAddressSpec(claim *netboxv1.IpAddressClaim, ip string, logger log return netboxv1.IpAddressSpec{ IpAddress: ip, Tenant: claim.Spec.Tenant, + Vrf: claim.Spec.Vrf, CustomFields: customFields, Description: claim.Spec.Description, Comments: claim.Spec.Comments, @@ -68,8 +69,9 @@ func generateIpAddressRestorationHash(claim *netboxv1.IpAddressClaim) string { Name: claim.Name, ParentPrefix: claim.Spec.ParentPrefix, Tenant: claim.Spec.Tenant, + Vrf: claim.Spec.Vrf, } - return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.Tenant))) + return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.Tenant+rd.Vrf))) } type IpAddressClaimRestorationData struct { @@ -78,4 +80,5 @@ type IpAddressClaimRestorationData struct { Name string ParentPrefix string Tenant string + Vrf string } diff --git a/internal/controller/iprange_controller.go b/internal/controller/iprange_controller.go index 916566da..a7795bd6 100644 --- a/internal/controller/iprange_controller.go +++ b/internal/controller/iprange_controller.go @@ -268,6 +268,7 @@ func (r *IpRangeReconciler) generateNetboxIpRangeModelFromIpRangeSpec(o *netboxv Custom: netboxCustomFields, Description: description, Tenant: o.Spec.Tenant, + Vrf: o.Spec.Vrf, }, }, nil } diff --git a/internal/controller/iprangeclaim_controller.go b/internal/controller/iprangeclaim_controller.go index 5827ccdf..7aee0dbf 100644 --- a/internal/controller/iprangeclaim_controller.go +++ b/internal/controller/iprangeclaim_controller.go @@ -290,6 +290,7 @@ func (r *IpRangeClaimReconciler) restoreOrAssignIpRangeAndSetCondition(ctx conte Size: o.Spec.Size, Metadata: &models.NetboxMetadata{ Tenant: o.Spec.Tenant, + Vrf: o.Spec.Vrf, }, }, ) diff --git a/internal/controller/iprangeclaim_helpers.go b/internal/controller/iprangeclaim_helpers.go index a05e3c3f..0ca0f921 100644 --- a/internal/controller/iprangeclaim_helpers.go +++ b/internal/controller/iprangeclaim_helpers.go @@ -59,6 +59,7 @@ func generateIpRangeSpec(claim *netboxv1.IpRangeClaim, startIp string, endIp str StartAddress: startIp, EndAddress: endIp, Tenant: claim.Spec.Tenant, + Vrf: claim.Spec.Vrf, CustomFields: customFields, Description: claim.Spec.Description, Comments: claim.Spec.Comments, @@ -72,9 +73,10 @@ func generateIpRangeRestorationHash(claim *netboxv1.IpRangeClaim) string { Name: claim.Name, ParentPrefix: claim.Spec.ParentPrefix, Tenant: claim.Spec.Tenant, + Vrf: claim.Spec.Vrf, Size: fmt.Sprintf("%d", claim.Spec.Size), } - return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.Tenant+rd.Size))) + return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.Tenant+rd.Vrf+rd.Size))) } type IpRangeClaimRestorationData struct { @@ -83,5 +85,6 @@ type IpRangeClaimRestorationData struct { Name string ParentPrefix string Tenant string + Vrf string Size string } diff --git a/internal/controller/prefix_controller.go b/internal/controller/prefix_controller.go index 2e35d6bc..f9707dad 100644 --- a/internal/controller/prefix_controller.go +++ b/internal/controller/prefix_controller.go @@ -300,6 +300,7 @@ func generateNetboxPrefixModelFromPrefixSpec(spec *netboxv1.PrefixSpec, req ctrl Description: req.NamespacedName.String() + " // " + spec.Description, Site: spec.Site, Tenant: spec.Tenant, + Vrf: spec.Vrf, }, }, nil } diff --git a/internal/controller/prefixclaim_controller.go b/internal/controller/prefixclaim_controller.go index b4200407..9e27dfc1 100644 --- a/internal/controller/prefixclaim_controller.go +++ b/internal/controller/prefixclaim_controller.go @@ -259,6 +259,7 @@ func (r *PrefixClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) Metadata: &models.NetboxMetadata{ Tenant: o.Spec.Tenant, Site: o.Spec.Site, + Vrf: o.Spec.Vrf, }, }) if err != nil { diff --git a/internal/controller/prefixclaim_helpers.go b/internal/controller/prefixclaim_helpers.go index e53cbfaa..89e42336 100644 --- a/internal/controller/prefixclaim_helpers.go +++ b/internal/controller/prefixclaim_helpers.go @@ -55,6 +55,7 @@ func generatePrefixSpec(claim *netboxv1.PrefixClaim, prefix string, logger logr. return netboxv1.PrefixSpec{ Prefix: prefix, Tenant: claim.Spec.Tenant, + Vrf: claim.Spec.Vrf, Site: claim.Spec.Site, CustomFields: customFields, Description: claim.Spec.Description, @@ -90,6 +91,7 @@ func generatePrefixRestorationHash(claim *netboxv1.PrefixClaim) string { ParentPrefix: claim.Spec.ParentPrefix, PrefixLength: claim.Spec.PrefixLength, Tenant: claim.Spec.Tenant, + Vrf: claim.Spec.Vrf, ParentPrefixSelector: parentPrefixSelectorStr, } @@ -103,6 +105,7 @@ type PrefixClaimRestorationData struct { ParentPrefix string PrefixLength string Tenant string + Vrf string ParentPrefixSelector string } @@ -110,5 +113,5 @@ func (rd *PrefixClaimRestorationData) ComputeHash() string { if rd == nil { return "" } - return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.PrefixLength+rd.Tenant+rd.ParentPrefixSelector))) + return fmt.Sprintf("%x", sha1.Sum([]byte(rd.Namespace+rd.Name+rd.ParentPrefix+rd.PrefixLength+rd.Tenant+rd.Vrf+rd.ParentPrefixSelector))) } diff --git a/kind/load-local-data-job/main.py b/kind/load-local-data-job/main.py index 1caad5c7..d22161cb 100644 --- a/kind/load-local-data-job/main.py +++ b/kind/load-local-data-job/main.py @@ -96,6 +96,35 @@ class Site: print("Sites loaded") +# insert VRFs +@dataclass +class Vrf: + name: str + rd: str + +vrfs = [ + Vrf( + name="MY_VRF", + rd="65000:1000", + ), +] + +for vrf in vrfs: + try: + nb.ipam.vrfs.create( + name=vrf.name, + rd=vrf.rd, + ) + except pynetbox.RequestError as e: + pprint(e.error) + +# Build a lookup map of VRF name -> ID for use when creating prefixes +vrf_id_map = {} +for v in nb.ipam.vrfs.all(): + vrf_id_map[v.name] = v.id + +print(f"VRFs loaded (resolved IDs: {vrf_id_map})") + # create custom fields and associate custom fields with IP/IPRange/Prefix @dataclass class CustomField: @@ -203,6 +232,7 @@ class Prefix: status: str custom_fields: dict description: str + vrf: int = None prefixes = [ Prefix( @@ -924,6 +954,60 @@ class Prefix: "cfDataTypeInteger": 7, }, ), + # Resources used by VRF e2e tests (4.x.x.x range, assigned to MY_VRF) + Prefix( + prefix="4.0.0.0/24", + description="chainsaw test ipaddressclaim-ipv4-vrf-apply-update", + site=None, + tenant={ + "name": "MY_TENANT", + "slug": "my_tenant", + }, + status="active", + custom_fields={}, + vrf=vrf_id_map["MY_VRF"], + ), + Prefix( + prefix="4.1.0.0/24", + description="chainsaw test iprangeclaim-ipv4-vrf-apply-update", + site=None, + tenant={ + "name": "MY_TENANT", + "slug": "my_tenant", + }, + status="active", + custom_fields={}, + vrf=vrf_id_map["MY_VRF"], + ), + Prefix( + prefix="4.2.0.0/24", + description="chainsaw test prefixclaim-ipv4-vrf-apply-update", + site=None, + tenant={ + "name": "MY_TENANT", + "slug": "my_tenant", + }, + status="active", + custom_fields={}, + vrf=vrf_id_map["MY_VRF"], + ), + Prefix( + prefix="4.3.0.0/24", + description="chainsaw test prefixclaim-ipv4-parentprefixselector-vrf", + site=None, + tenant={ + "name": "MY_TENANT", + "slug": "my_tenant", + }, + status="active", + custom_fields={ + "environment": "Production", + "poolName": "Pool 1", + "cfDataTypeBool": True, + "cfDataTypeInteger": 1, + }, + vrf=vrf_id_map["MY_VRF"], + ), ### END ### ### Used by e2e tests ### ### Modifying entries might cause tests to fail ### @@ -938,6 +1022,7 @@ class Prefix: tenant=prefix.tenant, status=prefix.status, custom_fields=prefix.custom_fields, + vrf=prefix.vrf, ) except pynetbox.RequestError as e: pprint(e.error) diff --git a/pkg/netbox/api/ip_address.go b/pkg/netbox/api/ip_address.go index 3d13666b..2afd7332 100644 --- a/pkg/netbox/api/ip_address.go +++ b/pkg/netbox/api/ip_address.go @@ -54,6 +54,14 @@ func (r *NetboxClient) ReserveOrUpdateIpAddress(ipAddress *models.IPAddress) (*n desiredIPAddress.Tenant = &tenantDetails.Id } + if ipAddress.Metadata != nil && ipAddress.Metadata.Vrf != "" { + vrfDetails, err := r.GetVrfDetails(ipAddress.Metadata.Vrf) + if err != nil { + return nil, err + } + desiredIPAddress.Vrf = &vrfDetails.Id + } + // create ip address since it doesn't exist if len(responseIpAddress.Payload.Results) == 0 { return r.CreateIpAddress(desiredIPAddress) diff --git a/pkg/netbox/api/ip_address_claim.go b/pkg/netbox/api/ip_address_claim.go index 7d336504..56077506 100644 --- a/pkg/netbox/api/ip_address_claim.go +++ b/pkg/netbox/api/ip_address_claim.go @@ -77,6 +77,14 @@ func (r *NetboxClient) GetAvailableIpAddressByClaim(ipAddressClaim *models.IPAdd return nil, err } + // Don't assign an IP if the requested VRF doesn't exist in netbox + if ipAddressClaim.Metadata.Vrf != "" { + _, err := r.GetVrfDetails(ipAddressClaim.Metadata.Vrf) + if err != nil { + return nil, err + } + } + responseParentPrefix, err := r.GetPrefix(&models.Prefix{ Prefix: ipAddressClaim.ParentPrefix, Metadata: ipAddressClaim.Metadata, diff --git a/pkg/netbox/api/ip_address_claim_test.go b/pkg/netbox/api/ip_address_claim_test.go index 56f8f3c6..671c4c05 100644 --- a/pkg/netbox/api/ip_address_claim_test.go +++ b/pkg/netbox/api/ip_address_claim_test.go @@ -352,6 +352,67 @@ func TestIPAddressClaim(t *testing.T) { }) } +func TestIPAddressClaim_GetNoAvailableIPAddressWithNonExistingVrf(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // tenant + tenantName := "tenant" + tenantId := int64(2) + tenantOutputSlug := "tenant1" + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + // non-existing vrf + vrfName := "non-existing-vrf" + inputVrf := ipam.NewIpamVrfsListParams().WithName(&vrfName) + // empty vrf list + emptyVrfList := &ipam.IpamVrfsListOK{ + Payload: &ipam.IpamVrfsListOKBody{ + Results: []*netboxModels.VRF{}, + }, + } + + mockIpam.EXPECT().IpamVrfsList(inputVrf, nil).Return(emptyVrfList, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + // expected error + expectedErrorMsg := "failed to fetch vrf 'non-existing-vrf': not found" + + parentPrefix := "10.112.140.0/24" + + // init client + client := &NetboxClient{ + Ipam: mockIpam, + Tenancy: mockTenancy, + } + + actual, err := client.GetAvailableIpAddressByClaim(&models.IPAddressClaim{ + ParentPrefix: parentPrefix, + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + Vrf: vrfName, + }, + }) + + assert.EqualErrorf(t, err, expectedErrorMsg, "Error should be: %v, got: %v", expectedErrorMsg, err) + assert.Equal(t, actual, (*models.IPAddress)(nil)) +} + func TestIPAddressClaim_GetNoAvailableIPAddressWithTenancyChecks(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/pkg/netbox/api/ip_range.go b/pkg/netbox/api/ip_range.go index 7d7cf9b4..518fdc69 100644 --- a/pkg/netbox/api/ip_range.go +++ b/pkg/netbox/api/ip_range.go @@ -57,6 +57,14 @@ func (r *NetboxClient) ReserveOrUpdateIpRange(ipRange *models.IpRange) (*netboxM desiredIpRange.Tenant = &tenantDetails.Id } + if ipRange.Metadata != nil && ipRange.Metadata.Vrf != "" { + vrfDetails, err := r.GetVrfDetails(ipRange.Metadata.Vrf) + if err != nil { + return nil, err + } + desiredIpRange.Vrf = &vrfDetails.Id + } + // create ip range since it doesn't exist if len(responseIpRange.Payload.Results) == 0 { return r.CreateIpRange(desiredIpRange) diff --git a/pkg/netbox/api/ip_range_claim.go b/pkg/netbox/api/ip_range_claim.go index cf1968a1..d6cbf391 100644 --- a/pkg/netbox/api/ip_range_claim.go +++ b/pkg/netbox/api/ip_range_claim.go @@ -70,6 +70,14 @@ func (r *NetboxClient) GetAvailableIpRangeByClaim(ipRangeClaim *models.IpRangeCl return nil, err } + // Don't assign an IP range if the requested VRF doesn't exist in netbox + if ipRangeClaim.Metadata.Vrf != "" { + _, err := r.GetVrfDetails(ipRangeClaim.Metadata.Vrf) + if err != nil { + return nil, err + } + } + responseParentPrefix, err := r.GetPrefix(&models.Prefix{ Prefix: ipRangeClaim.ParentPrefix, Metadata: ipRangeClaim.Metadata, diff --git a/pkg/netbox/api/ip_range_claim_test.go b/pkg/netbox/api/ip_range_claim_test.go index 1ea23cff..ef20b9a2 100644 --- a/pkg/netbox/api/ip_range_claim_test.go +++ b/pkg/netbox/api/ip_range_claim_test.go @@ -315,3 +315,65 @@ func TestIPRangeClaim(t *testing.T) { AssertError(t, err, "invalid IP range") }) } + +func TestIPRangeClaim_GetNoAvailableIPRangeWithNonExistingVrf(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // tenant + tenantName := "tenant" + tenantId := int64(2) + tenantOutputSlug := "tenant1" + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + // non-existing vrf + vrfName := "non-existing-vrf" + inputVrf := ipam.NewIpamVrfsListParams().WithName(&vrfName) + // empty vrf list + emptyVrfList := &ipam.IpamVrfsListOK{ + Payload: &ipam.IpamVrfsListOKBody{ + Results: []*netboxModels.VRF{}, + }, + } + + mockIpam.EXPECT().IpamVrfsList(inputVrf, nil).Return(emptyVrfList, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + // expected error + expectedErrorMsg := "failed to fetch vrf 'non-existing-vrf': not found" + + parentPrefix := "10.112.140.0/24" + + // init client + client := &NetboxClient{ + Ipam: mockIpam, + Tenancy: mockTenancy, + } + + actual, err := client.GetAvailableIpRangeByClaim(&models.IpRangeClaim{ + ParentPrefix: parentPrefix, + Size: 3, + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + Vrf: vrfName, + }, + }) + + assert.EqualErrorf(t, err, expectedErrorMsg, "Error should be: %v, got: %v", expectedErrorMsg, err) + assert.Equal(t, actual, (*models.IpRange)(nil)) +} diff --git a/pkg/netbox/api/prefix.go b/pkg/netbox/api/prefix.go index 1c15830a..c62ceb72 100644 --- a/pkg/netbox/api/prefix.go +++ b/pkg/netbox/api/prefix.go @@ -19,6 +19,7 @@ package api import ( "fmt" "net/http" + "strconv" "github.com/netbox-community/go-netbox/v3/netbox/client/ipam" netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" @@ -67,6 +68,14 @@ func (r *NetboxClient) ReserveOrUpdatePrefix(prefix *models.Prefix) (*netboxMode desiredPrefix.Site = &siteDetails.Id } + if prefix.Metadata != nil && prefix.Metadata.Vrf != "" { + vrfDetails, err := r.GetVrfDetails(prefix.Metadata.Vrf) + if err != nil { + return nil, err + } + desiredPrefix.Vrf = &vrfDetails.Id + } + // create prefix since it doesn't exist if len(responsePrefix.Payload.Results) == 0 { return r.CreatePrefix(desiredPrefix) @@ -97,6 +106,16 @@ func (r *NetboxClient) GetPrefix(prefix *models.Prefix) (*ipam.IpamPrefixesListO requestPrefix := ipam. NewIpamPrefixesListParams(). WithPrefix(&prefix.Prefix) + + if prefix.Metadata != nil && prefix.Metadata.Vrf != "" { + vrfDetails, err := r.GetVrfDetails(prefix.Metadata.Vrf) + if err != nil { + return nil, err + } + vrfIdStr := strconv.FormatInt(vrfDetails.Id, 10) + requestPrefix = requestPrefix.WithVrfID(&vrfIdStr) + } + responsePrefix, err := r.Ipam.IpamPrefixesList(requestPrefix, nil) if err != nil { return nil, utils.NetboxError("failed to fetch Prefix details", err) diff --git a/pkg/netbox/api/prefix_claim.go b/pkg/netbox/api/prefix_claim.go index 59178ce4..93092324 100644 --- a/pkg/netbox/api/prefix_claim.go +++ b/pkg/netbox/api/prefix_claim.go @@ -127,6 +127,15 @@ func (r *NetboxClient) GetAvailablePrefixesByParentPrefixSelector(prefixClaimSpe fieldEntries["site_id"] = strconv.Itoa(int(details.Id)) } + if vrf, ok := prefixClaimSpec.ParentPrefixSelector["vrf"]; ok { + details, err := r.GetVrfDetails(vrf) + if err != nil { + return nil, err + } + + fieldEntries["vrf_id"] = strconv.Itoa(int(details.Id)) + } + if family, ok := prefixClaimSpec.ParentPrefixSelector["family"]; ok { if family == "IPv4" { family = "4" @@ -141,7 +150,7 @@ func (r *NetboxClient) GetAvailablePrefixesByParentPrefixSelector(prefixClaimSpe parentPrefixSelectorCustomFields := make([]CustomFieldEntry, 0, len(prefixClaimSpec.ParentPrefixSelector)) for k, v := range prefixClaimSpec.ParentPrefixSelector { switch k { - case "tenant", "site", "family": + case "tenant", "site", "vrf", "family": // skip built in fields default: parentPrefixSelectorCustomFields = append(parentPrefixSelectorCustomFields, CustomFieldEntry{ @@ -230,6 +239,7 @@ func (r *NetboxClient) isParentPrefixCandidate(prefixClaimSpec *netboxv1.PrefixC Metadata: &models.NetboxMetadata{ Tenant: prefixClaimSpec.Tenant, Site: prefixClaimSpec.Site, + Vrf: prefixClaimSpec.Vrf, }, }); err == nil { return true @@ -252,6 +262,14 @@ func (r *NetboxClient) GetAvailablePrefixByClaim(prefixClaim *models.PrefixClaim } } + // Don't assign a prefix if the requested VRF doesn't exist in netbox + if prefixClaim.Metadata.Vrf != "" { + _, err := r.GetVrfDetails(prefixClaim.Metadata.Vrf) + if err != nil { + return nil, err + } + } + responseParentPrefix, err := r.GetPrefix(&models.Prefix{ Prefix: prefixClaim.ParentPrefix, Metadata: prefixClaim.Metadata, diff --git a/pkg/netbox/api/prefix_claim_test.go b/pkg/netbox/api/prefix_claim_test.go index 75860140..5fadc32a 100644 --- a/pkg/netbox/api/prefix_claim_test.go +++ b/pkg/netbox/api/prefix_claim_test.go @@ -912,6 +912,67 @@ func TestPrefixClaim_GetNoAvailablePrefixesWithErrorWhenGettingTenantList(t *tes assert.Equal(t, prefix, (*models.Prefix)(nil)) } +func TestPrefixClaim_GetNoAvailablePrefixesWithNonExistingVrf(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + + // tenant + tenantName := "tenant" + tenantId := int64(2) + tenantOutputSlug := "tenant1" + + inputTenant := tenancy.NewTenancyTenantsListParams().WithName(&tenantName) + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + // non-existing vrf + vrfName := "non-existing-vrf" + inputVrf := ipam.NewIpamVrfsListParams().WithName(&vrfName) + // empty vrf list + emptyVrfList := &ipam.IpamVrfsListOK{ + Payload: &ipam.IpamVrfsListOKBody{ + Results: []*netboxModels.VRF{}, + }, + } + + mockIpam.EXPECT().IpamVrfsList(inputVrf, nil).Return(emptyVrfList, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(inputTenant, nil).Return(expectedTenant, nil).AnyTimes() + + // expected error + expectedErrorMsg := "failed to fetch vrf 'non-existing-vrf': not found" + + parentPrefix := "10.112.140.0/24" + + netboxClient := &NetboxClient{ + Ipam: mockIpam, + Tenancy: mockTenancy, + } + + prefix, err := netboxClient.GetAvailablePrefixByClaim(&models.PrefixClaim{ + ParentPrefix: parentPrefix, + PrefixLength: "/28.", + Metadata: &models.NetboxMetadata{ + Tenant: tenantName, + Vrf: vrfName, + }, + }) + + assert.EqualErrorf(t, err, expectedErrorMsg, "Error should be: %v, got: %v", expectedErrorMsg, err) + assert.Equal(t, prefix, (*models.Prefix)(nil)) +} + func TestPrefixClaim_GetNoAvailablePrefixesWithNonExistingSite(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -1259,3 +1320,147 @@ func TestPrefixClaim_GetAvailablePrefixByParentPrefixSelectorFailIfNonExistingFi assert.Nil(t, actual) AssertError(t, err, "invalid parentPrefixSelector, netbox custom fields non-existing do not exist") } + +func TestPrefixClaim_GetAvailablePrefixByParentPrefixSelectorWithVrf(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + pxcSpec := netboxv1.PrefixClaimSpec{ + ParentPrefixSelector: map[string]string{ + "environment": "dev", + "family": "IPv4", + "tenant": "tenant", + "site": "Site1", + "vrf": "someVrf", + }, + PrefixLength: "/32", + } + + mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + mockPrefixIpam := mock_interfaces.NewMockIpamInterface(ctrl) + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + mockDcim := mock_interfaces.NewMockDcimInterface(ctrl) + + // example of site + siteId := int64(3) + siteName := "Site1" + siteOutputSlug := "site1" + expectedSite := &dcim.DcimSitesListOK{ + Payload: &dcim.DcimSitesListOKBody{ + Results: []*netboxModels.Site{ + { + ID: siteId, + Name: &siteName, + Slug: &siteOutputSlug, + }, + }, + }, + } + inputSite := dcim.NewDcimSitesListParams().WithName(&siteName) + + // tenant + tenantName := "tenant" + tenantId := int64(2) + tenantOutputSlug := "tenant1" + + expectedTenant := &tenancy.TenancyTenantsListOK{ + Payload: &tenancy.TenancyTenantsListOKBody{ + Results: []*netboxModels.Tenant{ + { + ID: tenantId, + Name: &tenantName, + Slug: &tenantOutputSlug, + }, + }, + }, + } + + // vrf + vrfName := "someVrf" + vrfId := int64(5) + inputVrf := ipam.NewIpamVrfsListParams().WithName(&vrfName) + expectedVrf := &ipam.IpamVrfsListOK{ + Payload: &ipam.IpamVrfsListOKBody{ + Results: []*netboxModels.VRF{ + { + ID: vrfId, + Name: &vrfName, + }, + }, + }, + } + + parentPrefix := "10.112.140.0/24" + parentPrefixId := int64(1) + prefixListInput := ipam. + NewIpamPrefixesListParams() + + prefixFamily := int64(IPv4Family) + prefixFamilyLabel := netboxModels.PrefixFamilyLabelIPV4 + prefixListOutput := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + ID: parentPrefixId, + Prefix: &parentPrefix, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, + }, + }, + }, + } + + prefixListInputWithParam := ipam.NewIpamPrefixesListParams().WithPrefix(&parentPrefix) + prefixListOutputWithParam := &ipam.IpamPrefixesListOK{ + Payload: &ipam.IpamPrefixesListOKBody{ + Results: []*netboxModels.Prefix{ + { + Prefix: &parentPrefix, + ID: parentPrefixId, + Family: &netboxModels.PrefixFamily{Label: &prefixFamilyLabel, Value: &prefixFamily}, + }, + }, + }, + } + + prefixAvailableListInput := ipam.NewIpamPrefixesAvailablePrefixesListParams().WithID(parentPrefixId) + prefixAvailableListOutput := &ipam.IpamPrefixesAvailablePrefixesListOK{ + Payload: []*netboxModels.AvailablePrefix{ + { + Family: prefixFamily, + Prefix: parentPrefix, + }, + }, + } + + // get prefix to check if it's a candidate + expectedCustomFieldName := "environment" + expectedCustomFields := &extras.ExtrasCustomFieldsListOK{ + Payload: &extras.ExtrasCustomFieldsListOKBody{ + Results: []*netboxModels.CustomField{ + { + Name: &expectedCustomFieldName, + }, + }, + }, + } + + mockPrefixIpam.EXPECT().IpamPrefixesList(prefixListInput, nil, gomock.Any()).Return(prefixListOutput, nil).Times(1) + mockPrefixIpam.EXPECT().IpamPrefixesList(prefixListInputWithParam, nil).Return(prefixListOutputWithParam, nil).Times(1) + mockPrefixIpam.EXPECT().IpamPrefixesAvailablePrefixesList(prefixAvailableListInput, nil).Return(prefixAvailableListOutput, nil).AnyTimes() + mockPrefixIpam.EXPECT().IpamVrfsList(inputVrf, nil).Return(expectedVrf, nil).AnyTimes() + mockTenancy.EXPECT().TenancyTenantsList(gomock.Any(), nil).Return(expectedTenant, nil).AnyTimes() + mockDcim.EXPECT().DcimSitesList(inputSite, nil).Return(expectedSite, nil).AnyTimes() + mockExtras.EXPECT().ExtrasCustomFieldsList(extras.NewExtrasCustomFieldsListParams(), gomock.Any(), gomock.Any()).Return(expectedCustomFields, nil).AnyTimes() + + netboxClient := &NetboxClient{ + Ipam: mockPrefixIpam, + Tenancy: mockTenancy, + Extras: mockExtras, + Dcim: mockDcim, + } + + actual, err := netboxClient.GetAvailablePrefixesByParentPrefixSelector(&pxcSpec) + + assert.Nil(t, err) + assert.Equal(t, parentPrefix, actual[0].Prefix) +} diff --git a/pkg/netbox/api/vrf.go b/pkg/netbox/api/vrf.go new file mode 100644 index 00000000..af8c145c --- /dev/null +++ b/pkg/netbox/api/vrf.go @@ -0,0 +1,40 @@ +/* +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 api + +import ( + "github.com/netbox-community/go-netbox/v3/netbox/client/ipam" + + "github.com/netbox-community/netbox-operator/pkg/netbox/models" + "github.com/netbox-community/netbox-operator/pkg/netbox/utils" +) + +func (r *NetboxClient) GetVrfDetails(name string) (*models.Vrf, error) { + request := ipam.NewIpamVrfsListParams().WithName(&name) + response, err := r.Ipam.IpamVrfsList(request, nil) + if err != nil { + return nil, utils.NetboxError("failed to fetch VRF details", err) + } + if len(response.Payload.Results) == 0 { + return nil, utils.NetboxNotFoundError("vrf '" + name + "'") + } + + return &models.Vrf{ + Id: response.Payload.Results[0].ID, + Name: *response.Payload.Results[0].Name, + }, nil +} diff --git a/pkg/netbox/api/vrf_test.go b/pkg/netbox/api/vrf_test.go new file mode 100644 index 00000000..b8971627 --- /dev/null +++ b/pkg/netbox/api/vrf_test.go @@ -0,0 +1,101 @@ +/* +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 api + +import ( + "errors" + "testing" + + "github.com/netbox-community/netbox-operator/gen/mock_interfaces" + + "github.com/netbox-community/go-netbox/v3/netbox/client/ipam" + netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestVrf_GetVrfDetails(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) + + vrf := "myVrf" + + vrfListRequestInput := ipam.NewIpamVrfsListParams().WithName(&vrf) + + vrfOutputId := int64(1) + vrfListOutput := &ipam.IpamVrfsListOK{ + Payload: &ipam.IpamVrfsListOKBody{ + Results: []*netboxModels.VRF{ + { + ID: vrfOutputId, + Name: &vrf, + }, + }, + }, + } + + mockIpam.EXPECT().IpamVrfsList(vrfListRequestInput, nil).Return(vrfListOutput, nil) + netboxClient := &NetboxClient{Ipam: mockIpam} + + actual, err := netboxClient.GetVrfDetails(vrf) + assert.NoError(t, err) + assert.Equal(t, vrf, actual.Name) + assert.Equal(t, vrfOutputId, actual.Id) +} + +func TestVrf_GetEmptyResult(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) + + vrf := "myVrf" + + vrfListRequestInput := ipam.NewIpamVrfsListParams().WithName(&vrf) + + emptyListVrfOutput := &ipam.IpamVrfsListOK{ + Payload: &ipam.IpamVrfsListOKBody{ + Results: []*netboxModels.VRF{}, + }, + } + + mockIpam.EXPECT().IpamVrfsList(vrfListRequestInput, nil).Return(emptyListVrfOutput, nil) + netboxClient := &NetboxClient{Ipam: mockIpam} + + actual, err := netboxClient.GetVrfDetails(vrf) + assert.Nil(t, actual) + assert.EqualError(t, err, "failed to fetch vrf 'myVrf': not found") +} + +func TestVrf_GetError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) + + vrf := "myVrf" + + vrfListRequestInput := ipam.NewIpamVrfsListParams().WithName(&vrf) + + expectedErr := "error getting vrfs list" + + mockIpam.EXPECT().IpamVrfsList(vrfListRequestInput, nil).Return(nil, errors.New(expectedErr)) + netboxClient := &NetboxClient{Ipam: mockIpam} + + actual, err := netboxClient.GetVrfDetails(vrf) + assert.Nil(t, actual) + assert.EqualError(t, err, "failed to fetch VRF details: "+expectedErr) +} diff --git a/pkg/netbox/interfaces/netbox.go b/pkg/netbox/interfaces/netbox.go index 852891fb..6c4e5830 100644 --- a/pkg/netbox/interfaces/netbox.go +++ b/pkg/netbox/interfaces/netbox.go @@ -42,6 +42,8 @@ type IpamInterface interface { IpamIPRangesUpdate(params *ipam.IpamIPRangesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesUpdateOK, error) IpamIPRangesDelete(params *ipam.IpamIPRangesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesDeleteNoContent, error) IpamIPRangesAvailableIpsList(params *ipam.IpamIPRangesAvailableIpsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesAvailableIpsListOK, error) + + IpamVrfsList(params *ipam.IpamVrfsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamVrfsListOK, error) } type TenancyInterface interface { diff --git a/pkg/netbox/models/ipam.go b/pkg/netbox/models/ipam.go index bfe40304..4c59bac4 100644 --- a/pkg/netbox/models/ipam.go +++ b/pkg/netbox/models/ipam.go @@ -28,6 +28,11 @@ type Site struct { Slug string `json:"slug,omitempty"` } +type Vrf struct { + Id int64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` +} + type NetboxMetadata struct { Comments string `json:"comments,omitempty"` Custom map[string]string `json:"customFields,omitempty"` @@ -35,6 +40,7 @@ type NetboxMetadata struct { Region string `json:"region,omitempty"` Site string `json:"site,omitempty"` Tenant string `json:"tenant,omitempty"` + Vrf string `json:"vrf,omitempty"` } type IPAddress struct { diff --git a/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/chainsaw-test.yaml b/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/chainsaw-test.yaml new file mode 100644 index 00000000..43bf453b --- /dev/null +++ b/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/chainsaw-test.yaml @@ -0,0 +1,111 @@ +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: ipaddressclaim-ipv4-vrf-apply-update + annotations: + description: Tests if creation and update is successful with VRF +spec: + steps: + - name: Apply CR + try: + - apply: + file: netbox_v1_ipaddressclaim.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpAddressClaim + metadata: + name: ipaddressclaim-ipv4-vrf-apply-update + spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: false + parentPrefix: "4.0.0.0/24" + status: + (conditions[?type == 'Ready']): + - status: 'True' + ipAddress: 4.0.0.1/32 + ipAddressDotDecimal: 4.0.0.1 + ipAddressName: ipaddressclaim-ipv4-vrf-apply-update + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpAddress + metadata: + name: ipaddressclaim-ipv4-vrf-apply-update + finalizers: + - ipaddress.netbox.dev/finalizer + ownerReferences: + - apiVersion: netbox.dev/v1 + blockOwnerDeletion: true + controller: true + kind: IpAddressClaim + name: ipaddressclaim-ipv4-vrf-apply-update + spec: + comments: your comments + description: some description + ipAddress: 4.0.0.1/32 + tenant: MY_TENANT + vrf: MY_VRF + status: + (conditions[?type == 'Ready']): + - status: 'True' + - name: Update CR + try: + - apply: + file: netbox_v1_ipaddressclaim-update.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpAddressClaim + metadata: + name: ipaddressclaim-ipv4-vrf-apply-update + spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "new description" + comments: "new comments" + preserveInNetbox: false + parentPrefix: "4.0.0.0/24" + status: + (conditions[?type == 'Ready']): + - status: 'True' + ipAddress: 4.0.0.1/32 + ipAddressDotDecimal: 4.0.0.1 + ipAddressName: ipaddressclaim-ipv4-vrf-apply-update + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpAddress + metadata: + name: ipaddressclaim-ipv4-vrf-apply-update + finalizers: + - ipaddress.netbox.dev/finalizer + ownerReferences: + - apiVersion: netbox.dev/v1 + blockOwnerDeletion: true + controller: true + kind: IpAddressClaim + name: ipaddressclaim-ipv4-vrf-apply-update + spec: + comments: new comments + description: new description + ipAddress: 4.0.0.1/32 + tenant: MY_TENANT + vrf: MY_VRF + status: + (conditions[?type == 'Ready']): + - status: 'True' + - name: Cleanup events + description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource + cleanup: + - script: + content: |- + kubectl delete events --field-selector involvedObject.name=ipaddressclaim-ipv4-vrf-apply-update -n $NAMESPACE diff --git a/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim-update.yaml b/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim-update.yaml new file mode 100644 index 00000000..eebc6f5c --- /dev/null +++ b/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim-update.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpAddressClaim +metadata: + name: ipaddressclaim-ipv4-vrf-apply-update +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "new description" + comments: "new comments" + preserveInNetbox: false + parentPrefix: "4.0.0.0/24" diff --git a/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim.yaml b/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim.yaml new file mode 100644 index 00000000..07ea9471 --- /dev/null +++ b/tests/e2e/ipaddress/ipv4/ipaddressclaim-ipv4-vrf-apply-update/netbox_v1_ipaddressclaim.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpAddressClaim +metadata: + name: ipaddressclaim-ipv4-vrf-apply-update +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: false + parentPrefix: "4.0.0.0/24" diff --git a/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/chainsaw-test.yaml b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/chainsaw-test.yaml new file mode 100644 index 00000000..39e03761 --- /dev/null +++ b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/chainsaw-test.yaml @@ -0,0 +1,45 @@ +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: iprangeclaim-ipv4-invalid-vrf + annotations: + description: Tests if creation fails with invalid VRF +spec: + steps: + - name: Apply CR + try: + - apply: + file: netbox_v1_iprangeclaim.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpRangeClaim + metadata: + name: iprangeclaim-ipv4-invalid-vrf + spec: + vrf: "nonexistingvrf" + status: + (conditions[?type == 'IPRangeAssigned']): + - status: 'False' + - assert: + resource: + apiVersion: v1 + kind: Event + type: Warning + reason: IPRangeCRNotCreated + source: + component: ip-range-claim-controller + message: "Failed to fetch new IP Range from NetBox: failed to fetch vrf 'nonexistingvrf': not found" + involvedObject: + apiVersion: netbox.dev/v1 + kind: IpRangeClaim + name: iprangeclaim-ipv4-invalid-vrf + - name: Cleanup events and leases + description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) + cleanup: + - script: + content: |- + kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-invalid-vrf -n $NAMESPACE diff --git a/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/netbox_v1_iprangeclaim.yaml b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/netbox_v1_iprangeclaim.yaml new file mode 100644 index 00000000..1a74ff07 --- /dev/null +++ b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-invalid-vrf/netbox_v1_iprangeclaim.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpRangeClaim +metadata: + name: iprangeclaim-ipv4-invalid-vrf +spec: + tenant: "MY_TENANT" + vrf: "nonexistingvrf" + description: "some description" + comments: "your comments" + preserveInNetbox: false + parentPrefix: "3.2.3.0/24" + size: 30 diff --git a/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/chainsaw-test.yaml b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/chainsaw-test.yaml new file mode 100644 index 00000000..0c10549d --- /dev/null +++ b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/chainsaw-test.yaml @@ -0,0 +1,121 @@ +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: iprangeclaim-ipv4-vrf-apply-update + annotations: + description: Tests if creation and update is successful with VRF +spec: + steps: + - name: Apply CR + try: + - apply: + file: netbox_v1_iprangeclaim.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpRangeClaim + metadata: + name: iprangeclaim-ipv4-vrf-apply-update + spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + parentPrefix: "4.1.0.0/24" + size: 30 + status: + (conditions[?type == 'Ready']): + - status: 'True' + endAddress: 4.1.0.30/32 + endAddressDotDecimal: 4.1.0.30 + ipRangeName: iprangeclaim-ipv4-vrf-apply-update + ipRange: 4.1.0.1/32-4.1.0.30/32 + ipRangeDotDecimal: 4.1.0.1-4.1.0.30 + startAddress: 4.1.0.1/32 + startAddressDotDecimal: 4.1.0.1 + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpRange + metadata: + name: iprangeclaim-ipv4-vrf-apply-update + finalizers: + - iprange.netbox.dev/finalizer + ownerReferences: + - apiVersion: netbox.dev/v1 + blockOwnerDeletion: true + controller: true + kind: IpRangeClaim + name: iprangeclaim-ipv4-vrf-apply-update + spec: + comments: your comments + description: some description + startAddress: 4.1.0.1/32 + endAddress: 4.1.0.30/32 + tenant: MY_TENANT + vrf: MY_VRF + status: + (conditions[?type == 'Ready']): + - status: 'True' + - name: Update CR + try: + - apply: + file: netbox_v1_iprangeclaim-update.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpRangeClaim + metadata: + name: iprangeclaim-ipv4-vrf-apply-update + spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "new description" + comments: "new comments" + parentPrefix: "4.1.0.0/24" + size: 30 + status: + (conditions[?type == 'Ready']): + - status: 'True' + endAddress: 4.1.0.30/32 + endAddressDotDecimal: 4.1.0.30 + ipRangeName: iprangeclaim-ipv4-vrf-apply-update + ipRange: 4.1.0.1/32-4.1.0.30/32 + ipRangeDotDecimal: 4.1.0.1-4.1.0.30 + startAddress: 4.1.0.1/32 + startAddressDotDecimal: 4.1.0.1 + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: IpRange + metadata: + name: iprangeclaim-ipv4-vrf-apply-update + finalizers: + - iprange.netbox.dev/finalizer + ownerReferences: + - apiVersion: netbox.dev/v1 + blockOwnerDeletion: true + controller: true + kind: IpRangeClaim + name: iprangeclaim-ipv4-vrf-apply-update + spec: + comments: new comments + description: new description + startAddress: 4.1.0.1/32 + endAddress: 4.1.0.30/32 + tenant: MY_TENANT + vrf: MY_VRF + status: + (conditions[?type == 'Ready']): + - status: 'True' + - name: Cleanup events and leases + description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource and lease cleanup for preventing delays when using the same prefixes (e.g. with "invalid" tests) + cleanup: + - script: + content: |- + kubectl delete events --field-selector involvedObject.name=iprangeclaim-ipv4-vrf-apply-update -n $NAMESPACE diff --git a/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim-update.yaml b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim-update.yaml new file mode 100644 index 00000000..80f803dc --- /dev/null +++ b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim-update.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpRangeClaim +metadata: + name: iprangeclaim-ipv4-vrf-apply-update +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "new description" + comments: "new comments" + preserveInNetbox: false + parentPrefix: "4.1.0.0/24" + size: 30 diff --git a/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim.yaml b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim.yaml new file mode 100644 index 00000000..34638237 --- /dev/null +++ b/tests/e2e/iprange/ipv4/iprangeclaim-ipv4-vrf-apply-update/netbox_v1_iprangeclaim.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: netbox.dev/v1 +kind: IpRangeClaim +metadata: + name: iprangeclaim-ipv4-vrf-apply-update +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: false + parentPrefix: "4.1.0.0/24" + size: 30 diff --git a/tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/chainsaw-test.yaml b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/chainsaw-test.yaml new file mode 100644 index 00000000..c86826cd --- /dev/null +++ b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/chainsaw-test.yaml @@ -0,0 +1,78 @@ +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: prefixclaim-ipv4-parentprefixselector-vrf + annotations: + description: Tests if creation succeeds when using parentPrefixSelector with VRF +spec: + steps: + - name: Apply CR + try: + - apply: + file: netbox_v1_prefixclaim.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: PrefixClaim + metadata: + name: prefixclaim-ipv4-parentprefixselector-vrf + spec: + comments: your comments + description: some description + parentPrefixSelector: + environment: Production + family: IPv4 + poolName: Pool 1 + tenant: MY_TENANT + vrf: MY_VRF + prefixLength: /28 + preserveInNetbox: true + tenant: MY_TENANT + vrf: MY_VRF + status: + parentPrefix: 4.3.0.0/24 + prefix: 4.3.0.0/28 + prefixName: prefixclaim-ipv4-parentprefixselector-vrf + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: Prefix + metadata: + name: prefixclaim-ipv4-parentprefixselector-vrf + spec: + comments: your comments + description: some description + prefix: 4.3.0.0/28 + preserveInNetbox: true + tenant: MY_TENANT + vrf: MY_VRF + - name: Set preserveInNetbox to false + description: Set preserveInNetbox to false to clean up the NetBox test instance + try: + - patch: + resource: + apiVersion: netbox.dev/v1 + kind: PrefixClaim + metadata: + name: prefixclaim-ipv4-parentprefixselector-vrf + spec: + preserveInNetbox: false + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: Prefix + metadata: + name: prefixclaim-ipv4-parentprefixselector-vrf + status: + (conditions[?type == 'Ready']): + - observedGeneration: 2 + status: 'True' + - name: Cleanup events + description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource + cleanup: + - script: + content: |- + kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv4-parentprefixselector-vrf -n $NAMESPACE diff --git a/tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/netbox_v1_prefixclaim.yaml b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/netbox_v1_prefixclaim.yaml new file mode 100644 index 00000000..bae61506 --- /dev/null +++ b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-parentprefixselector-vrf/netbox_v1_prefixclaim.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: netbox.dev/v1 +kind: PrefixClaim +metadata: + name: prefixclaim-ipv4-parentprefixselector-vrf +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: true + prefixLength: "/28" + parentPrefixSelector: # The keys and values are case-sensitive + tenant: "MY_TENANT" # Use the `name` value instead of the `slug` value + vrf: "MY_VRF" + family: "IPv4" # Can only be either IPv4 or IPv6" + # custom fields of your interest + environment: "Production" + poolName: "Pool 1" diff --git a/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/chainsaw-test.yaml b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/chainsaw-test.yaml new file mode 100644 index 00000000..49451826 --- /dev/null +++ b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/chainsaw-test.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: prefixclaim-ipv4-vrf-apply-update + annotations: + description: Tests if creation and update is successful with VRF using parentPrefix +spec: + steps: + - name: Apply CR + try: + - apply: + file: netbox_v1_prefixclaim.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: PrefixClaim + metadata: + name: prefixclaim-ipv4-vrf-apply-update + spec: + comments: your comments + description: some description + parentPrefix: 4.2.0.0/24 + prefixLength: /28 + tenant: MY_TENANT + vrf: MY_VRF + status: + parentPrefix: 4.2.0.0/24 + prefix: 4.2.0.0/28 + prefixName: prefixclaim-ipv4-vrf-apply-update + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: Prefix + metadata: + name: prefixclaim-ipv4-vrf-apply-update + spec: + comments: your comments + description: some description + prefix: 4.2.0.0/28 + tenant: MY_TENANT + vrf: MY_VRF + - name: Update CR + try: + - apply: + file: netbox_v1_prefixclaim-update.yaml + - name: Check CR spec and status + try: + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: PrefixClaim + metadata: + name: prefixclaim-ipv4-vrf-apply-update + spec: + comments: new comments + description: new description + parentPrefix: 4.2.0.0/24 + prefixLength: /28 + tenant: MY_TENANT + vrf: MY_VRF + status: + parentPrefix: 4.2.0.0/24 + prefix: 4.2.0.0/28 + prefixName: prefixclaim-ipv4-vrf-apply-update + - assert: + resource: + apiVersion: netbox.dev/v1 + kind: Prefix + metadata: + name: prefixclaim-ipv4-vrf-apply-update + spec: + comments: new comments + description: new description + prefix: 4.2.0.0/28 + tenant: MY_TENANT + vrf: MY_VRF + - name: Cleanup events + description: Events cleanup required to fix issues with failing tests that assert the wrong Error resource + cleanup: + - script: + content: |- + kubectl delete events --field-selector involvedObject.name=prefixclaim-ipv4-vrf-apply-update -n $NAMESPACE diff --git a/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim-update.yaml b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim-update.yaml new file mode 100644 index 00000000..19cc28c4 --- /dev/null +++ b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim-update.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: netbox.dev/v1 +kind: PrefixClaim +metadata: + name: prefixclaim-ipv4-vrf-apply-update +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "new description" + comments: "new comments" + preserveInNetbox: false + parentPrefix: "4.2.0.0/24" + prefixLength: "/28" diff --git a/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim.yaml b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim.yaml new file mode 100644 index 00000000..3983f1f6 --- /dev/null +++ b/tests/e2e/prefix/ipv4/prefixclaim-ipv4-vrf-apply-update/netbox_v1_prefixclaim.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: netbox.dev/v1 +kind: PrefixClaim +metadata: + name: prefixclaim-ipv4-vrf-apply-update +spec: + tenant: "MY_TENANT" + vrf: "MY_VRF" + description: "some description" + comments: "your comments" + preserveInNetbox: false + parentPrefix: "4.2.0.0/24" + prefixLength: "/28"