diff --git a/api/v1/ipaddress_types.go b/api/v1/ipaddress_types.go index 0c6a9b71..3dc09a52 100644 --- a/api/v1/ipaddress_types.go +++ b/api/v1/ipaddress_types.go @@ -36,6 +36,10 @@ type IpAddressSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // A list of tags that will be assigned to the resource in NetBox. + // Each tag must define exactly one of the `name` or `slug` fields. + Tags []Tag `json:"tags,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..a0cd7bc9 100644 --- a/api/v1/ipaddressclaim_types.go +++ b/api/v1/ipaddressclaim_types.go @@ -36,6 +36,10 @@ type IpAddressClaimSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // A list of tags that will be assigned to the NetBox resource. + // Each tag must define exactly one of the `name` or `slug` fields. + Tags []Tag `json:"tags,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..4adfc025 100644 --- a/api/v1/iprange_types.go +++ b/api/v1/iprange_types.go @@ -44,6 +44,10 @@ type IpRangeSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // A list of tags that will be assigned to the resource in NetBox. + // Each tag must define exactly one of the `name` or `slug` fields. + Tags []Tag `json:"tags,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..f9d960c0 100644 --- a/api/v1/iprangeclaim_types.go +++ b/api/v1/iprangeclaim_types.go @@ -47,6 +47,10 @@ type IpRangeClaimSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // A list of tags that will be assigned to the NetBox resource. + // Each tag must define exactly one of the `name` or `slug` fields. + Tags []Tag `json:"tags,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..8bcf4c3f 100644 --- a/api/v1/prefix_types.go +++ b/api/v1/prefix_types.go @@ -42,6 +42,13 @@ type PrefixSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // A list of tags that will be assigned to the resource in NetBox. + // Each tag may contain either the `name` or `slug` field (one of them is required). + // Example: + // - name: tag1 + // - slug: tag2 + Tags []Tag `json:"tags,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 24f14516..ef698271 100644 --- a/api/v1/prefixclaim_types.go +++ b/api/v1/prefixclaim_types.go @@ -61,6 +61,10 @@ type PrefixClaimSpec struct { //+kubebuilder:validation:XValidation:rule="self == oldSelf",message="Field 'tenant' is immutable" Tenant string `json:"tenant,omitempty"` + // A list of tags that will be assigned to the NetBox resource. + // Each tag must define exactly one of the `name` or `slug` fields. + Tags []Tag `json:"tags,omitempty"` + // Description that should be added to the resource in NetBox // Field is mutable, not required Description string `json:"description,omitempty"` diff --git a/api/v1/tag_types.go b/api/v1/tag_types.go new file mode 100644 index 00000000..ac63d314 --- /dev/null +++ b/api/v1/tag_types.go @@ -0,0 +1,28 @@ +/* +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 + +// +kubebuilder:validation:XValidation:rule="has(self.name) != has(self.slug)",message="exactly one of name or slug must be specified" +type Tag struct { + // +optional + // Name of the tag + Name string `json:"name,omitempty"` + + // +optional + // Slug of the tag + Slug string `json:"slug,omitempty"` +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 99e78261..251e3b2d 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -114,6 +114,11 @@ func (in *IpAddressClaimList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IpAddressClaimSpec) DeepCopyInto(out *IpAddressClaimSpec) { *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]Tag, len(*in)) + copy(*out, *in) + } if in.CustomFields != nil { in, out := &in.CustomFields, &out.CustomFields *out = make(map[string]string, len(*in)) @@ -190,6 +195,11 @@ func (in *IpAddressList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IpAddressSpec) DeepCopyInto(out *IpAddressSpec) { *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]Tag, len(*in)) + copy(*out, *in) + } if in.CustomFields != nil { in, out := &in.CustomFields, &out.CustomFields *out = make(map[string]string, len(*in)) @@ -320,6 +330,11 @@ func (in *IpRangeClaimList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IpRangeClaimSpec) DeepCopyInto(out *IpRangeClaimSpec) { *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]Tag, len(*in)) + copy(*out, *in) + } if in.CustomFields != nil { in, out := &in.CustomFields, &out.CustomFields *out = make(map[string]string, len(*in)) @@ -406,6 +421,11 @@ func (in *IpRangeList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IpRangeSpec) DeepCopyInto(out *IpRangeSpec) { *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]Tag, len(*in)) + copy(*out, *in) + } if in.CustomFields != nil { in, out := &in.CustomFields, &out.CustomFields *out = make(map[string]string, len(*in)) @@ -543,6 +563,11 @@ func (in *PrefixClaimSpec) DeepCopyInto(out *PrefixClaimSpec) { (*out)[key] = val } } + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]Tag, len(*in)) + copy(*out, *in) + } if in.CustomFields != nil { in, out := &in.CustomFields, &out.CustomFields *out = make(map[string]string, len(*in)) @@ -619,6 +644,11 @@ func (in *PrefixList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PrefixSpec) DeepCopyInto(out *PrefixSpec) { *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]Tag, len(*in)) + copy(*out, *in) + } if in.CustomFields != nil { in, out := &in.CustomFields, &out.CustomFields *out = make(map[string]string, len(*in)) @@ -659,3 +689,18 @@ func (in *PrefixStatus) DeepCopy() *PrefixStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tag) DeepCopyInto(out *Tag) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tag. +func (in *Tag) DeepCopy() *Tag { + if in == nil { + return nil + } + out := new(Tag) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/netbox.dev_ipaddressclaims.yaml b/config/crd/bases/netbox.dev_ipaddressclaims.yaml index 225db11e..5cbcbfdb 100644 --- a/config/crd/bases/netbox.dev_ipaddressclaims.yaml +++ b/config/crd/bases/netbox.dev_ipaddressclaims.yaml @@ -105,6 +105,23 @@ spec: recreated in Kubernetes) Field is mutable, not required type: boolean + tags: + description: |- + A list of tags that will be assigned to the NetBox resource. + Each tag must define exactly one of the `name` or `slug` fields. + items: + properties: + name: + description: Name of the tag + type: string + slug: + description: Slug of the tag + type: string + type: object + x-kubernetes-validations: + - message: exactly one of name or slug must be specified + rule: has(self.name) != has(self.slug) + type: array tenant: description: |- The NetBox Tenant to be assigned to this resource in NetBox. Use the `name` value instead of the `slug` value diff --git a/config/crd/bases/netbox.dev_ipaddresses.yaml b/config/crd/bases/netbox.dev_ipaddresses.yaml index 80e60752..026dcab8 100644 --- a/config/crd/bases/netbox.dev_ipaddresses.yaml +++ b/config/crd/bases/netbox.dev_ipaddresses.yaml @@ -104,6 +104,23 @@ spec: recreated in Kubernetes) Field is mutable, not required type: boolean + tags: + description: |- + A list of tags that will be assigned to the resource in NetBox. + Each tag must define exactly one of the `name` or `slug` fields. + items: + properties: + name: + description: Name of the tag + type: string + slug: + description: Slug of the tag + type: string + type: object + x-kubernetes-validations: + - message: exactly one of name or slug must be specified + rule: has(self.name) != has(self.slug) + type: array tenant: description: |- The NetBox Tenant to be assigned to this resource in NetBox. Use the `name` value instead of the `slug` value diff --git a/config/crd/bases/netbox.dev_iprangeclaims.yaml b/config/crd/bases/netbox.dev_iprangeclaims.yaml index 248fa1cb..5696e7fe 100644 --- a/config/crd/bases/netbox.dev_iprangeclaims.yaml +++ b/config/crd/bases/netbox.dev_iprangeclaims.yaml @@ -118,6 +118,23 @@ spec: x-kubernetes-validations: - message: Field 'size' is immutable rule: self == oldSelf + tags: + description: |- + A list of tags that will be assigned to the NetBox resource. + Each tag must define exactly one of the `name` or `slug` fields. + items: + properties: + name: + description: Name of the tag + type: string + slug: + description: Slug of the tag + type: string + type: object + x-kubernetes-validations: + - message: exactly one of name or slug must be specified + rule: has(self.name) != has(self.slug) + type: array tenant: description: |- The NetBox Tenant to be assigned to this resource in NetBox. Use the `name` value instead of the `slug` value diff --git a/config/crd/bases/netbox.dev_ipranges.yaml b/config/crd/bases/netbox.dev_ipranges.yaml index c716bb15..f0510884 100644 --- a/config/crd/bases/netbox.dev_ipranges.yaml +++ b/config/crd/bases/netbox.dev_ipranges.yaml @@ -117,6 +117,23 @@ spec: x-kubernetes-validations: - message: Field 'startAddress' is immutable rule: self == oldSelf + tags: + description: |- + A list of tags that will be assigned to the resource in NetBox. + Each tag must define exactly one of the `name` or `slug` fields. + items: + properties: + name: + description: Name of the tag + type: string + slug: + description: Slug of the tag + type: string + type: object + x-kubernetes-validations: + - message: exactly one of name or slug must be specified + rule: has(self.name) != has(self.slug) + type: array tenant: description: |- The NetBox Tenant to be assigned to this resource in NetBox. Use the `name` value instead of the `slug` value diff --git a/config/crd/bases/netbox.dev_prefixclaims.yaml b/config/crd/bases/netbox.dev_prefixclaims.yaml index bf812179..c96e81c7 100644 --- a/config/crd/bases/netbox.dev_prefixclaims.yaml +++ b/config/crd/bases/netbox.dev_prefixclaims.yaml @@ -140,6 +140,23 @@ spec: x-kubernetes-validations: - message: Field 'site' is immutable rule: self == oldSelf + tags: + description: |- + A list of tags that will be assigned to the NetBox resource. + Each tag must define exactly one of the `name` or `slug` fields. + items: + properties: + name: + description: Name of the tag + type: string + slug: + description: Slug of the tag + type: string + type: object + x-kubernetes-validations: + - message: exactly one of name or slug must be specified + rule: has(self.name) != has(self.slug) + type: array tenant: description: |- The NetBox Tenant to be assigned to this resource in NetBox. Use the `name` value instead of the `slug` value diff --git a/config/crd/bases/netbox.dev_prefixes.yaml b/config/crd/bases/netbox.dev_prefixes.yaml index fba59720..e31b8e87 100644 --- a/config/crd/bases/netbox.dev_prefixes.yaml +++ b/config/crd/bases/netbox.dev_prefixes.yaml @@ -112,6 +112,26 @@ spec: x-kubernetes-validations: - message: Field 'site' is required once set rule: self == oldSelf || self != '' + tags: + description: |- + A list of tags that will be assigned to the resource in NetBox. + Each tag may contain either the `name` or `slug` field (one of them is required). + Example: + - name: tag1 + - slug: tag2 + items: + properties: + name: + description: Name of the tag + type: string + slug: + description: Slug of the tag + type: string + type: object + x-kubernetes-validations: + - message: exactly one of name or slug must be specified + rule: has(self.name) != has(self.slug) + type: array tenant: description: |- The NetBox Tenant to be assigned to this resource in NetBox. Use the `name` value instead of the `slug` value diff --git a/config/samples/netbox_v1_ipaddress.yaml b/config/samples/netbox_v1_ipaddress.yaml index 4937daee..a01f340c 100644 --- a/config/samples/netbox_v1_ipaddress.yaml +++ b/config/samples/netbox_v1_ipaddress.yaml @@ -11,4 +11,6 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: host-alpha ipAddress: "2.0.0.100/32" diff --git a/config/samples/netbox_v1_ipaddressclaim.yaml b/config/samples/netbox_v1_ipaddressclaim.yaml index 8bfdee0b..62112af7 100644 --- a/config/samples/netbox_v1_ipaddressclaim.yaml +++ b/config/samples/netbox_v1_ipaddressclaim.yaml @@ -11,4 +11,6 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: claim-alpha parentPrefix: "2.0.0.0/16" diff --git a/config/samples/netbox_v1_iprange.yaml b/config/samples/netbox_v1_iprange.yaml index 93ace170..1b837c57 100644 --- a/config/samples/netbox_v1_iprange.yaml +++ b/config/samples/netbox_v1_iprange.yaml @@ -11,5 +11,7 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: range-alpha startAddress: "2.0.0.200/32" endAddress: "2.0.0.202/32" diff --git a/config/samples/netbox_v1_iprangeclaim.yaml b/config/samples/netbox_v1_iprangeclaim.yaml index c6e477bd..7e54be4e 100644 --- a/config/samples/netbox_v1_iprangeclaim.yaml +++ b/config/samples/netbox_v1_iprangeclaim.yaml @@ -11,5 +11,7 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: range-claim-alpha parentPrefix: "2.0.0.0/16" size: 3 diff --git a/config/samples/netbox_v1_prefix.yaml b/config/samples/netbox_v1_prefix.yaml index 2deecff5..ec799fff 100644 --- a/config/samples/netbox_v1_prefix.yaml +++ b/config/samples/netbox_v1_prefix.yaml @@ -13,3 +13,7 @@ spec: comments: "your comments" preserveInNetbox: true prefix: "2.0.0.0/24" + tags: + - name: Alpha + - slug: golf + - name: Bravo diff --git a/config/samples/netbox_v1_prefixclaim.yaml b/config/samples/netbox_v1_prefixclaim.yaml index e8534426..72bbfb4f 100644 --- a/config/samples/netbox_v1_prefixclaim.yaml +++ b/config/samples/netbox_v1_prefixclaim.yaml @@ -12,5 +12,7 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: alpha parentPrefix: "2.0.0.0/16" prefixLength: "/28" diff --git a/config/samples/netbox_v1_prefixclaim_parentprefixselector.yaml b/config/samples/netbox_v1_prefixclaim_parentprefixselector.yaml index 34016354..4e0396d1 100644 --- a/config/samples/netbox_v1_prefixclaim_parentprefixselector.yaml +++ b/config/samples/netbox_v1_prefixclaim_parentprefixselector.yaml @@ -12,6 +12,8 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: selector-alpha prefixLength: "/31" parentPrefixSelector: tenant: "MY_TENANT" diff --git a/config/samples/netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml b/config/samples/netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml index 7ee833ca..2546b0fe 100644 --- a/config/samples/netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml +++ b/config/samples/netbox_v1_prefixclaim_parentprefixselector_bool_int.yaml @@ -12,6 +12,8 @@ spec: description: "some description" comments: "your comments" preserveInNetbox: true + tags: + - name: selector-bool prefixLength: "/31" parentPrefixSelector: # should return a prefix in 3.0.0.0/24 with the sample data diff --git a/gen/mock_interfaces/netbox_mocks.go b/gen/mock_interfaces/netbox_mocks.go index d49403aa..dc33eca6 100644 --- a/gen/mock_interfaces/netbox_mocks.go +++ b/gen/mock_interfaces/netbox_mocks.go @@ -5,7 +5,6 @@ // // mockgen -destination gen/mock_interfaces/netbox_mocks.go -source=pkg/netbox/interfaces/netbox.go // - // Package mock_interfaces is a generated GoMock package. package mock_interfaces @@ -24,7 +23,6 @@ import ( type MockIpamInterface struct { ctrl *gomock.Controller recorder *MockIpamInterfaceMockRecorder - isgomock struct{} } // MockIpamInterfaceMockRecorder is the mock recorder for MockIpamInterface. @@ -47,7 +45,7 @@ func (m *MockIpamInterface) EXPECT() *MockIpamInterfaceMockRecorder { // IpamIPAddressesCreate mocks base method. func (m *MockIpamInterface) IpamIPAddressesCreate(params *ipam.IpamIPAddressesCreateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesCreateCreated, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -58,16 +56,16 @@ func (m *MockIpamInterface) IpamIPAddressesCreate(params *ipam.IpamIPAddressesCr } // IpamIPAddressesCreate indicates an expected call of IpamIPAddressesCreate. -func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesCreate(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesCreate(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPAddressesCreate", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPAddressesCreate), varargs...) } // IpamIPAddressesDelete mocks base method. func (m *MockIpamInterface) IpamIPAddressesDelete(params *ipam.IpamIPAddressesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesDeleteNoContent, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -78,16 +76,16 @@ func (m *MockIpamInterface) IpamIPAddressesDelete(params *ipam.IpamIPAddressesDe } // IpamIPAddressesDelete indicates an expected call of IpamIPAddressesDelete. -func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesDelete(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesDelete(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPAddressesDelete", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPAddressesDelete), varargs...) } // IpamIPAddressesList mocks base method. func (m *MockIpamInterface) IpamIPAddressesList(params *ipam.IpamIPAddressesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -98,16 +96,16 @@ func (m *MockIpamInterface) IpamIPAddressesList(params *ipam.IpamIPAddressesList } // IpamIPAddressesList indicates an expected call of IpamIPAddressesList. -func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPAddressesList", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPAddressesList), varargs...) } // IpamIPAddressesUpdate mocks base method. func (m *MockIpamInterface) IpamIPAddressesUpdate(params *ipam.IpamIPAddressesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPAddressesUpdateOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -118,16 +116,16 @@ func (m *MockIpamInterface) IpamIPAddressesUpdate(params *ipam.IpamIPAddressesUp } // IpamIPAddressesUpdate indicates an expected call of IpamIPAddressesUpdate. -func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesUpdate(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPAddressesUpdate(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPAddressesUpdate", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPAddressesUpdate), varargs...) } // IpamIPRangesAvailableIpsList mocks base method. func (m *MockIpamInterface) IpamIPRangesAvailableIpsList(params *ipam.IpamIPRangesAvailableIpsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesAvailableIpsListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -138,16 +136,16 @@ func (m *MockIpamInterface) IpamIPRangesAvailableIpsList(params *ipam.IpamIPRang } // IpamIPRangesAvailableIpsList indicates an expected call of IpamIPRangesAvailableIpsList. -func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesAvailableIpsList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesAvailableIpsList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPRangesAvailableIpsList", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPRangesAvailableIpsList), varargs...) } // IpamIPRangesCreate mocks base method. func (m *MockIpamInterface) IpamIPRangesCreate(params *ipam.IpamIPRangesCreateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesCreateCreated, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -158,16 +156,16 @@ func (m *MockIpamInterface) IpamIPRangesCreate(params *ipam.IpamIPRangesCreatePa } // IpamIPRangesCreate indicates an expected call of IpamIPRangesCreate. -func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesCreate(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesCreate(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPRangesCreate", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPRangesCreate), varargs...) } // IpamIPRangesDelete mocks base method. func (m *MockIpamInterface) IpamIPRangesDelete(params *ipam.IpamIPRangesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesDeleteNoContent, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -178,16 +176,16 @@ func (m *MockIpamInterface) IpamIPRangesDelete(params *ipam.IpamIPRangesDeletePa } // IpamIPRangesDelete indicates an expected call of IpamIPRangesDelete. -func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesDelete(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesDelete(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPRangesDelete", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPRangesDelete), varargs...) } // IpamIPRangesList mocks base method. func (m *MockIpamInterface) IpamIPRangesList(params *ipam.IpamIPRangesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -198,16 +196,16 @@ func (m *MockIpamInterface) IpamIPRangesList(params *ipam.IpamIPRangesListParams } // IpamIPRangesList indicates an expected call of IpamIPRangesList. -func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPRangesList", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPRangesList), varargs...) } // IpamIPRangesUpdate mocks base method. func (m *MockIpamInterface) IpamIPRangesUpdate(params *ipam.IpamIPRangesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamIPRangesUpdateOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -218,16 +216,16 @@ func (m *MockIpamInterface) IpamIPRangesUpdate(params *ipam.IpamIPRangesUpdatePa } // IpamIPRangesUpdate indicates an expected call of IpamIPRangesUpdate. -func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesUpdate(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamIPRangesUpdate(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamIPRangesUpdate", reflect.TypeOf((*MockIpamInterface)(nil).IpamIPRangesUpdate), varargs...) } // IpamPrefixesAvailableIpsList mocks base method. func (m *MockIpamInterface) IpamPrefixesAvailableIpsList(params *ipam.IpamPrefixesAvailableIpsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesAvailableIpsListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -238,16 +236,16 @@ func (m *MockIpamInterface) IpamPrefixesAvailableIpsList(params *ipam.IpamPrefix } // IpamPrefixesAvailableIpsList indicates an expected call of IpamPrefixesAvailableIpsList. -func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesAvailableIpsList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesAvailableIpsList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesAvailableIpsList", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesAvailableIpsList), varargs...) } // IpamPrefixesAvailablePrefixesList mocks base method. func (m *MockIpamInterface) IpamPrefixesAvailablePrefixesList(params *ipam.IpamPrefixesAvailablePrefixesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesAvailablePrefixesListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -258,16 +256,16 @@ func (m *MockIpamInterface) IpamPrefixesAvailablePrefixesList(params *ipam.IpamP } // IpamPrefixesAvailablePrefixesList indicates an expected call of IpamPrefixesAvailablePrefixesList. -func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesAvailablePrefixesList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesAvailablePrefixesList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesAvailablePrefixesList", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesAvailablePrefixesList), varargs...) } // IpamPrefixesCreate mocks base method. func (m *MockIpamInterface) IpamPrefixesCreate(params *ipam.IpamPrefixesCreateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesCreateCreated, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -278,16 +276,16 @@ func (m *MockIpamInterface) IpamPrefixesCreate(params *ipam.IpamPrefixesCreatePa } // IpamPrefixesCreate indicates an expected call of IpamPrefixesCreate. -func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesCreate(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesCreate(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesCreate", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesCreate), varargs...) } // IpamPrefixesDelete mocks base method. func (m *MockIpamInterface) IpamPrefixesDelete(params *ipam.IpamPrefixesDeleteParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesDeleteNoContent, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -298,16 +296,16 @@ func (m *MockIpamInterface) IpamPrefixesDelete(params *ipam.IpamPrefixesDeletePa } // IpamPrefixesDelete indicates an expected call of IpamPrefixesDelete. -func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesDelete(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesDelete(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesDelete", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesDelete), varargs...) } // IpamPrefixesList mocks base method. func (m *MockIpamInterface) IpamPrefixesList(params *ipam.IpamPrefixesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -318,16 +316,16 @@ func (m *MockIpamInterface) IpamPrefixesList(params *ipam.IpamPrefixesListParams } // IpamPrefixesList indicates an expected call of IpamPrefixesList. -func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesList", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesList), varargs...) } // IpamPrefixesUpdate mocks base method. func (m *MockIpamInterface) IpamPrefixesUpdate(params *ipam.IpamPrefixesUpdateParams, authInfo runtime.ClientAuthInfoWriter, opts ...ipam.ClientOption) (*ipam.IpamPrefixesUpdateOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -338,9 +336,9 @@ func (m *MockIpamInterface) IpamPrefixesUpdate(params *ipam.IpamPrefixesUpdatePa } // IpamPrefixesUpdate indicates an expected call of IpamPrefixesUpdate. -func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesUpdate(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesUpdate(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IpamPrefixesUpdate", reflect.TypeOf((*MockIpamInterface)(nil).IpamPrefixesUpdate), varargs...) } @@ -348,7 +346,6 @@ func (mr *MockIpamInterfaceMockRecorder) IpamPrefixesUpdate(params, authInfo any type MockTenancyInterface struct { ctrl *gomock.Controller recorder *MockTenancyInterfaceMockRecorder - isgomock struct{} } // MockTenancyInterfaceMockRecorder is the mock recorder for MockTenancyInterface. @@ -371,7 +368,7 @@ func (m *MockTenancyInterface) EXPECT() *MockTenancyInterfaceMockRecorder { // TenancyTenantsList mocks base method. func (m *MockTenancyInterface) TenancyTenantsList(params *tenancy.TenancyTenantsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...tenancy.ClientOption) (*tenancy.TenancyTenantsListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -382,9 +379,9 @@ func (m *MockTenancyInterface) TenancyTenantsList(params *tenancy.TenancyTenants } // TenancyTenantsList indicates an expected call of TenancyTenantsList. -func (mr *MockTenancyInterfaceMockRecorder) TenancyTenantsList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockTenancyInterfaceMockRecorder) TenancyTenantsList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TenancyTenantsList", reflect.TypeOf((*MockTenancyInterface)(nil).TenancyTenantsList), varargs...) } @@ -392,7 +389,6 @@ func (mr *MockTenancyInterfaceMockRecorder) TenancyTenantsList(params, authInfo type MockExtrasInterface struct { ctrl *gomock.Controller recorder *MockExtrasInterfaceMockRecorder - isgomock struct{} } // MockExtrasInterfaceMockRecorder is the mock recorder for MockExtrasInterface. @@ -415,7 +411,7 @@ func (m *MockExtrasInterface) EXPECT() *MockExtrasInterfaceMockRecorder { // ExtrasCustomFieldsList mocks base method. func (m *MockExtrasInterface) ExtrasCustomFieldsList(params *extras.ExtrasCustomFieldsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...extras.ClientOption) (*extras.ExtrasCustomFieldsListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -426,17 +422,36 @@ func (m *MockExtrasInterface) ExtrasCustomFieldsList(params *extras.ExtrasCustom } // ExtrasCustomFieldsList indicates an expected call of ExtrasCustomFieldsList. -func (mr *MockExtrasInterfaceMockRecorder) ExtrasCustomFieldsList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockExtrasInterfaceMockRecorder) ExtrasCustomFieldsList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtrasCustomFieldsList", reflect.TypeOf((*MockExtrasInterface)(nil).ExtrasCustomFieldsList), varargs...) } +// ExtrasTagsList mocks base method. +func (m *MockExtrasInterface) ExtrasTagsList(params *extras.ExtrasTagsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...extras.ClientOption) (*extras.ExtrasTagsListOK, error) { + m.ctrl.T.Helper() + varargs := []interface{}{params, authInfo} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ExtrasTagsList", varargs...) + ret0, _ := ret[0].(*extras.ExtrasTagsListOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExtrasTagsList indicates an expected call of ExtrasTagsList. +func (mr *MockExtrasInterfaceMockRecorder) ExtrasTagsList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{params, authInfo}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtrasTagsList", reflect.TypeOf((*MockExtrasInterface)(nil).ExtrasTagsList), varargs...) +} + // MockDcimInterface is a mock of DcimInterface interface. type MockDcimInterface struct { ctrl *gomock.Controller recorder *MockDcimInterfaceMockRecorder - isgomock struct{} } // MockDcimInterfaceMockRecorder is the mock recorder for MockDcimInterface. @@ -459,7 +474,7 @@ func (m *MockDcimInterface) EXPECT() *MockDcimInterfaceMockRecorder { // DcimSitesList mocks base method. func (m *MockDcimInterface) DcimSitesList(params *dcim.DcimSitesListParams, authInfo runtime.ClientAuthInfoWriter, opts ...dcim.ClientOption) (*dcim.DcimSitesListOK, error) { m.ctrl.T.Helper() - varargs := []any{params, authInfo} + varargs := []interface{}{params, authInfo} for _, a := range opts { varargs = append(varargs, a) } @@ -470,8 +485,8 @@ func (m *MockDcimInterface) DcimSitesList(params *dcim.DcimSitesListParams, auth } // DcimSitesList indicates an expected call of DcimSitesList. -func (mr *MockDcimInterfaceMockRecorder) DcimSitesList(params, authInfo any, opts ...any) *gomock.Call { +func (mr *MockDcimInterfaceMockRecorder) DcimSitesList(params, authInfo interface{}, opts ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]any{params, authInfo}, opts...) + varargs := append([]interface{}{params, authInfo}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DcimSitesList", reflect.TypeOf((*MockDcimInterface)(nil).DcimSitesList), varargs...) } diff --git a/internal/controller/ipaddress_controller.go b/internal/controller/ipaddress_controller.go index 03f032d4..3bc6d049 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, + Tags: convertAPITagsToModelTags(spec.Tags), }, }, nil } diff --git a/internal/controller/ipaddressclaim_controller.go b/internal/controller/ipaddressclaim_controller.go index 2045efe4..8c2ba0ab 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, + Tags: convertAPITagsToModelTags(o.Spec.Tags), }, }) if err != nil { @@ -192,6 +193,7 @@ func (r *IpAddressClaimReconciler) Reconcile(ctx context.Context, req ctrl.Reque ipAddress.Spec.Comments = updatedIpAddressSpec.Comments ipAddress.Spec.Description = updatedIpAddressSpec.Description ipAddress.Spec.PreserveInNetbox = updatedIpAddressSpec.PreserveInNetbox + ipAddress.Spec.Tags = updatedIpAddressSpec.Tags err = controllerutil.SetControllerReference(o, ipAddress, r.Scheme) if err != nil { return err diff --git a/internal/controller/ipaddressclaim_helpers.go b/internal/controller/ipaddressclaim_helpers.go index 0552d9ac..4727eb25 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, + Tags: cloneAPITags(claim.Spec.Tags), CustomFields: customFields, Description: claim.Spec.Description, Comments: claim.Spec.Comments, diff --git a/internal/controller/iprange_controller.go b/internal/controller/iprange_controller.go index 916566da..fb8a66da 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, + Tags: convertAPITagsToModelTags(o.Spec.Tags), }, }, nil } diff --git a/internal/controller/iprangeclaim_controller.go b/internal/controller/iprangeclaim_controller.go index 5827ccdf..052e328a 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, + Tags: convertAPITagsToModelTags(o.Spec.Tags), }, }, ) diff --git a/internal/controller/iprangeclaim_helpers.go b/internal/controller/iprangeclaim_helpers.go index a05e3c3f..ab3151b9 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, + Tags: cloneAPITags(claim.Spec.Tags), CustomFields: customFields, Description: claim.Spec.Description, Comments: claim.Spec.Comments, diff --git a/internal/controller/netbox_testdata_test.go b/internal/controller/netbox_testdata_test.go index 0bed0e22..b8448ba0 100644 --- a/internal/controller/netbox_testdata_test.go +++ b/internal/controller/netbox_testdata_test.go @@ -248,6 +248,7 @@ var expectedIpToUpdate = &netboxModels.WritableIPAddress{ }, Description: nsn + description + warningComment, Status: "active", + Tags: []*netboxModels.NestedTag{}, Tenant: &tenantId} var expectedIpToUpdateWithHash = &netboxModels.WritableIPAddress{ @@ -259,6 +260,7 @@ var expectedIpToUpdateWithHash = &netboxModels.WritableIPAddress{ }, Description: nsn + description + warningComment, Status: "active", + Tags: []*netboxModels.NestedTag{}, Tenant: &tenantId} var ExpectedIpAddressUpdateParams = ipam.NewIpamIPAddressesUpdateParams().WithDefaults(). diff --git a/internal/controller/prefix_controller.go b/internal/controller/prefix_controller.go index 2e35d6bc..4ab5d4f6 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, + Tags: convertAPITagsToModelTags(spec.Tags), }, }, nil } diff --git a/internal/controller/prefixclaim_controller.go b/internal/controller/prefixclaim_controller.go index b4200407..f5fb8e5e 100644 --- a/internal/controller/prefixclaim_controller.go +++ b/internal/controller/prefixclaim_controller.go @@ -318,6 +318,7 @@ func (r *PrefixClaimReconciler) Reconcile(ctx context.Context, req ctrl.Request) prefix.Spec.Description = updatedPrefixSpec.Description prefix.Spec.Comments = updatedPrefixSpec.Comments prefix.Spec.PreserveInNetbox = updatedPrefixSpec.PreserveInNetbox + prefix.Spec.Tags = updatedPrefixSpec.Tags err = controllerutil.SetControllerReference(o, prefix, r.Scheme) if err != nil { return err diff --git a/internal/controller/prefixclaim_helpers.go b/internal/controller/prefixclaim_helpers.go index e53cbfaa..67959b36 100644 --- a/internal/controller/prefixclaim_helpers.go +++ b/internal/controller/prefixclaim_helpers.go @@ -56,6 +56,7 @@ func generatePrefixSpec(claim *netboxv1.PrefixClaim, prefix string, logger logr. Prefix: prefix, Tenant: claim.Spec.Tenant, Site: claim.Spec.Site, + Tags: cloneAPITags(claim.Spec.Tags), CustomFields: customFields, Description: claim.Spec.Description, Comments: claim.Spec.Comments, diff --git a/internal/controller/tag_helpers.go b/internal/controller/tag_helpers.go new file mode 100644 index 00000000..8bab46e8 --- /dev/null +++ b/internal/controller/tag_helpers.go @@ -0,0 +1,49 @@ +/* +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 controller + +import ( + netboxv1 "github.com/netbox-community/netbox-operator/api/v1" + "github.com/netbox-community/netbox-operator/pkg/netbox/models" +) + +func convertAPITagsToModelTags(tags []netboxv1.Tag) []models.Tag { + if len(tags) == 0 { + return nil + } + + converted := make([]models.Tag, len(tags)) + for i, tag := range tags { + converted[i] = models.Tag{ + Name: tag.Name, + Slug: tag.Slug, + } + } + + return converted +} + +func cloneAPITags(tags []netboxv1.Tag) []netboxv1.Tag { + if len(tags) == 0 { + return nil + } + + cloned := make([]netboxv1.Tag, len(tags)) + copy(cloned, tags) + + return cloned +} diff --git a/internal/controller/tag_helpers_test.go b/internal/controller/tag_helpers_test.go new file mode 100644 index 00000000..c614b52a --- /dev/null +++ b/internal/controller/tag_helpers_test.go @@ -0,0 +1,44 @@ +/* +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 controller + +import ( + "testing" + + netboxv1 "github.com/netbox-community/netbox-operator/api/v1" + "github.com/stretchr/testify/assert" +) + +func TestConvertAPITagsToModelTags(t *testing.T) { + apiTags := []netboxv1.Tag{{Name: "first"}, {Slug: "second"}} + modelTags := convertAPITagsToModelTags(apiTags) + + assert.Len(t, modelTags, 2) + assert.Equal(t, "first", modelTags[0].Name) + assert.Equal(t, "second", modelTags[1].Slug) +} + +func TestCloneAPITags(t *testing.T) { + original := []netboxv1.Tag{{Name: "original"}} + cloned := cloneAPITags(original) + + assert.Len(t, cloned, 1) + cloned[0].Name = "updated" + assert.Equal(t, "original", original[0].Name) + + assert.Nil(t, cloneAPITags(nil)) +} diff --git a/pkg/netbox/api/ip_address.go b/pkg/netbox/api/ip_address.go index 3d13666b..b0d98412 100644 --- a/pkg/netbox/api/ip_address.go +++ b/pkg/netbox/api/ip_address.go @@ -54,6 +54,15 @@ func (r *NetboxClient) ReserveOrUpdateIpAddress(ipAddress *models.IPAddress) (*n desiredIPAddress.Tenant = &tenantDetails.Id } + desiredIPAddress.Tags = []*netboxModels.NestedTag{} + if ipAddress.Metadata != nil { + var err error + desiredIPAddress.Tags, err = r.buildWritableTags(ipAddress.Metadata.Tags) + if err != nil { + return nil, err + } + } + // 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_range.go b/pkg/netbox/api/ip_range.go index 7d7cf9b4..8bf7cdb2 100644 --- a/pkg/netbox/api/ip_range.go +++ b/pkg/netbox/api/ip_range.go @@ -57,6 +57,15 @@ func (r *NetboxClient) ReserveOrUpdateIpRange(ipRange *models.IpRange) (*netboxM desiredIpRange.Tenant = &tenantDetails.Id } + desiredIpRange.Tags = []*netboxModels.NestedTag{} + if ipRange.Metadata != nil { + var err error + desiredIpRange.Tags, err = r.buildWritableTags(ipRange.Metadata.Tags) + if err != nil { + return nil, err + } + } + // create ip range since it doesn't exist if len(responseIpRange.Payload.Results) == 0 { return r.CreateIpRange(desiredIpRange) diff --git a/pkg/netbox/api/prefix.go b/pkg/netbox/api/prefix.go index 1c15830a..f7344d92 100644 --- a/pkg/netbox/api/prefix.go +++ b/pkg/netbox/api/prefix.go @@ -67,6 +67,15 @@ func (r *NetboxClient) ReserveOrUpdatePrefix(prefix *models.Prefix) (*netboxMode desiredPrefix.Site = &siteDetails.Id } + desiredPrefix.Tags = []*netboxModels.NestedTag{} + if prefix.Metadata != nil { + var err error + desiredPrefix.Tags, err = r.buildWritableTags(prefix.Metadata.Tags) + if err != nil { + return nil, err + } + } + // create prefix since it doesn't exist if len(responsePrefix.Payload.Results) == 0 { return r.CreatePrefix(desiredPrefix) diff --git a/pkg/netbox/api/prefix_test.go b/pkg/netbox/api/prefix_test.go index bfd4a838..82205721 100644 --- a/pkg/netbox/api/prefix_test.go +++ b/pkg/netbox/api/prefix_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/netbox-community/go-netbox/v3/netbox/client/dcim" + "github.com/netbox-community/go-netbox/v3/netbox/client/extras" "github.com/netbox-community/go-netbox/v3/netbox/client/ipam" "github.com/netbox-community/go-netbox/v3/netbox/client/tenancy" netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" @@ -376,12 +377,13 @@ func TestPrefix_ReserveOrUpdate(t *testing.T) { }, } - t.Run("reserve with tenant and site", func(t *testing.T) { + t.Run("reserve with tenant, site, and tags", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) mockDcim := mock_interfaces.NewMockDcimInterface(ctrl) + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) //prefix mock output createPrefixOutput := &ipam.IpamPrefixesCreateCreated{ @@ -400,16 +402,37 @@ func TestPrefix_ReserveOrUpdate(t *testing.T) { }, } - mockTenancy.EXPECT().TenancyTenantsList(tenantListRequestInput, nil).Return(tenantListRequestOutput, nil).AnyTimes() - mockDcim.EXPECT().DcimSitesList(siteListRequestInput, nil).Return(siteListRequestOutput, nil).AnyTimes() + tagByName := "tag-by-name" + tagBySlug := "tag-by-slug" + resolvedTagByName := &netboxModels.Tag{ID: 1, Name: &tagByName, Slug: &tagBySlug} + resolvedTagBySlug := &netboxModels.Tag{ID: 2, Name: &tagByName, Slug: &tagBySlug} + + nameRequest := extras.NewExtrasTagsListParams().WithName(&tagByName) + slugRequest := extras.NewExtrasTagsListParams().WithSlug(&tagBySlug) + + gomock.InOrder( + mockTenancy.EXPECT().TenancyTenantsList(tenantListRequestInput, nil).Return(tenantListRequestOutput, nil).AnyTimes(), + mockDcim.EXPECT().DcimSitesList(siteListRequestInput, nil).Return(siteListRequestOutput, nil).AnyTimes(), + mockExtras.EXPECT().ExtrasTagsList(nameRequest, nil).Return(&extras.ExtrasTagsListOK{Payload: &extras.ExtrasTagsListOKBody{Results: []*netboxModels.Tag{resolvedTagByName}}}, nil), + mockExtras.EXPECT().ExtrasTagsList(slugRequest, nil).Return(&extras.ExtrasTagsListOK{Payload: &extras.ExtrasTagsListOKBody{Results: []*netboxModels.Tag{resolvedTagBySlug}}}, nil), + ) + mockIpam.EXPECT().IpamPrefixesList(prefixListRequestInput, nil).Return(emptyPrefixListOutput, nil) - // use go mock Any as the input parameter contains pointers - mockIpam.EXPECT().IpamPrefixesCreate(gomock.Any(), nil).Return(createPrefixOutput, nil) + mockIpam.EXPECT().IpamPrefixesCreate(gomock.AssignableToTypeOf(&ipam.IpamPrefixesCreateParams{}), nil).DoAndReturn( + func(params *ipam.IpamPrefixesCreateParams, _ interface{}, _ ...ipam.ClientOption) (*ipam.IpamPrefixesCreateCreated, error) { + if assert.Len(t, params.Data.Tags, 2) { + assert.Equal(t, resolvedTagByName.ID, params.Data.Tags[0].ID) + assert.Equal(t, resolvedTagBySlug.ID, params.Data.Tags[1].ID) + } + return createPrefixOutput, nil + }, + ) netboxClient := &NetboxClient{ Ipam: mockIpam, Tenancy: mockTenancy, Dcim: mockDcim, + Extras: mockExtras, } prefixModel := models.Prefix{ @@ -420,6 +443,10 @@ func TestPrefix_ReserveOrUpdate(t *testing.T) { Site: site, Custom: make(map[string]string), Tenant: tenant, + Tags: []models.Tag{ + {Name: tagByName}, + {Slug: tagBySlug}, + }, }, } @@ -429,11 +456,12 @@ func TestPrefix_ReserveOrUpdate(t *testing.T) { assert.Nil(t, err) }) - t.Run("update without tenant and site", func(t *testing.T) { + t.Run("update without tenant and site updates tags", func(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockIpam := mock_interfaces.NewMockIpamInterface(ctrl) mockTenancy := mock_interfaces.NewMockTenancyInterface(ctrl) + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) prefixListOutput := &ipam.IpamPrefixesListOK{ Payload: &ipam.IpamPrefixesListOKBody{ @@ -461,11 +489,25 @@ func TestPrefix_ReserveOrUpdate(t *testing.T) { } mockIpam.EXPECT().IpamPrefixesList(prefixListRequestInput, nil).Return(prefixListOutput, nil) - mockIpam.EXPECT().IpamPrefixesUpdate(gomock.Any(), nil).Return(updatePrefixOutput, nil) + + updatedTagName := "updated-tag" + updatedTagSlug := "updated-tag" + nameRequest := extras.NewExtrasTagsListParams().WithName(&updatedTagName) + mockExtras.EXPECT().ExtrasTagsList(nameRequest, nil).Return(&extras.ExtrasTagsListOK{Payload: &extras.ExtrasTagsListOKBody{Results: []*netboxModels.Tag{{ID: 3, Name: &updatedTagName, Slug: &updatedTagSlug}}}}, nil) + + mockIpam.EXPECT().IpamPrefixesUpdate(gomock.AssignableToTypeOf(&ipam.IpamPrefixesUpdateParams{}), nil).DoAndReturn( + func(params *ipam.IpamPrefixesUpdateParams, _ interface{}, _ ...ipam.ClientOption) (*ipam.IpamPrefixesUpdateOK, error) { + if assert.Len(t, params.Data.Tags, 1) { + assert.Equal(t, int64(3), params.Data.Tags[0].ID) + } + return updatePrefixOutput, nil + }, + ) netboxClient := &NetboxClient{ Ipam: mockIpam, Tenancy: mockTenancy, + Extras: mockExtras, } prefixModel := models.Prefix{ @@ -473,6 +515,7 @@ func TestPrefix_ReserveOrUpdate(t *testing.T) { Metadata: &models.NetboxMetadata{ Comments: comments, Description: description, + Tags: []models.Tag{{Name: updatedTagName}}, }, } diff --git a/pkg/netbox/api/tags.go b/pkg/netbox/api/tags.go new file mode 100644 index 00000000..c333889d --- /dev/null +++ b/pkg/netbox/api/tags.go @@ -0,0 +1,68 @@ +/* +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/extras" + netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" + + "github.com/netbox-community/netbox-operator/pkg/netbox/models" + "github.com/netbox-community/netbox-operator/pkg/netbox/utils" +) + +func (r *NetboxClient) GetTagDetails(name string, slug string) (*models.Tag, error) { + var request *extras.ExtrasTagsListParams + if name != "" { + request = extras.NewExtrasTagsListParams().WithName(&name) + } + if slug != "" { + request = extras.NewExtrasTagsListParams().WithSlug(&slug) + } + + if name == "" && slug == "" { + return nil, utils.NetboxError("either name or slug must be provided to fetch Tag details", nil) + } + // response, err := r.Tags.ExtrasTagsList(request, nil) + response, err := r.Extras.ExtrasTagsList(request, nil) + if err != nil { + return nil, utils.NetboxError("failed to fetch Tag details", err) + } + + if len(response.Payload.Results) == 0 { + return nil, utils.NetboxNotFoundError("tag '" + name + "/" + slug + "'") + } + + tag := response.Payload.Results[0] + return &models.Tag{ + Id: tag.ID, + Name: *tag.Name, + Slug: *tag.Slug, + }, nil + +} + +func (r *NetboxClient) buildWritableTags(tags []models.Tag) ([]*netboxModels.NestedTag, error) { + nestedTags := make([]*netboxModels.NestedTag, 0, len(tags)) + for _, tag := range tags { + tagDetails, err := r.GetTagDetails(tag.Name, tag.Slug) + if err != nil { + return nil, err + } + nestedTags = append(nestedTags, &netboxModels.NestedTag{ID: tagDetails.Id, Name: &tagDetails.Name, Slug: &tagDetails.Slug}) + } + return nestedTags, nil +} diff --git a/pkg/netbox/api/tags_test.go b/pkg/netbox/api/tags_test.go new file mode 100644 index 00000000..01255aa5 --- /dev/null +++ b/pkg/netbox/api/tags_test.go @@ -0,0 +1,203 @@ +/* +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/go-netbox/v3/netbox/client/extras" + netboxModels "github.com/netbox-community/go-netbox/v3/netbox/models" + "github.com/netbox-community/netbox-operator/gen/mock_interfaces" + "github.com/netbox-community/netbox-operator/pkg/netbox/models" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestTags_GetTagDetailsByName(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + + tagName := "myTag" + tagSlug := "mytag" + tagId := int64(1) + + tagListRequestInput := extras.NewExtrasTagsListParams().WithName(&tagName) + tagListOutput := &extras.ExtrasTagsListOK{ + Payload: &extras.ExtrasTagsListOKBody{ + Results: []*netboxModels.Tag{ + { + ID: tagId, + Name: &tagName, + Slug: &tagSlug, + }, + }, + }, + } + + mockExtras.EXPECT().ExtrasTagsList(tagListRequestInput, nil).Return(tagListOutput, nil) + netboxClient := &NetboxClient{Extras: mockExtras} + + actual, err := netboxClient.GetTagDetails(tagName, "") + assert.NoError(t, err) + assert.Equal(t, tagName, actual.Name) + assert.Equal(t, tagId, actual.Id) + assert.Equal(t, tagSlug, actual.Slug) +} + +func TestTags_GetTagDetailsBySlug(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + + tagName := "myTag" + tagSlug := "mytag" + tagId := int64(1) + + tagListRequestInput := extras.NewExtrasTagsListParams().WithSlug(&tagSlug) + tagListOutput := &extras.ExtrasTagsListOK{ + Payload: &extras.ExtrasTagsListOKBody{ + Results: []*netboxModels.Tag{ + { + ID: tagId, + Name: &tagName, + Slug: &tagSlug, + }, + }, + }, + } + + mockExtras.EXPECT().ExtrasTagsList(tagListRequestInput, nil).Return(tagListOutput, nil) + netboxClient := &NetboxClient{Extras: mockExtras} + + actual, err := netboxClient.GetTagDetails("", tagSlug) + assert.NoError(t, err) + assert.Equal(t, tagName, actual.Name) + assert.Equal(t, tagId, actual.Id) + assert.Equal(t, tagSlug, actual.Slug) +} + +func TestTags_GetTagDetailsNotFound(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + + tagName := "notfound" + tagListRequestInput := extras.NewExtrasTagsListParams().WithName(&tagName) + tagListOutput := &extras.ExtrasTagsListOK{ + Payload: &extras.ExtrasTagsListOKBody{ + Results: []*netboxModels.Tag{}, + }, + } + + mockExtras.EXPECT().ExtrasTagsList(tagListRequestInput, nil).Return(tagListOutput, nil) + netboxClient := &NetboxClient{Extras: mockExtras} + + actual, err := netboxClient.GetTagDetails(tagName, "") + assert.Nil(t, actual) + assert.EqualError(t, err, "failed to fetch tag 'notfound/': not found") +} + +func TestTags_GetTagDetailsError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + + tagName := "error" + tagListRequestInput := extras.NewExtrasTagsListParams().WithName(&tagName) + + mockExtras.EXPECT().ExtrasTagsList(tagListRequestInput, nil).Return(nil, errors.New("some error")) + netboxClient := &NetboxClient{Extras: mockExtras} + + actual, err := netboxClient.GetTagDetails(tagName, "") + assert.Nil(t, actual) + assert.Contains(t, err.Error(), "failed to fetch Tag details") +} + +func TestTags_GetTagDetailsNoInput(t *testing.T) { + netboxClient := &NetboxClient{} + actual, err := netboxClient.GetTagDetails("", "") + assert.Nil(t, actual) + assert.Contains(t, err.Error(), "either name or slug must be provided") +} + +func TestTags_BuildWritableTags(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + + tagName := "tag-by-name" + tagSlug := "tag-by-slug" + tagID := int64(1) + + nameRequest := extras.NewExtrasTagsListParams().WithName(&tagName) + nameResponse := &extras.ExtrasTagsListOK{ + Payload: &extras.ExtrasTagsListOKBody{ + Results: []*netboxModels.Tag{{ + ID: tagID, + Name: &tagName, + Slug: &tagSlug, + }}, + }, + } + + slugRequest := extras.NewExtrasTagsListParams().WithSlug(&tagSlug) + slugResponse := &extras.ExtrasTagsListOK{ + Payload: &extras.ExtrasTagsListOKBody{ + Results: []*netboxModels.Tag{{ + ID: tagID + 1, + Name: &tagName, + Slug: &tagSlug, + }}, + }, + } + + gomock.InOrder( + mockExtras.EXPECT().ExtrasTagsList(nameRequest, nil).Return(nameResponse, nil), + mockExtras.EXPECT().ExtrasTagsList(slugRequest, nil).Return(slugResponse, nil), + ) + + client := &NetboxClient{Extras: mockExtras} + + result, err := client.buildWritableTags([]models.Tag{{Name: tagName}, {Slug: tagSlug}}) + assert.NoError(t, err) + assert.Len(t, result, 2) + assert.Equal(t, tagID, result[0].ID) + assert.Equal(t, tagName, *result[0].Name) + assert.Equal(t, tagSlug, *result[0].Slug) + assert.Equal(t, tagID+1, result[1].ID) + assert.Equal(t, tagName, *result[1].Name) + assert.Equal(t, tagSlug, *result[1].Slug) +} + +func TestTags_BuildWritableTagsError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockExtras := mock_interfaces.NewMockExtrasInterface(ctrl) + + tagName := "tag-error" + nameRequest := extras.NewExtrasTagsListParams().WithName(&tagName) + + mockExtras.EXPECT().ExtrasTagsList(nameRequest, nil).Return(nil, errors.New("boom")) + + client := &NetboxClient{Extras: mockExtras} + + result, err := client.buildWritableTags([]models.Tag{{Name: tagName}}) + assert.Nil(t, result) + assert.Error(t, err) +} diff --git a/pkg/netbox/interfaces/netbox.go b/pkg/netbox/interfaces/netbox.go index 852891fb..0c7f4c79 100644 --- a/pkg/netbox/interfaces/netbox.go +++ b/pkg/netbox/interfaces/netbox.go @@ -50,6 +50,7 @@ type TenancyInterface interface { type ExtrasInterface interface { ExtrasCustomFieldsList(params *extras.ExtrasCustomFieldsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...extras.ClientOption) (*extras.ExtrasCustomFieldsListOK, error) + ExtrasTagsList(params *extras.ExtrasTagsListParams, authInfo runtime.ClientAuthInfoWriter, opts ...extras.ClientOption) (*extras.ExtrasTagsListOK, error) } type DcimInterface interface { diff --git a/pkg/netbox/models/ipam.go b/pkg/netbox/models/ipam.go index bfe40304..e35ac4f8 100644 --- a/pkg/netbox/models/ipam.go +++ b/pkg/netbox/models/ipam.go @@ -22,6 +22,12 @@ type Tenant struct { Slug string `json:"slug,omitempty"` } +type Tag struct { + Id int64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Slug string `json:"slug,omitempty"` +} + type Site struct { Id int64 `json:"id,omitempty"` Name string `json:"name,omitempty"` @@ -35,6 +41,7 @@ type NetboxMetadata struct { Region string `json:"region,omitempty"` Site string `json:"site,omitempty"` Tenant string `json:"tenant,omitempty"` + Tags []Tag `json:"tags,omitempty"` } type IPAddress struct {