Working manually configured firewall group entries
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{})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -223,6 +223,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
|
||||||
186
internal/controller/firewallgroup_controller.go
Normal file
186
internal/controller/firewallgroup_controller.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"net"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
|
||||||
|
goUnifi "github.com/vegardengen/go-unifi/unifi"
|
||||||
|
"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.
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user