Merge pull request #2 from vegardengen/feature/create-functionality-to-create-a-unifi-network-object-based-on-an-annotation
Feature/create functionality to create a unifi network object
This commit is contained in:
89
api/v1beta1/firewallgroup_types.go
Normal file
89
api/v1beta1/firewallgroup_types.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Vegard Engen.
|
||||||
|
|
||||||
|
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 v1beta1
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||||
|
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||||
|
|
||||||
|
// FirewallGroupSpec defines the desired state of FirewallGroup.
|
||||||
|
type FirewallGroupSpec struct {
|
||||||
|
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||||
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
|
|
||||||
|
// Foo is an example field of FirewallGroup. Edit firewallgroup_types.go to remove/update
|
||||||
|
// Description is a human-readable explanation for the object
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
|
||||||
|
MatchServicesInAllNamespaces bool `json:"matchServicesInAllNamespaces,omitempty"`
|
||||||
|
// ManualAddresses is a list of manual IPs or CIDRs (IPv4 or IPv6)
|
||||||
|
// +optional
|
||||||
|
ManualAddresses []string `json:"manualAddresses,omitempty"`
|
||||||
|
|
||||||
|
// AutoIncludeSelector defines which services to extract addresses from
|
||||||
|
// +optional
|
||||||
|
AutoIncludeSelector *metav1.LabelSelector `json:"autoIncludeSelector,omitempty"`
|
||||||
|
|
||||||
|
// AddressType can be "ip", "cidr", or "both"
|
||||||
|
// +kubebuilder:validation:Enum=ip;cidr;both
|
||||||
|
// +optional
|
||||||
|
AddressType string `json:"addressType,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirewallGroupStatus defines the observed state of FirewallGroup.
|
||||||
|
type FirewallGroupStatus struct {
|
||||||
|
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||||
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
|
|
||||||
|
ResolvedAddresses []string `json:"resolvedAddresses,omitempty"`
|
||||||
|
|
||||||
|
// SyncedWithUnifi indicates whether the addresses are successfully pushed
|
||||||
|
// +optional
|
||||||
|
SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"`
|
||||||
|
|
||||||
|
// LastSyncTime is the last time the object was synced
|
||||||
|
// +optional
|
||||||
|
LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
// +kubebuilder:subresource:status
|
||||||
|
|
||||||
|
// FirewallGroup is the Schema for the firewallgroups API.
|
||||||
|
type FirewallGroup struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec FirewallGroupSpec `json:"spec,omitempty"`
|
||||||
|
Status FirewallGroupStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:object:root=true
|
||||||
|
|
||||||
|
// FirewallGroupList contains a list of FirewallGroup.
|
||||||
|
type FirewallGroupList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
Items []FirewallGroup `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SchemeBuilder.Register(&FirewallGroup{}, &FirewallGroupList{})
|
||||||
|
}
|
||||||
@@ -29,31 +29,30 @@ type NetworkconfigurationSpec struct {
|
|||||||
// Important: Run "make" to regenerate code after modifying this file
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
|
|
||||||
// Foo is an example field of Networkconfiguration. Edit networkconfiguration_types.go to remove/update
|
// Foo is an example field of Networkconfiguration. Edit networkconfiguration_types.go to remove/update
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
FirewallZoneID string `json:"firewall_zone_id,omitempty"`
|
FirewallZoneID string `json:"firewall_zone_id,omitempty"`
|
||||||
GatewayType string `json:"gateway_type,omitempty"`
|
GatewayType string `json:"gateway_type,omitempty"`
|
||||||
IPSubnet string `json:"ip_subnet,omitempty"`
|
IPSubnet string `json:"ip_subnet,omitempty"`
|
||||||
Ipv6InterfaceType string `json:"ipv6_interface_type,omitempty"`
|
Ipv6InterfaceType string `json:"ipv6_interface_type,omitempty"`
|
||||||
Ipv6PdAutoPrefixidEnabled bool `json:"ipv6_pd_auto_prefixid_enabled,omitempty"`
|
Ipv6PdAutoPrefixidEnabled bool `json:"ipv6_pd_auto_prefixid_enabled,omitempty"`
|
||||||
Ipv6RaEnabled bool `json:"ipv6_ra_enabled,omitempty"`
|
Ipv6RaEnabled bool `json:"ipv6_ra_enabled,omitempty"`
|
||||||
Ipv6SettingPreference string `json:"ipv6_setting_preference,omitempty"`
|
Ipv6SettingPreference string `json:"ipv6_setting_preference,omitempty"`
|
||||||
Ipv6Subnet string `json:"ipv6_subnet,omitempty"`
|
Ipv6Subnet string `json:"ipv6_subnet,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Networkname string `json:"network_name"`
|
Networkname string `json:"network_name"`
|
||||||
NetworkID string `json:"network_id,omitempty"`
|
NetworkID string `json:"network_id,omitempty"`
|
||||||
Networkgroup string `json:"networkgroup,omitempty"`
|
Networkgroup string `json:"networkgroup,omitempty"`
|
||||||
Purpose string `json:"purpose,omitempty"`
|
Purpose string `json:"purpose,omitempty"`
|
||||||
SettingPreference string `json:"setting_preference,omitempty"`
|
SettingPreference string `json:"setting_preference,omitempty"`
|
||||||
Vlan int64 `json:"vlan,omitempty"`
|
Vlan int64 `json:"vlan,omitempty"`
|
||||||
VlanEnabled bool `json:"vlan_enabled,omitempty"`
|
VlanEnabled bool `json:"vlan_enabled,omitempty"`
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkconfigurationStatus defines the observed state of Networkconfiguration.
|
// NetworkconfigurationStatus defines the observed state of Networkconfiguration.
|
||||||
type NetworkconfigurationStatus struct {
|
type NetworkconfigurationStatus struct {
|
||||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
Ipv6SubnetStatus string `json:"ipv6_subnet_status,omitempty"`
|
Ipv6SubnetStatus string `json:"ipv6_subnet_status,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
|
|||||||
@@ -21,9 +21,118 @@ limitations under the License.
|
|||||||
package v1beta1
|
package v1beta1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *FirewallGroup) DeepCopyInto(out *FirewallGroup) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroup.
|
||||||
|
func (in *FirewallGroup) DeepCopy() *FirewallGroup {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(FirewallGroup)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *FirewallGroup) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *FirewallGroupList) DeepCopyInto(out *FirewallGroupList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]FirewallGroup, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupList.
|
||||||
|
func (in *FirewallGroupList) DeepCopy() *FirewallGroupList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(FirewallGroupList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *FirewallGroupList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) {
|
||||||
|
*out = *in
|
||||||
|
if in.ManualAddresses != nil {
|
||||||
|
in, out := &in.ManualAddresses, &out.ManualAddresses
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.AutoIncludeSelector != nil {
|
||||||
|
in, out := &in.AutoIncludeSelector, &out.AutoIncludeSelector
|
||||||
|
*out = new(v1.LabelSelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupSpec.
|
||||||
|
func (in *FirewallGroupSpec) DeepCopy() *FirewallGroupSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(FirewallGroupSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *FirewallGroupStatus) DeepCopyInto(out *FirewallGroupStatus) {
|
||||||
|
*out = *in
|
||||||
|
if in.ResolvedAddresses != nil {
|
||||||
|
in, out := &in.ResolvedAddresses, &out.ResolvedAddresses
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
if in.LastSyncTime != nil {
|
||||||
|
in, out := &in.LastSyncTime, &out.LastSyncTime
|
||||||
|
*out = (*in).DeepCopy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupStatus.
|
||||||
|
func (in *FirewallGroupStatus) DeepCopy() *FirewallGroupStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(FirewallGroupStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Networkconfiguration) DeepCopyInto(out *Networkconfiguration) {
|
func (in *Networkconfiguration) DeepCopyInto(out *Networkconfiguration) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|||||||
26
cmd/main.go
26
cmd/main.go
@@ -205,17 +205,16 @@ func main() {
|
|||||||
|
|
||||||
// Unifi client
|
// Unifi client
|
||||||
setupLog.Info("Setting up UniFi client")
|
setupLog.Info("Setting up UniFi client")
|
||||||
unifiClient, err := unifi.CreateUnifiClient()
|
unifiClient, err := unifi.CreateUnifiClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setupLog.Error(err, "failed to create UniFi client")
|
setupLog.Error(err, "failed to create UniFi client")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
setupLog.Info("Finished Setting up UniFi client")
|
setupLog.Info("Finished Setting up UniFi client")
|
||||||
|
|
||||||
|
|
||||||
if err = (&controller.NetworkconfigurationReconciler{
|
if err = (&controller.NetworkconfigurationReconciler{
|
||||||
Client: mgr.GetClient(),
|
Client: mgr.GetClient(),
|
||||||
Scheme: mgr.GetScheme(),
|
Scheme: mgr.GetScheme(),
|
||||||
UnifiClient: unifiClient,
|
UnifiClient: unifiClient,
|
||||||
}).SetupWithManager(mgr); err != nil {
|
}).SetupWithManager(mgr); err != nil {
|
||||||
setupLog.Error(err, "unable to create controller", "controller", "Networkconfiguration")
|
setupLog.Error(err, "unable to create controller", "controller", "Networkconfiguration")
|
||||||
@@ -223,6 +222,15 @@ func main() {
|
|||||||
}
|
}
|
||||||
// +kubebuilder:scaffold:builder
|
// +kubebuilder:scaffold:builder
|
||||||
|
|
||||||
|
if err = (&controller.FirewallGroupReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Scheme: mgr.GetScheme(),
|
||||||
|
UnifiClient: unifiClient,
|
||||||
|
}).SetupWithManager(mgr); err != nil {
|
||||||
|
setupLog.Error(err, "unable to create controller", "controller", "FirewallGroup")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
if metricsCertWatcher != nil {
|
if metricsCertWatcher != nil {
|
||||||
setupLog.Info("Adding metrics certificate watcher to manager")
|
setupLog.Info("Adding metrics certificate watcher to manager")
|
||||||
if err := mgr.Add(metricsCertWatcher); err != nil {
|
if err := mgr.Add(metricsCertWatcher); err != nil {
|
||||||
|
|||||||
130
config/crd/bases/unifi.engen.priv.no_firewallgroups.yaml
Normal file
130
config/crd/bases/unifi.engen.priv.no_firewallgroups.yaml
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
controller-gen.kubebuilder.io/version: v0.17.2
|
||||||
|
name: firewallgroups.unifi.engen.priv.no
|
||||||
|
spec:
|
||||||
|
group: unifi.engen.priv.no
|
||||||
|
names:
|
||||||
|
kind: FirewallGroup
|
||||||
|
listKind: FirewallGroupList
|
||||||
|
plural: firewallgroups
|
||||||
|
singular: firewallgroup
|
||||||
|
scope: Namespaced
|
||||||
|
versions:
|
||||||
|
- name: v1beta1
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
description: FirewallGroup is the Schema for the firewallgroups API.
|
||||||
|
properties:
|
||||||
|
apiVersion:
|
||||||
|
description: |-
|
||||||
|
APIVersion defines the versioned schema of this representation of an object.
|
||||||
|
Servers should convert recognized schemas to the latest internal value, and
|
||||||
|
may reject unrecognized values.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
description: |-
|
||||||
|
Kind is a string value representing the REST resource this object represents.
|
||||||
|
Servers may infer this from the endpoint the client submits requests to.
|
||||||
|
Cannot be updated.
|
||||||
|
In CamelCase.
|
||||||
|
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||||
|
type: string
|
||||||
|
metadata:
|
||||||
|
type: object
|
||||||
|
spec:
|
||||||
|
description: FirewallGroupSpec defines the desired state of FirewallGroup.
|
||||||
|
properties:
|
||||||
|
addressType:
|
||||||
|
description: AddressType can be "ip", "cidr", or "both"
|
||||||
|
enum:
|
||||||
|
- ip
|
||||||
|
- cidr
|
||||||
|
- both
|
||||||
|
type: string
|
||||||
|
autoIncludeSelector:
|
||||||
|
description: AutoIncludeSelector defines which services to extract
|
||||||
|
addresses from
|
||||||
|
properties:
|
||||||
|
matchExpressions:
|
||||||
|
description: matchExpressions is a list of label selector requirements.
|
||||||
|
The requirements are ANDed.
|
||||||
|
items:
|
||||||
|
description: |-
|
||||||
|
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||||
|
relates the key and values.
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: key is the label key that the selector applies
|
||||||
|
to.
|
||||||
|
type: string
|
||||||
|
operator:
|
||||||
|
description: |-
|
||||||
|
operator represents a key's relationship to a set of values.
|
||||||
|
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||||
|
type: string
|
||||||
|
values:
|
||||||
|
description: |-
|
||||||
|
values is an array of string values. If the operator is In or NotIn,
|
||||||
|
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||||
|
the values array must be empty. This array is replaced during a strategic
|
||||||
|
merge patch.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
- operator
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
x-kubernetes-list-type: atomic
|
||||||
|
matchLabels:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||||
|
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||||
|
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
x-kubernetes-map-type: atomic
|
||||||
|
manualAddresses:
|
||||||
|
description: ManualAddresses is a list of manual IPs or CIDRs (IPv4
|
||||||
|
or IPv6)
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
matchServicesInAllNamespaces:
|
||||||
|
type: boolean
|
||||||
|
name:
|
||||||
|
description: |-
|
||||||
|
Foo is an example field of FirewallGroup. Edit firewallgroup_types.go to remove/update
|
||||||
|
Description is a human-readable explanation for the object
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
status:
|
||||||
|
description: FirewallGroupStatus defines the observed state of FirewallGroup.
|
||||||
|
properties:
|
||||||
|
lastSyncTime:
|
||||||
|
description: LastSyncTime is the last time the object was synced
|
||||||
|
format: date-time
|
||||||
|
type: string
|
||||||
|
resolvedAddresses:
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
syncedWithUnifi:
|
||||||
|
description: SyncedWithUnifi indicates whether the addresses are successfully
|
||||||
|
pushed
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
subresources:
|
||||||
|
status: {}
|
||||||
@@ -7,6 +7,7 @@ rules:
|
|||||||
- apiGroups:
|
- apiGroups:
|
||||||
- unifi.engen.priv.no
|
- unifi.engen.priv.no
|
||||||
resources:
|
resources:
|
||||||
|
- firewallgroups
|
||||||
- networkconfigurations
|
- networkconfigurations
|
||||||
verbs:
|
verbs:
|
||||||
- create
|
- create
|
||||||
@@ -19,12 +20,14 @@ rules:
|
|||||||
- apiGroups:
|
- apiGroups:
|
||||||
- unifi.engen.priv.no
|
- unifi.engen.priv.no
|
||||||
resources:
|
resources:
|
||||||
|
- firewallgroups/finalizers
|
||||||
- networkconfigurations/finalizers
|
- networkconfigurations/finalizers
|
||||||
verbs:
|
verbs:
|
||||||
- update
|
- update
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- unifi.engen.priv.no
|
- unifi.engen.priv.no
|
||||||
resources:
|
resources:
|
||||||
|
- firewallgroups/status
|
||||||
- networkconfigurations/status
|
- networkconfigurations/status
|
||||||
verbs:
|
verbs:
|
||||||
- get
|
- get
|
||||||
|
|||||||
17
config/samples/unifi_v1beta1_firewallgroup.yaml
Normal file
17
config/samples/unifi_v1beta1_firewallgroup.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: unifi.engen.priv.no/v1beta1
|
||||||
|
kind: FirewallGroup
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: unifi-network-operator
|
||||||
|
app.kubernetes.io/managed-by: kustomize
|
||||||
|
name: firewallgroup-sample
|
||||||
|
spec:
|
||||||
|
name: Test
|
||||||
|
manualAddresses:
|
||||||
|
- 192.168.1.153
|
||||||
|
- 192.168.1.154
|
||||||
|
- 192.168.1.155
|
||||||
|
- 2a01::3
|
||||||
|
- 2a01:0::5
|
||||||
|
- 2a01:2a01::/32
|
||||||
|
# TODO(user): Add fields here
|
||||||
184
internal/controller/firewallgroup_controller.go
Normal file
184
internal/controller/firewallgroup_controller.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Vegard Engen.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
|
||||||
|
goUnifi "github.com/vegardengen/go-unifi/unifi"
|
||||||
|
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
|
||||||
|
"github.com/vegardengen/unifi-network-operator/internal/unifi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FirewallGroupReconciler reconciles a FirewallGroup object
|
||||||
|
type FirewallGroupReconciler struct {
|
||||||
|
client.Client
|
||||||
|
Scheme *runtime.Scheme
|
||||||
|
UnifiClient *unifi.UnifiClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups/status,verbs=get;update;patch
|
||||||
|
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallgroups/finalizers,verbs=update
|
||||||
|
|
||||||
|
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||||
|
// move the current state of the cluster closer to the desired state.
|
||||||
|
// TODO(user): Modify the Reconcile function to compare the state specified by
|
||||||
|
// the FirewallGroup object against the actual cluster state, and then
|
||||||
|
// perform operations to make the cluster state reflect the state specified by
|
||||||
|
// the user.
|
||||||
|
//
|
||||||
|
// For more details, check Reconcile and its Result here:
|
||||||
|
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile
|
||||||
|
|
||||||
|
func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||||
|
log := log.FromContext(ctx)
|
||||||
|
var nwObj unifiv1beta1.FirewallGroup
|
||||||
|
if err := r.Get(ctx, req.NamespacedName, &nwObj); err != nil {
|
||||||
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
|
}
|
||||||
|
log.Info(nwObj.Spec.Name)
|
||||||
|
var ipv4, ipv6 []string
|
||||||
|
|
||||||
|
for _, addressEntry := range nwObj.Spec.ManualAddresses {
|
||||||
|
ip := net.ParseIP(addressEntry)
|
||||||
|
|
||||||
|
if ip != nil {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
log.Info(fmt.Sprintf("IPv4 address: %s", addressEntry))
|
||||||
|
ipv4 = append(ipv4, addressEntry)
|
||||||
|
} else {
|
||||||
|
log.Info(fmt.Sprintf("IPv6 address: %s", addressEntry))
|
||||||
|
ipv6 = append(ipv6, ip.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr, net, err := net.ParseCIDR(addressEntry)
|
||||||
|
if err == nil && addr.Equal(net.IP) {
|
||||||
|
if addr.To4() != nil {
|
||||||
|
log.Info(fmt.Sprintf("Ipv4 Net: %s", net))
|
||||||
|
ipv4 = append(ipv4, addressEntry)
|
||||||
|
} else {
|
||||||
|
mask, _ := net.Mask.Size()
|
||||||
|
log.Info(fmt.Sprintf("Ipv6 Net: %s", net))
|
||||||
|
ipv6 = append(ipv6, addr.Mask(net.Mask).String()+"/"+fmt.Sprint(mask))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error(err, fmt.Sprintf("Could not parse: %s", addressEntry))
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firewall_groups, err := r.UnifiClient.Client.ListFirewallGroup(context.Background(), r.UnifiClient.SiteID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not list network objects")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
ipv4_name := "k8s-" + nwObj.Spec.Name + "-ipv4"
|
||||||
|
ipv6_name := "k8s-" + nwObj.Spec.Name + "-ipv6"
|
||||||
|
ipv4_done := false
|
||||||
|
ipv6_done := false
|
||||||
|
for _, firewall_group := range firewall_groups {
|
||||||
|
if firewall_group.Name == ipv4_name {
|
||||||
|
if len(ipv4) == 0 {
|
||||||
|
log.Info(fmt.Sprintf("Delete %s", ipv4_name))
|
||||||
|
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not delete firewall group")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
ipv4_done = true
|
||||||
|
} else {
|
||||||
|
if !reflect.DeepEqual(firewall_group.GroupMembers, ipv4) {
|
||||||
|
firewall_group.GroupMembers = ipv4
|
||||||
|
log.Info(fmt.Sprintf("Updating %s", ipv4_name))
|
||||||
|
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not update firewall group")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipv4_done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firewall_group.Name == ipv6_name {
|
||||||
|
if len(ipv6) == 0 {
|
||||||
|
log.Info(fmt.Sprintf("Delete %s", ipv6_name))
|
||||||
|
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not delete firewall group")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
ipv6_done = true
|
||||||
|
} else {
|
||||||
|
if !reflect.DeepEqual(firewall_group.GroupMembers, ipv6) {
|
||||||
|
firewall_group.GroupMembers = ipv6
|
||||||
|
log.Info(fmt.Sprintf("Updating %s", ipv6_name))
|
||||||
|
_, err := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not update firewall group")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ipv6_done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ipv4) > 0 && !ipv4_done {
|
||||||
|
log.Info(fmt.Sprintf("Creating %s", ipv4_name))
|
||||||
|
var firewall_group goUnifi.FirewallGroup
|
||||||
|
firewall_group.Name = ipv4_name
|
||||||
|
firewall_group.SiteID = r.UnifiClient.SiteID
|
||||||
|
firewall_group.GroupMembers = ipv4
|
||||||
|
firewall_group.GroupType = "address-group"
|
||||||
|
_, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not create firewall group")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(ipv6) > 0 && !ipv6_done {
|
||||||
|
log.Info(fmt.Sprintf("Creating %s", ipv6_name))
|
||||||
|
var firewall_group goUnifi.FirewallGroup
|
||||||
|
firewall_group.Name = ipv6_name
|
||||||
|
firewall_group.SiteID = r.UnifiClient.SiteID
|
||||||
|
firewall_group.GroupMembers = ipv6
|
||||||
|
firewall_group.GroupType = "ipv6-address-group"
|
||||||
|
_, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "Could not create firewall group")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
|
func (r *FirewallGroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
|
For(&unifiv1beta1.FirewallGroup{}).
|
||||||
|
Named("firewallgroup").
|
||||||
|
Complete(r)
|
||||||
|
}
|
||||||
84
internal/controller/firewallgroup_controller_test.go
Normal file
84
internal/controller/firewallgroup_controller_test.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 Vegard Engen.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("FirewallGroup Controller", func() {
|
||||||
|
Context("When reconciling a resource", func() {
|
||||||
|
const resourceName = "test-resource"
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
typeNamespacedName := types.NamespacedName{
|
||||||
|
Name: resourceName,
|
||||||
|
Namespace: "default", // TODO(user):Modify as needed
|
||||||
|
}
|
||||||
|
firewallgroup := &unifiv1beta1.FirewallGroup{}
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
By("creating the custom resource for the Kind FirewallGroup")
|
||||||
|
err := k8sClient.Get(ctx, typeNamespacedName, firewallgroup)
|
||||||
|
if err != nil && errors.IsNotFound(err) {
|
||||||
|
resource := &unifiv1beta1.FirewallGroup{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: resourceName,
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
// TODO(user): Specify other spec details if needed.
|
||||||
|
}
|
||||||
|
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
// TODO(user): Cleanup logic after each test, like removing the resource instance.
|
||||||
|
resource := &unifiv1beta1.FirewallGroup{}
|
||||||
|
err := k8sClient.Get(ctx, typeNamespacedName, resource)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
By("Cleanup the specific resource instance FirewallGroup")
|
||||||
|
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
|
||||||
|
})
|
||||||
|
It("should successfully reconcile the resource", func() {
|
||||||
|
By("Reconciling the created resource")
|
||||||
|
controllerReconciler := &FirewallGroupReconciler{
|
||||||
|
Client: k8sClient,
|
||||||
|
Scheme: k8sClient.Scheme(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
|
||||||
|
NamespacedName: typeNamespacedName,
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
// TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
|
||||||
|
// Example: If you expect a certain status condition after reconciliation, verify it here.
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -32,7 +32,7 @@ import (
|
|||||||
// NetworkconfigurationReconciler reconciles a Networkconfiguration object
|
// NetworkconfigurationReconciler reconciles a Networkconfiguration object
|
||||||
type NetworkconfigurationReconciler struct {
|
type NetworkconfigurationReconciler struct {
|
||||||
client.Client
|
client.Client
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
UnifiClient *unifi.UnifiClient
|
UnifiClient *unifi.UnifiClient
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,35 +53,33 @@ func (r *NetworkconfigurationReconciler) Reconcile(ctx context.Context, req ctrl
|
|||||||
log := log.FromContext(ctx)
|
log := log.FromContext(ctx)
|
||||||
|
|
||||||
var networkCRDs unifiv1.NetworkconfigurationList
|
var networkCRDs unifiv1.NetworkconfigurationList
|
||||||
if err := r.List(ctx, &networkCRDs); err != nil {
|
if err := r.List(ctx, &networkCRDs); err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
k8sNetworks := make(map[string]*unifiv1.Networkconfiguration)
|
k8sNetworks := make(map[string]*unifiv1.Networkconfiguration)
|
||||||
for i := range networkCRDs.Items {
|
for i := range networkCRDs.Items {
|
||||||
log.Info(fmt.Sprintf("Inserting network %s\n", networkCRDs.Items[i].Spec.NetworkID))
|
log.Info(fmt.Sprintf("Inserting network %s\n", networkCRDs.Items[i].Spec.NetworkID))
|
||||||
k8sNetworks[networkCRDs.Items[i].Spec.NetworkID] = &networkCRDs.Items[i]
|
k8sNetworks[networkCRDs.Items[i].Spec.NetworkID] = &networkCRDs.Items[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID)
|
networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err,"Failed to list Unifi Networks")
|
log.Error(err, "Failed to list Unifi Networks")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
seenNetworks := map[string]bool{}
|
seenNetworks := map[string]bool{}
|
||||||
|
|
||||||
for _,network := range networks {
|
for _, network := range networks {
|
||||||
networkID := network.ID
|
networkID := network.ID
|
||||||
seenNetworks[networkID] = true
|
seenNetworks[networkID] = true
|
||||||
log.Info(fmt.Sprintf("Searching for %s\n",networkID))
|
log.Info(fmt.Sprintf("Searching for %s\n", networkID))
|
||||||
|
|
||||||
if existing, found := k8sNetworks[networkID]; found {
|
if existing, found := k8sNetworks[networkID]; found {
|
||||||
log.Info(fmt.Sprintf("Found network match: %s/%s", existing.Spec.NetworkID,networkID))
|
log.Info(fmt.Sprintf("Found network match: %s/%s", existing.Spec.NetworkID, networkID))
|
||||||
} else {
|
} else {
|
||||||
log.Info(fmt.Sprintf("New network: %s with ID %s", network.Name, network.ID))
|
log.Info(fmt.Sprintf("New network: %s with ID %s", network.Name, network.ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ func CreateUnifiClient() (*UnifiClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := client.SetHTTPClient(httpClient); err != nil {
|
if err := client.SetHTTPClient(httpClient); err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("failed to set HTTP client: %s", err))
|
return nil, fmt.Errorf("failed to set HTTP client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.Login(context.Background(), username, password); err != nil {
|
if err := client.Login(context.Background(), username, password); err != nil {
|
||||||
return nil, errors.New(fmt.Sprintf("could not log into UniFi controller: %s", err))
|
return nil, fmt.Errorf("could not log into UniFi controller: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
unifiClient := &UnifiClient{
|
unifiClient := &UnifiClient{
|
||||||
|
|||||||
Reference in New Issue
Block a user