Compare commits

...

33 Commits

Author SHA1 Message Date
52afa7365d Fix reconciler logic. 2025-04-20 13:06:26 +02:00
6b85bf78c0 Add status fields and finalizer 2025-04-20 10:30:21 +02:00
a018b3e258 Fix log statement that created panic 2025-04-19 20:35:00 +02:00
70a2987230 Merge pull request #30 from vegardengen/29-create-finalizer-for-cleaning-up-firewallgroup-resources
Finalizer to clean up firewall group objects when deleting a firewall group resource
2025-04-19 15:52:18 +02:00
4fd0a7dc14 Finalizer to clean up firewall group objects when deleting a firewall group resources 2025-04-19 15:51:17 +02:00
54d94a90d5 Merge pull request #27 from vegardengen/12-create-firewall-rule-api
12 create firewall rule api
2025-04-19 00:00:14 +02:00
c63d52bb46 Firewall rule API, including needed changes in other APIs 2025-04-18 23:59:19 +02:00
89a811bef9 Preliminary scaffolding 2025-04-15 15:25:30 +02:00
4c873ea723 Merge pull request #26 from vegardengen/24-create-network-api
24 create network api
2025-04-15 10:36:24 +02:00
7e93d04c4a Add logic to reduce full resync of firewall zonea 2025-04-15 10:34:46 +02:00
625e8e0423 Add tracking only network APi 2025-04-15 10:03:07 +02:00
d63566b794 Merge pull request #25 from vegardengen/chore/formatting
Chore: Formatting
2025-04-14 19:36:58 +02:00
8ec57323fe go fmt 2025-04-14 19:35:35 +02:00
664e56def8 Merge pull request #23 from vegardengen/22-use-reauthentication-method-in-more-reconcilers
Use reauthentication
2025-04-14 17:48:02 +02:00
3f59db13f0 Use reauthentication 2025-04-14 17:47:26 +02:00
a023204d6c Merge pull request #21 from vegardengen/19-create-configmap-with-default-namespace
Fix configmap role
2025-04-14 15:14:21 +02:00
46d5e2ce2a Fix configmap role 2025-04-14 15:13:09 +02:00
72c13517b0 Merge pull request #20 from vegardengen/19-create-configmap-with-default-namespace
19 create configmap with default namespace
2025-04-14 15:08:32 +02:00
7b2acb168a Add namespace 2025-04-14 15:07:49 +02:00
46a0832aea Use configmap in firewallzone controller 2025-04-14 14:05:48 +02:00
c61a555d8a Add OperatorConfig to all controllers 2025-04-14 13:58:49 +02:00
b4b3888bc9 Add configmap support 2025-04-14 13:47:53 +02:00
6636314010 Merge remote-tracking branch 'refs/remotes/github/master' 2025-04-14 13:36:32 +02:00
8168f00038 Dependency update 2025-04-14 12:56:11 +02:00
c681a0c987 Tracking only Firewall Zone API done 2025-04-14 10:38:29 +02:00
f31d386bd0 Merge pull request #15 from vegardengen/14-create-object-for-ports-on-firewall-groups
Handle ports in specification and in load balancer services
2025-04-12 19:53:12 +02:00
2aeb2dd25a Handle ports in specification and in load balancer services 2025-04-12 19:52:33 +02:00
4af8b3f78c Merge pull request #10 from vegardengen/9-add-watcher-for-annotations-for-firewall-groups-and-handle-service-updates
Add watcher to handle Firewall Groups Defined with Annotations on services
2025-04-11 00:05:44 +02:00
3f7d615378 Watch for services with the correct annotation 2025-04-11 00:00:39 +02:00
7628ab73c7 Does not work 2025-04-10 20:26:38 +02:00
9b9188efa9 Merge pull request #8 from vegardengen/7-handle-annotations-for-firewall-groups-in-normal-reconcile-flow
Handle annotations in firewallgroup reconcile function
2025-04-10 19:41:11 +02:00
7aadd06258 Handle annotations in firewallgroup reconcile function 2025-04-10 19:36:55 +02:00
7fc1960d4c Merge pull request #5 from vegardengen/4-handle-reauthentication
Reauthentication and deleting firewall groups that are in use.
2025-04-10 13:53:53 +02:00
35 changed files with 3525 additions and 269 deletions

18
PROJECT
View File

@@ -17,4 +17,22 @@ resources:
kind: Networkconfiguration
path: github.com/vegardengen/unifi-network-operator/api/v1beta1
version: v1beta1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: engen.priv.no
group: unifi
kind: FirewallZone
path: github.com/vegardengen/unifi-network-operator/api/v1beta1
version: v1beta1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: engen.priv.no
group: unifi
kind: FirewallRule
path: github.com/vegardengen/unifi-network-operator/api/v1beta1
version: v1beta1
version: "3"

View File

@@ -0,0 +1,41 @@
package v1beta1
// 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.
// FirewallRuleSpec defines the desired state of FirewallRule.
type NamedUnifiResource struct {
Name string `json:"name,omitempty"`
ID string `json:"id,omitempty"`
}
type ServiceEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallGroupEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallZoneEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallRuleEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type NetworkEntry struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
}
type FirewallSource struct {
FirewallZones []FirewallZoneEntry `json:"from_zones,omitempty"`
Networks []NetworkEntry `json:"from_networks,omitempty"`
}
type FirewallDestination struct {
FirewallGroups []FirewallGroupEntry `json:"firewall_groups,omitempty"`
Services []ServiceEntry `json:"services,omitempty"`
}

View File

@@ -24,27 +24,28 @@ import (
// 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
ID string `json:"id,omitempty"`
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"`
ManualPorts []string `json:"manualPorts,omitempty"`
ManualServices []ServiceEntry `json:"manual_services,omitempty"`
AutoCreatedFrom FirewallRuleEntry `json:"auto_created_from,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.
@@ -52,17 +53,29 @@ 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"`
ResolvedIPV4Addresses []string `json:"resolvedIPV4Addresses,omitempty"`
ResolvedIPV6Addresses []string `json:"resolvedIPV6Addresses,omitempty"`
ResolvedTCPPorts []string `json:"resolvedTCPorts,omitempty"`
ResolvedUDPPorts []string `json:"resolvedUDPorts,omitempty"`
// SyncedWithUnifi indicates whether the addresses are successfully pushed
// +optional
SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"`
ResourcesManaged *FirewallGroupResourcesManaged `json:"resources_managed,omitempty"`
// LastSyncTime is the last time the object was synced
// +optional
LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
}
type FirewallGroupResourcesManaged struct {
IPV4Object *NamedUnifiResource `json:"ipv4_object,omitempty"`
IPV6Object *NamedUnifiResource `json:"ipv6_object,omitempty"`
TCPPortsObject *NamedUnifiResource `json:"tcp_ports_object,omitempty"`
UDPPortsObject *NamedUnifiResource `json:"udp_ports_object,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status

View File

@@ -0,0 +1,92 @@
/*
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.
// FirewallRuleSpec defines the desired state of FirewallRule.
// type ServiceSpec struct {
// Namespace string `json:"namespace,omitempty"`
// Name string `json:"name,omitempty"`
// }
// type FirewallSource struct {
// Zones []string `json:"from_zones,omitempty"`
// Networks []string `json:"from_networks,omitempty"`
//}
//type FirewallDestination struct {
// FirewallGroups []string `json:"firewall_group,omitempty"`
// Services []ServiceSpec `json:"service,omitempty"`
//}
type FirewallRuleSpec struct {
Name string `json:"name"`
Source FirewallSource `json:"source"`
Destination FirewallDestination `json:"destination"`
MatchFirewallGroupsInAllNamespaces bool `json:"match_firewall_groups_in_all_namespaces,omitempty"`
MatchServicesInAllNamespaces bool `json:"match_services_in_all_namespaces,omitempty"`
}
// FirewallRuleStatus defines the observed state of FirewallRule.
type FirewallRuleStatus struct {
ResourcesManaged *FirewallRuleResourcesManaged `json:"resources_managed,omitempty"`
}
type FirewallRuleResourcesManaged struct {
UnifiFirewallRules []UnifiFirewallRuleEntry `json:"firewall_rules_managed,omitempty"`
FirewallGroups []FirewallGroupEntry `json:"firewall_groups_managed,omitempty"`
}
type UnifiFirewallRuleEntry struct {
From string `json:"from"`
To string `json:"to"`
TcpIpv4ID string `json:"tcpipv4_id"`
UdpIpv4ID string `json:"udpipv4_id"`
TcpIpv6ID string `json:"tcpipv6_id"`
UdpIpv6ID string `json:"udpipv6_id"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// FirewallRule is the Schema for the firewallrules API.
type FirewallRule struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FirewallRuleSpec `json:"spec,omitempty"`
Status FirewallRuleStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// FirewallRuleList contains a list of FirewallRule.
type FirewallRuleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FirewallRule `json:"items"`
}
func init() {
SchemeBuilder.Register(&FirewallRule{}, &FirewallRuleList{})
}

View File

@@ -0,0 +1,73 @@
/*
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.
// FirewallZoneSpec defines the desired state of FirewallZone.
type FirewallZoneSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Name string `json:"name,omitempty"`
ID string `json:"_id,omitempty"`
DefaultZone bool `json:"default_zone,omitempty"`
ZoneKey string `json:"zone_key,omitempty"`
NetworkIDs []string `json:"network_ids,omitempty"`
}
// FirewallZoneStatus defines the observed state of FirewallZone.
type FirewallZoneStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
ResourcesManaged *FirewallZoneResourcesManaged `json:"resources_managed,omitempty"`
}
type FirewallZoneResourcesManaged struct {
UnifiFirewallZones []NamedUnifiResource `json:"firewall_zones_managed,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// FirewallZone is the Schema for the firewallzones API.
type FirewallZone struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec FirewallZoneSpec `json:"spec,omitempty"`
Status FirewallZoneStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// FirewallZoneList contains a list of FirewallZone.
type FirewallZoneList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []FirewallZone `json:"items"`
}
func init() {
SchemeBuilder.Register(&FirewallZone{}, &FirewallZoneList{})
}

View File

@@ -29,8 +29,9 @@ type NetworkconfigurationSpec struct {
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Networkconfiguration. Edit networkconfiguration_types.go to remove/update
ID string `json:"_id,omitempty"`
Enabled bool `json:"enabled,omitempty"`
FirewallZoneID string `json:"firewall_zone_id,omitempty"`
FirewallZone string `json:"firewall_zone,omitempty"`
GatewayType string `json:"gateway_type,omitempty"`
IPSubnet string `json:"ip_subnet,omitempty"`
Ipv6InterfaceType string `json:"ipv6_interface_type,omitempty"`
@@ -39,8 +40,6 @@ type NetworkconfigurationSpec struct {
Ipv6SettingPreference string `json:"ipv6_setting_preference,omitempty"`
Ipv6Subnet string `json:"ipv6_subnet,omitempty"`
Name string `json:"name"`
Networkname string `json:"network_name"`
NetworkID string `json:"network_id,omitempty"`
Networkgroup string `json:"networkgroup,omitempty"`
Purpose string `json:"purpose,omitempty"`
SettingPreference string `json:"setting_preference,omitempty"`
@@ -52,7 +51,21 @@ type NetworkconfigurationSpec struct {
type NetworkconfigurationStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
FirewallZoneID string `json:"firewall_zone_id,omitempty"`
Ipv6SubnetStatus string `json:"ipv6_subnet_status,omitempty"`
// SyncedWithUnifi indicates whether the addresses are successfully pushed
// +optional
SyncedWithUnifi bool `json:"syncedWithUnifi,omitempty"`
ResourcesManaged *NetworkconfigurationResourcesManaged `json:"resources_managed,omitempty"`
// LastSyncTime is the last time the object was synced
// +optional
LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
}
type NetworkconfigurationResourcesManaged struct {
UnifiNetworks []NamedUnifiResource `json:"networks_managed,omitempty"`
}
// +kubebuilder:object:root=true

View File

@@ -25,6 +25,31 @@ import (
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 *FirewallDestination) DeepCopyInto(out *FirewallDestination) {
*out = *in
if in.FirewallGroups != nil {
in, out := &in.FirewallGroups, &out.FirewallGroups
*out = make([]FirewallGroupEntry, len(*in))
copy(*out, *in)
}
if in.Services != nil {
in, out := &in.Services, &out.Services
*out = make([]ServiceEntry, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallDestination.
func (in *FirewallDestination) DeepCopy() *FirewallDestination {
if in == nil {
return nil
}
out := new(FirewallDestination)
in.DeepCopyInto(out)
return out
}
// 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
@@ -52,6 +77,21 @@ func (in *FirewallGroup) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupEntry) DeepCopyInto(out *FirewallGroupEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupEntry.
func (in *FirewallGroupEntry) DeepCopy() *FirewallGroupEntry {
if in == nil {
return nil
}
out := new(FirewallGroupEntry)
in.DeepCopyInto(out)
return out
}
// 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
@@ -84,6 +124,41 @@ func (in *FirewallGroupList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallGroupResourcesManaged) DeepCopyInto(out *FirewallGroupResourcesManaged) {
*out = *in
if in.IPV4Object != nil {
in, out := &in.IPV4Object, &out.IPV4Object
*out = new(NamedUnifiResource)
**out = **in
}
if in.IPV6Object != nil {
in, out := &in.IPV6Object, &out.IPV6Object
*out = new(NamedUnifiResource)
**out = **in
}
if in.TCPPortsObject != nil {
in, out := &in.TCPPortsObject, &out.TCPPortsObject
*out = new(NamedUnifiResource)
**out = **in
}
if in.UDPPortsObject != nil {
in, out := &in.UDPPortsObject, &out.UDPPortsObject
*out = new(NamedUnifiResource)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallGroupResourcesManaged.
func (in *FirewallGroupResourcesManaged) DeepCopy() *FirewallGroupResourcesManaged {
if in == nil {
return nil
}
out := new(FirewallGroupResourcesManaged)
in.DeepCopyInto(out)
return out
}
// 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
@@ -92,6 +167,17 @@ func (in *FirewallGroupSpec) DeepCopyInto(out *FirewallGroupSpec) {
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ManualPorts != nil {
in, out := &in.ManualPorts, &out.ManualPorts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ManualServices != nil {
in, out := &in.ManualServices, &out.ManualServices
*out = make([]ServiceEntry, len(*in))
copy(*out, *in)
}
out.AutoCreatedFrom = in.AutoCreatedFrom
if in.AutoIncludeSelector != nil {
in, out := &in.AutoIncludeSelector, &out.AutoIncludeSelector
*out = new(v1.LabelSelector)
@@ -112,11 +198,31 @@ func (in *FirewallGroupSpec) DeepCopy() *FirewallGroupSpec {
// 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
if in.ResolvedIPV4Addresses != nil {
in, out := &in.ResolvedIPV4Addresses, &out.ResolvedIPV4Addresses
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResolvedIPV6Addresses != nil {
in, out := &in.ResolvedIPV6Addresses, &out.ResolvedIPV6Addresses
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResolvedTCPPorts != nil {
in, out := &in.ResolvedTCPPorts, &out.ResolvedTCPPorts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResolvedUDPPorts != nil {
in, out := &in.ResolvedUDPPorts, &out.ResolvedUDPPorts
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(FirewallGroupResourcesManaged)
(*in).DeepCopyInto(*out)
}
if in.LastSyncTime != nil {
in, out := &in.LastSyncTime, &out.LastSyncTime
*out = (*in).DeepCopy()
@@ -133,13 +239,338 @@ func (in *FirewallGroupStatus) DeepCopy() *FirewallGroupStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRule) DeepCopyInto(out *FirewallRule) {
*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 FirewallRule.
func (in *FirewallRule) DeepCopy() *FirewallRule {
if in == nil {
return nil
}
out := new(FirewallRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FirewallRule) 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 *FirewallRuleEntry) DeepCopyInto(out *FirewallRuleEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleEntry.
func (in *FirewallRuleEntry) DeepCopy() *FirewallRuleEntry {
if in == nil {
return nil
}
out := new(FirewallRuleEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRuleList) DeepCopyInto(out *FirewallRuleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]FirewallRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleList.
func (in *FirewallRuleList) DeepCopy() *FirewallRuleList {
if in == nil {
return nil
}
out := new(FirewallRuleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FirewallRuleList) 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 *FirewallRuleResourcesManaged) DeepCopyInto(out *FirewallRuleResourcesManaged) {
*out = *in
if in.UnifiFirewallRules != nil {
in, out := &in.UnifiFirewallRules, &out.UnifiFirewallRules
*out = make([]UnifiFirewallRuleEntry, len(*in))
copy(*out, *in)
}
if in.FirewallGroups != nil {
in, out := &in.FirewallGroups, &out.FirewallGroups
*out = make([]FirewallGroupEntry, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleResourcesManaged.
func (in *FirewallRuleResourcesManaged) DeepCopy() *FirewallRuleResourcesManaged {
if in == nil {
return nil
}
out := new(FirewallRuleResourcesManaged)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRuleSpec) DeepCopyInto(out *FirewallRuleSpec) {
*out = *in
in.Source.DeepCopyInto(&out.Source)
in.Destination.DeepCopyInto(&out.Destination)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleSpec.
func (in *FirewallRuleSpec) DeepCopy() *FirewallRuleSpec {
if in == nil {
return nil
}
out := new(FirewallRuleSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallRuleStatus) DeepCopyInto(out *FirewallRuleStatus) {
*out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(FirewallRuleResourcesManaged)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallRuleStatus.
func (in *FirewallRuleStatus) DeepCopy() *FirewallRuleStatus {
if in == nil {
return nil
}
out := new(FirewallRuleStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallSource) DeepCopyInto(out *FirewallSource) {
*out = *in
if in.FirewallZones != nil {
in, out := &in.FirewallZones, &out.FirewallZones
*out = make([]FirewallZoneEntry, len(*in))
copy(*out, *in)
}
if in.Networks != nil {
in, out := &in.Networks, &out.Networks
*out = make([]NetworkEntry, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallSource.
func (in *FirewallSource) DeepCopy() *FirewallSource {
if in == nil {
return nil
}
out := new(FirewallSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZone) DeepCopyInto(out *FirewallZone) {
*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 FirewallZone.
func (in *FirewallZone) DeepCopy() *FirewallZone {
if in == nil {
return nil
}
out := new(FirewallZone)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FirewallZone) 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 *FirewallZoneEntry) DeepCopyInto(out *FirewallZoneEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneEntry.
func (in *FirewallZoneEntry) DeepCopy() *FirewallZoneEntry {
if in == nil {
return nil
}
out := new(FirewallZoneEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneList) DeepCopyInto(out *FirewallZoneList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]FirewallZone, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneList.
func (in *FirewallZoneList) DeepCopy() *FirewallZoneList {
if in == nil {
return nil
}
out := new(FirewallZoneList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *FirewallZoneList) 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 *FirewallZoneResourcesManaged) DeepCopyInto(out *FirewallZoneResourcesManaged) {
*out = *in
if in.UnifiFirewallZones != nil {
in, out := &in.UnifiFirewallZones, &out.UnifiFirewallZones
*out = make([]NamedUnifiResource, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneResourcesManaged.
func (in *FirewallZoneResourcesManaged) DeepCopy() *FirewallZoneResourcesManaged {
if in == nil {
return nil
}
out := new(FirewallZoneResourcesManaged)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneSpec) DeepCopyInto(out *FirewallZoneSpec) {
*out = *in
if in.NetworkIDs != nil {
in, out := &in.NetworkIDs, &out.NetworkIDs
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneSpec.
func (in *FirewallZoneSpec) DeepCopy() *FirewallZoneSpec {
if in == nil {
return nil
}
out := new(FirewallZoneSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FirewallZoneStatus) DeepCopyInto(out *FirewallZoneStatus) {
*out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(FirewallZoneResourcesManaged)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirewallZoneStatus.
func (in *FirewallZoneStatus) DeepCopy() *FirewallZoneStatus {
if in == nil {
return nil
}
out := new(FirewallZoneStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamedUnifiResource) DeepCopyInto(out *NamedUnifiResource) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamedUnifiResource.
func (in *NamedUnifiResource) DeepCopy() *NamedUnifiResource {
if in == nil {
return nil
}
out := new(NamedUnifiResource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkEntry) DeepCopyInto(out *NetworkEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkEntry.
func (in *NetworkEntry) DeepCopy() *NetworkEntry {
if in == nil {
return nil
}
out := new(NetworkEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Networkconfiguration) DeepCopyInto(out *Networkconfiguration) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
out.Status = in.Status
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Networkconfiguration.
@@ -192,6 +623,26 @@ func (in *NetworkconfigurationList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkconfigurationResourcesManaged) DeepCopyInto(out *NetworkconfigurationResourcesManaged) {
*out = *in
if in.UnifiNetworks != nil {
in, out := &in.UnifiNetworks, &out.UnifiNetworks
*out = make([]NamedUnifiResource, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkconfigurationResourcesManaged.
func (in *NetworkconfigurationResourcesManaged) DeepCopy() *NetworkconfigurationResourcesManaged {
if in == nil {
return nil
}
out := new(NetworkconfigurationResourcesManaged)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkconfigurationSpec) DeepCopyInto(out *NetworkconfigurationSpec) {
*out = *in
@@ -210,6 +661,15 @@ func (in *NetworkconfigurationSpec) DeepCopy() *NetworkconfigurationSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NetworkconfigurationStatus) DeepCopyInto(out *NetworkconfigurationStatus) {
*out = *in
if in.ResourcesManaged != nil {
in, out := &in.ResourcesManaged, &out.ResourcesManaged
*out = new(NetworkconfigurationResourcesManaged)
(*in).DeepCopyInto(*out)
}
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 NetworkconfigurationStatus.
@@ -221,3 +681,33 @@ func (in *NetworkconfigurationStatus) DeepCopy() *NetworkconfigurationStatus {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceEntry) DeepCopyInto(out *ServiceEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceEntry.
func (in *ServiceEntry) DeepCopy() *ServiceEntry {
if in == nil {
return nil
}
out := new(ServiceEntry)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UnifiFirewallRuleEntry) DeepCopyInto(out *UnifiFirewallRuleEntry) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UnifiFirewallRuleEntry.
func (in *UnifiFirewallRuleEntry) DeepCopy() *UnifiFirewallRuleEntry {
if in == nil {
return nil
}
out := new(UnifiFirewallRuleEntry)
in.DeepCopyInto(out)
return out
}

View File

@@ -38,6 +38,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/controller"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
// +kubebuilder:scaffold:imports
@@ -203,6 +204,8 @@ func main() {
os.Exit(1)
}
configLoader := config.NewConfigLoader(mgr.GetClient())
// Unifi client
setupLog.Info("Setting up UniFi client")
unifiClient, err := unifi.CreateUnifiClient()
@@ -216,16 +219,36 @@ func main() {
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Networkconfiguration")
os.Exit(1)
}
if err = (&controller.FirewallZoneReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallZone")
os.Exit(1)
}
if err = (&controller.FirewallRuleReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallRule")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err = (&controller.FirewallGroupReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UnifiClient: unifiClient,
ConfigLoader: configLoader,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FirewallGroup")
os.Exit(1)

View File

@@ -37,15 +37,14 @@ spec:
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
auto_created_from:
properties:
name:
type: string
namespace:
type: string
type: object
autoIncludeSelector:
description: AutoIncludeSelector defines which services to extract
addresses from
@@ -93,18 +92,33 @@ spec:
type: object
type: object
x-kubernetes-map-type: atomic
id:
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
manual_services:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
manualAddresses:
description: ManualAddresses is a list of manual IPs or CIDRs (IPv4
or IPv6)
items:
type: string
type: array
manualPorts:
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:
@@ -114,10 +128,57 @@ spec:
description: LastSyncTime is the last time the object was synced
format: date-time
type: string
resolvedAddresses:
resolvedIPV4Addresses:
items:
type: string
type: array
resolvedIPV6Addresses:
items:
type: string
type: array
resolvedTCPorts:
items:
type: string
type: array
resolvedUDPorts:
items:
type: string
type: array
resources_managed:
properties:
ipv4_object:
description: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
id:
type: string
name:
type: string
type: object
ipv6_object:
description: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
id:
type: string
name:
type: string
type: object
tcp_ports_object:
description: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
id:
type: string
name:
type: string
type: object
udp_ports_object:
description: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
id:
type: string
name:
type: string
type: object
type: object
syncedWithUnifi:
description: SyncedWithUnifi indicates whether the addresses are successfully
pushed

View File

@@ -0,0 +1,138 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallrules.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallRule
listKind: FirewallRuleList
plural: firewallrules
singular: firewallrule
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallRule is the Schema for the firewallrules 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:
properties:
destination:
properties:
firewall_groups:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
services:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
type: object
match_firewall_groups_in_all_namespaces:
type: boolean
match_services_in_all_namespaces:
type: boolean
name:
type: string
source:
properties:
from_networks:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
from_zones:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
type: object
required:
- destination
- name
- source
type: object
status:
description: FirewallRuleStatus defines the observed state of FirewallRule.
properties:
resources_managed:
properties:
firewall_groups_managed:
items:
properties:
name:
type: string
namespace:
type: string
type: object
type: array
firewall_rules_managed:
items:
properties:
from:
type: string
tcpipv4_id:
type: string
tcpipv6_id:
type: string
to:
type: string
udpipv4_id:
type: string
udpipv6_id:
type: string
required:
- from
- tcpipv4_id
- tcpipv6_id
- to
- udpipv4_id
- udpipv6_id
type: object
type: array
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -0,0 +1,76 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
name: firewallzones.unifi.engen.priv.no
spec:
group: unifi.engen.priv.no
names:
kind: FirewallZone
listKind: FirewallZoneList
plural: firewallzones
singular: firewallzone
scope: Namespaced
versions:
- name: v1beta1
schema:
openAPIV3Schema:
description: FirewallZone is the Schema for the firewallzones 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: FirewallZoneSpec defines the desired state of FirewallZone.
properties:
_id:
type: string
default_zone:
type: boolean
name:
type: string
network_ids:
items:
type: string
type: array
zone_key:
type: string
type: object
status:
description: FirewallZoneStatus defines the observed state of FirewallZone.
properties:
resources_managed:
properties:
firewall_zones_managed:
items:
description: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
id:
type: string
name:
type: string
type: object
type: array
type: object
type: object
type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -40,11 +40,13 @@ spec:
spec:
description: NetworkconfigurationSpec defines the desired state of Networkconfiguration.
properties:
enabled:
_id:
description: Foo is an example field of Networkconfiguration. Edit
networkconfiguration_types.go to remove/update
type: string
enabled:
type: boolean
firewall_zone_id:
firewall_zone:
type: string
gateway_type:
type: string
@@ -62,10 +64,6 @@ spec:
type: string
name:
type: string
network_id:
type: string
network_name:
type: string
networkgroup:
type: string
purpose:
@@ -79,17 +77,39 @@ spec:
type: boolean
required:
- name
- network_name
type: object
status:
description: NetworkconfigurationStatus defines the observed state of
Networkconfiguration.
properties:
ipv6_subnet_status:
firewall_zone_id:
description: |-
INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
Important: Run "make" to regenerate code after modifying this file
type: string
ipv6_subnet_status:
type: string
lastSyncTime:
description: LastSyncTime is the last time the object was synced
format: date-time
type: string
resources_managed:
properties:
networks_managed:
items:
description: FirewallRuleSpec defines the desired state of FirewallRule.
properties:
id:
type: string
name:
type: string
type: object
type: array
type: object
syncedWithUnifi:
description: SyncedWithUnifi indicates whether the addresses are successfully
pushed
type: boolean
type: object
type: object
served: true

View File

@@ -3,6 +3,8 @@
# It should be run by config/default
resources:
- bases/unifi.engen.priv.no_networkconfigurations.yaml
- bases/unifi.engen.priv.no_firewallzones.yaml
- bases/unifi.engen.priv.no_firewallrules.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patches:

View File

@@ -0,0 +1,27 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants full permissions ('*') over unifi.engen.priv.no.
# This role is intended for users authorized to modify roles and bindings within the cluster,
# enabling them to delegate specific permissions to other users or groups as needed.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallrule-admin-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallrules
verbs:
- '*'
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallrules/status
verbs:
- get

View File

@@ -0,0 +1,33 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants permissions to create, update, and delete resources within the unifi.engen.priv.no.
# This role is intended for users who need to manage these resources
# but should not control RBAC or manage permissions for others.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallrule-editor-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallrules
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallrules/status
verbs:
- get

View File

@@ -0,0 +1,29 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants read-only access to unifi.engen.priv.no resources.
# This role is intended for users who need visibility into these resources
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallrule-viewer-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallrules
verbs:
- get
- list
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallrules/status
verbs:
- get

View File

@@ -0,0 +1,27 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants full permissions ('*') over unifi.engen.priv.no.
# This role is intended for users authorized to modify roles and bindings within the cluster,
# enabling them to delegate specific permissions to other users or groups as needed.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallzone-admin-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallzones
verbs:
- '*'
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallzones/status
verbs:
- get

View File

@@ -0,0 +1,33 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants permissions to create, update, and delete resources within the unifi.engen.priv.no.
# This role is intended for users who need to manage these resources
# but should not control RBAC or manage permissions for others.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallzone-editor-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallzones
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallzones/status
verbs:
- get

View File

@@ -0,0 +1,29 @@
# This rule is not used by the project unifi-network-operator itself.
# It is provided to allow the cluster admin to help manage permissions for users.
#
# Grants read-only access to unifi.engen.priv.no resources.
# This role is intended for users who need visibility into these resources
# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallzone-viewer-role
rules:
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallzones
verbs:
- get
- list
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallzones/status
verbs:
- get

View File

@@ -22,6 +22,12 @@ resources:
# default, aiding admins in cluster management. Those roles are
# not used by the {{ .ProjectName }} itself. You can comment the following lines
# if you do not want those helpers be installed with your Project.
- firewallrule_admin_role.yaml
- firewallrule_editor_role.yaml
- firewallrule_viewer_role.yaml
- firewallzone_admin_role.yaml
- firewallzone_editor_role.yaml
- firewallzone_viewer_role.yaml
- networkconfiguration_admin_role.yaml
- networkconfiguration_editor_role.yaml
- networkconfiguration_viewer_role.yaml

View File

@@ -4,10 +4,21 @@ kind: ClusterRole
metadata:
name: manager-role
rules:
- apiGroups:
- ""
resources:
- configmaps
- services
verbs:
- get
- list
- watch
- apiGroups:
- unifi.engen.priv.no
resources:
- firewallgroups
- firewallrules
- firewallzones
- networkconfigurations
verbs:
- create
@@ -21,6 +32,8 @@ rules:
- unifi.engen.priv.no
resources:
- firewallgroups/finalizers
- firewallrules/finalizers
- firewallzones/finalizers
- networkconfigurations/finalizers
verbs:
- update
@@ -28,6 +41,8 @@ rules:
- unifi.engen.priv.no
resources:
- firewallgroups/status
- firewallrules/status
- firewallzones/status
- networkconfigurations/status
verbs:
- get

View File

@@ -1,4 +1,6 @@
## Append samples of your project ##
resources:
- unifi_v1beta1_networkconfiguration.yaml
- unifi_v1beta1_firewallzone.yaml
- unifi_v1beta1_firewallrule.yaml
# +kubebuilder:scaffold:manifestskustomizesamples

View File

@@ -7,6 +7,8 @@ metadata:
name: firewallgroup-sample
spec:
name: Test
matchServicesInAllNamespaces: true
manualAddresses:
- 192.168.1.153
- 192.168.1.154
# TODO(user): Add fields here

View File

@@ -0,0 +1,8 @@
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallRule
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallrule-sample
spec:

View File

@@ -0,0 +1,9 @@
apiVersion: unifi.engen.priv.no/v1beta1
kind: FirewallZone
metadata:
labels:
app.kubernetes.io/name: unifi-network-operator
app.kubernetes.io/managed-by: kustomize
name: firewallzone-sample
spec:
# TODO(user): Add fields here

124
go.mod
View File

@@ -1,24 +1,27 @@
module github.com/vegardengen/unifi-network-operator
go 1.23.0
go 1.24.0
toolchain go1.24.1
godebug default=go1.23
require (
github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.36.1
github.com/ubiquiti-community/go-unifi v1.33.10
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/vegardengen/go-unifi v0.0.1-alpha25
k8s.io/api v0.32.1
k8s.io/apimachinery v0.32.1
k8s.io/client-go v0.32.1
sigs.k8s.io/controller-runtime v0.20.2
sigs.k8s.io/controller-runtime v0.20.4
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
cel.dev/expr v0.18.0 // indirect
cel.dev/expr v0.23.1 // indirect
cloud.google.com/go v0.112.0 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
cloud.google.com/go/kms v1.15.5 // indirect
cloud.google.com/go/storage v1.36.0 // indirect
@@ -63,7 +66,7 @@ require (
github.com/alexkohler/nakedret/v2 v2.0.2 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
@@ -122,7 +125,7 @@ require (
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/cloudflare/circl v1.3.5 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.11.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -144,7 +147,7 @@ require (
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/elliotchance/orderedmap/v2 v2.2.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/esimonov/ifshort v1.0.4 // indirect
github.com/ettle/strcase v0.1.1 // indirect
@@ -153,8 +156,8 @@ require (
github.com/fatih/structtag v1.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/firefart/nonamedreturns v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
github.com/ghostiam/protogetter v0.2.3 // indirect
github.com/go-critic/go-critic v0.9.0 // indirect
@@ -167,13 +170,13 @@ require (
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.4 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.26.0 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/strfmt v0.21.7 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
@@ -204,14 +207,14 @@ require (
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/cel-go v0.22.0 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.16.1 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/ko v0.14.1 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/rpmpack v0.5.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect
@@ -229,7 +232,7 @@ require (
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
@@ -253,7 +256,7 @@ require (
github.com/kisielk/errcheck v1.6.3 // indirect
github.com/kisielk/gotool v1.0.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.4 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.8 // indirect
@@ -267,7 +270,7 @@ require (
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
@@ -306,15 +309,16 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polyfloyd/go-errorlint v1.4.5 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.0 // indirect
github.com/quasilyte/go-ruleguard v0.4.0 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
github.com/rivo/uniseg v0.4.2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryancurrah/gomodguard v1.3.0 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
@@ -343,14 +347,14 @@ require (
github.com/sourcegraph/go-diff v0.7.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/cobra v1.9.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/viper v1.17.0 // indirect
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect
github.com/tdakkota/asciicheck v0.2.0 // indirect
@@ -367,7 +371,6 @@ require (
github.com/ultraware/whitespace v0.0.5 // indirect
github.com/uudashr/gocognit v1.1.2 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/vegardengen/go-unifi v0.0.0-20250408133614-8e97d8aceca3 // indirect
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
@@ -382,40 +385,41 @@ require (
go-simpler.org/sloglint v0.1.2 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.tmz.dev/musttag v0.7.2 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
gocloud.dev v0.34.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/sys v0.32.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.32.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/api v0.155.0 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a // indirect
google.golang.org/grpc v1.71.1 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
@@ -426,20 +430,20 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/tools v0.4.6 // indirect
k8s.io/api v0.32.1 // indirect
k8s.io/apiextensions-apiserver v0.32.1 // indirect
k8s.io/apiserver v0.32.1 // indirect
k8s.io/component-base v0.32.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect
mvdan.cc/gofumpt v0.5.0 // indirect
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect
mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.0 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/kind v0.20.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

292
go.sum
View File

@@ -2,8 +2,8 @@
4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs=
4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc=
4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU=
cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo=
cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg=
cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -31,8 +31,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
@@ -152,8 +152,8 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@@ -311,8 +311,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc=
@@ -358,8 +358,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/elliotchance/orderedmap/v2 v2.2.0 h1:7/2iwO98kYT4XkOjA9mBEIwvi4KpGB4cyHeOFOnj4Vk=
github.com/elliotchance/orderedmap/v2 v2.2.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -385,10 +385,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y=
github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/ghostiam/protogetter v0.2.3 h1:qdv2pzo3BpLqezwqfGDLZ+nHEYmc5bUpIdsMbBVwMjw=
@@ -429,13 +429,12 @@ github.com/go-openapi/errors v0.20.4 h1:unTcVm6PispJsMECE3zWgvG4xTiKda1LIR5rCRWL
github.com/go-openapi/errors v0.20.4/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
@@ -453,9 +452,8 @@ github.com/go-openapi/strfmt v0.21.7/go.mod h1:adeGTkxE44sPyLk0JV235VQAO/ZXUr8KA
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-openapi/validate v0.22.1 h1:G+c2ub6q47kfX1sOBLwIQwzBVt8qmOAARyo/9Fqs9NU=
github.com/go-openapi/validate v0.22.1/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -577,8 +575,10 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g=
github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8=
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=
github.com/google/cel-go v0.24.1 h1:jsBCtxG8mM5wiUJDSGUqU0K7Mtr3w7Eyv00rw4DiZxI=
github.com/google/cel-go v0.24.1/go.mod h1:Hdf9TqOaTNSFQA1ybQaRqATVoK7m/zcf7IMhGXP5zI8=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -594,8 +594,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
@@ -620,8 +620,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
@@ -666,8 +666,8 @@ github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3
github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk=
github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A=
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -739,8 +739,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8=
github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -783,8 +783,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
@@ -863,10 +863,10 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI=
@@ -896,32 +896,34 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v1.4.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI=
github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
github.com/quasilyte/go-ruleguard v0.4.0 h1:DyM6r+TKL+xbKB4Nm7Afd1IQh9kEUKQs2pboWGKtvQo=
github.com/quasilyte/go-ruleguard v0.4.0/go.mod h1:Eu76Z/R8IXtViWUIHkE3p8gdH3/PKk1eh3YGfaEof10=
github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo=
@@ -937,8 +939,8 @@ github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw=
@@ -1006,11 +1008,12 @@ github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0=
@@ -1036,8 +1039,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8=
@@ -1065,8 +1068,6 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/ubiquiti-community/go-unifi v1.33.10 h1:SJ0WGX1rAzLHlNVOoGLecF8G43o6KXKs6eWhbwKuZNY=
github.com/ubiquiti-community/go-unifi v1.33.10/go.mod h1:b6sqInBsNP+LgCcrRZeK6/+JPBh4vL1Bi8R06a/VKXs=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI=
@@ -1077,8 +1078,54 @@ github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvni
github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/vegardengen/go-unifi v0.0.0-20250408133614-8e97d8aceca3 h1:Qvy/EgeaVSF0GnLVPeAb3VQYp5DSytxEcmqQjbSCuDM=
github.com/vegardengen/go-unifi v0.0.0-20250408133614-8e97d8aceca3/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha1 h1:PF1Y4NH/bfk6A7PLGxiqAhQkrfPau0B1vSayfmqlbRM=
github.com/vegardengen/go-unifi v0.0.1-alpha1/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha11 h1:+2eOU2jkJSji6YYVHUfQht/tjfGeLZCQlNGD2Oh7ndE=
github.com/vegardengen/go-unifi v0.0.1-alpha11/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha12 h1:1g0JGmB1FivG0nU9EvZVyZkX5i+HR3qRGto0jk4k8hU=
github.com/vegardengen/go-unifi v0.0.1-alpha12/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha13 h1:MEFYSlYGBmbSkxTAJ4i89iV2ji5wrvDsRgx4LmuCz3Y=
github.com/vegardengen/go-unifi v0.0.1-alpha13/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha14 h1:O5vrSemL/XbX+9aNmovYNE0xAtdurVa3TawN2wBaUbs=
github.com/vegardengen/go-unifi v0.0.1-alpha14/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha15 h1:qjmfOmxDBu8/pSKiWP7YQzh8qZEUUJ9S4vs93GjfeqM=
github.com/vegardengen/go-unifi v0.0.1-alpha15/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha16 h1:OVVaOjYO8dh32fF1BQxSrRoV878ZBLtB/KQx9jIe9Ak=
github.com/vegardengen/go-unifi v0.0.1-alpha16/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha17 h1:SoSStxP7CV+lxlKg2mqfadpXCQ2xhwRwM78sdxtPmKU=
github.com/vegardengen/go-unifi v0.0.1-alpha17/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha18 h1:vUaALQmoRzP1z764GCzfQLt1IjnlG/ODJvNxx0B84D4=
github.com/vegardengen/go-unifi v0.0.1-alpha18/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha19 h1:B1U2hXNT/6HHsw92oHElhwpuYvwIUCvCdQ5D/tOik4o=
github.com/vegardengen/go-unifi v0.0.1-alpha19/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha2 h1:2aVWpzi+i1X4rVFLOphAp7ZoTvER/rySzvpdB5cul4I=
github.com/vegardengen/go-unifi v0.0.1-alpha2/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha20 h1:kQ+QQ1IYGwvjLkoXYBEU4gSD2yQj+9FwHtMf7+GQlok=
github.com/vegardengen/go-unifi v0.0.1-alpha20/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha21 h1:a+yPTu97ETRdkhmKltMrJTo28qA0GPWctPFyICLCUZw=
github.com/vegardengen/go-unifi v0.0.1-alpha21/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha22 h1:2axPhv/S6bawsh7n3/ik4oBcRHNFjrTykOzgTRd+LnU=
github.com/vegardengen/go-unifi v0.0.1-alpha22/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha23 h1:PFKiAKy9u8s04a8cjSRIJa6N3BfPYQW8NmpEtXi+KJM=
github.com/vegardengen/go-unifi v0.0.1-alpha23/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha24 h1:0L5aRrQEdXBQAzO/AK/OaA/Gt2ymA8pzA8MoAk12aOc=
github.com/vegardengen/go-unifi v0.0.1-alpha24/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha25 h1:GAwtNpMslE6/0IfM80cWFaMqwqsn+NXlUVsvauN7v68=
github.com/vegardengen/go-unifi v0.0.1-alpha25/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha3 h1:ppllnTPduJIRZWSVLh9oGNXjo4L2hVzpVzCxvRhLSpM=
github.com/vegardengen/go-unifi v0.0.1-alpha3/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha4 h1:ZqdkYf/DRBwc1O+TwNsEAuMiXEv4j82XWbhbcMqUDnU=
github.com/vegardengen/go-unifi v0.0.1-alpha4/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha5 h1:x9uFlYX2tEch7YTpZriCRZHYPzm63jH5c/gCeNTwlho=
github.com/vegardengen/go-unifi v0.0.1-alpha5/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha6 h1:eU2a2xnHi2ITHot1rmq/MeWfd9/6gu5qmVgcWTbDX7Q=
github.com/vegardengen/go-unifi v0.0.1-alpha6/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha7 h1:JBkC4biYdnvNp9N5lodCA8jvg4OIard32bo2i/XnbyI=
github.com/vegardengen/go-unifi v0.0.1-alpha7/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha8 h1:neSsL5onDVF9TleCkMYy2Piigysf48IqEhjjGEUmMpg=
github.com/vegardengen/go-unifi v0.0.1-alpha8/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/vegardengen/go-unifi v0.0.1-alpha9 h1:KWH1e2+esmdMO9u3WPdpvz5ku+PdScvZPmt+tzACloQ=
github.com/vegardengen/go-unifi v0.0.1-alpha9/go.mod h1:iwGJACYaRNb8eElwajOM7uYtyZraV9+5171gv3Q1QSc=
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I=
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
@@ -1131,28 +1178,32 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.tmz.dev/musttag v0.7.2 h1:1J6S9ipDbalBSODNT5jCep8dhZyMr4ttnjQagmGYR5s=
go.tmz.dev/musttag v0.7.2/go.mod h1:m6q5NiiSKMnQYokefa2xGoyoXnrswCbJ0AWYzf4Zs28=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -1181,8 +1232,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1193,8 +1244,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ=
@@ -1229,8 +1280,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1276,8 +1327,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1288,8 +1339,8 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1304,8 +1355,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1372,8 +1423,8 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1381,8 +1432,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1397,13 +1448,13 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -1479,16 +1530,16 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1555,10 +1606,10 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=
google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a h1:OQ7sHVzkx6L57dQpzUS4ckfWJ51KDH74XHTDe23xWAs=
google.golang.org/genproto/googleapis/api v0.0.0-20250409194420-de1ac958c67a/go.mod h1:2R6XrVC8Oc08GlNh8ujEpc7HkLiEZ16QeY7FxIs20ac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a h1:GIqLhp/cYUkuGuiT+vJk8vhOP86L4+SP5j8yXgeVpvI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250409194420-de1ac958c67a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -1575,8 +1626,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1589,8 +1640,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
@@ -1651,10 +1702,10 @@ k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk=
k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro=
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I=
@@ -1666,16 +1717,19 @@ mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RF
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc=
sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.0 h1:XotDXzqvJ8Nx5eiZZueLpTuafJz8SiodgOemI+w87QU=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.32.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/kind v0.20.0 h1:f0sc3v9mQbGnjBUaqSFST1dwIuiikKVGgoTwpoP33a8=
sigs.k8s.io/kind v0.20.0/go.mod h1:aBlbxg08cauDgZ612shr017/rZwqd7AS563FvpWKPVs=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=

44
internal/config/config.go Normal file
View File

@@ -0,0 +1,44 @@
package config
import (
"context"
"sync"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)
type ConfigLoaderType struct {
Client client.Client
mu sync.Mutex
loaded bool
config *corev1.ConfigMap
err error
}
func NewConfigLoader(k8sClient client.Client) *ConfigLoaderType {
return &ConfigLoaderType{Client: k8sClient}
}
func (c *ConfigLoaderType) GetConfig(ctx context.Context, name string) (*corev1.ConfigMap, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.loaded {
return c.config, c.err
}
cm := &corev1.ConfigMap{}
err := c.Client.Get(ctx, types.NamespacedName{
Name: name,
Namespace: "unifi-network-operator-system",
}, cm)
c.loaded = true
c.config = cm
c.err = err
return cm, err
}

View File

@@ -21,28 +21,44 @@ import (
"fmt"
"net"
"reflect"
"regexp"
"slices"
"strconv"
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
// "sigs.k8s.io/controller-runtime/pkg/source"
goUnifi "github.com/vegardengen/go-unifi/unifi"
unifiv1beta1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
)
const firewallGroupFinalizer = "finalizer.unifi.engen.priv.no/firewallgroup"
// FirewallGroupReconciler reconciles a FirewallGroup object
type FirewallGroupReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
// +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
// +kubebuilder:rbac:groups="",resources=services,verbs=list;get;watch
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
@@ -54,16 +70,161 @@ type FirewallGroupReconciler struct {
// 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) {
func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.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 {
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
defaultNs := cfg.Data["defaultNamespace"]
log.Info(defaultNs)
var firewallGroup unifiv1beta1.FirewallGroup
if err := r.Get(ctx, req.NamespacedName, &firewallGroup); err != nil {
return reconcile.Result{}, client.IgnoreNotFound(err)
}
log.Info(firewallGroup.Spec.Name)
// Check if the object is being deleted
if firewallGroup.DeletionTimestamp != nil {
if controllerutil.ContainsFinalizer(&firewallGroup, firewallGroupFinalizer) {
err := r.UnifiClient.Reauthenticate()
if err != nil {
return reconcile.Result{}, err
}
log.Info("Running finalizer logic for FirewallGroup", "name", firewallGroup.Name)
if len(firewallGroup.Status.ResourcesManaged.IPV4Object.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete ipv4 object %s", firewallGroup.Status.ResourcesManaged.IPV4Object.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV4Object.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV4Object.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
if len(firewallGroup.Status.ResourcesManaged.IPV6Object.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete ipv6 object %s", firewallGroup.Status.ResourcesManaged.IPV6Object.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV6Object.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.IPV6Object.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"::1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
if len(firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete tcp object %s", firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"0"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
if len(firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID) > 0 {
log.Info(fmt.Sprintf("Trying to delete udp object %s", firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
firewall_group, err := r.UnifiClient.Client.GetFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID)
if err != nil {
log.Error(err, "Could not get object for renaming.")
return reconcile.Result{}, err
} else {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"0"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
}
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
}
}
controllerutil.RemoveFinalizer(&firewallGroup, firewallGroupFinalizer)
if err := r.Update(ctx, &firewallGroup); err != nil {
return ctrl.Result{}, err
}
log.Info("Successfully finalized FirewallGroup")
}
return ctrl.Result{}, nil
}
if !controllerutil.ContainsFinalizer(&firewallGroup, firewallGroupFinalizer) {
controllerutil.AddFinalizer(&firewallGroup, firewallGroupFinalizer)
if err := r.Update(ctx, &firewallGroup); err != nil {
return ctrl.Result{}, err
}
}
var ipv4, ipv6, tcpports, udpports []string
for _, addressEntry := range firewallGroup.Spec.ManualAddresses {
ip := net.ParseIP(addressEntry)
if ip != nil {
@@ -87,23 +248,127 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
}
} else {
log.Error(err, fmt.Sprintf("Could not parse: %s", addressEntry))
return ctrl.Result{}, err
return reconcile.Result{}, err
}
}
}
err := r.UnifiClient.Reauthenticate()
for _, portEntry := range firewallGroup.Spec.ManualPorts {
port_type := "tcp"
port := portEntry
if match, _ := regexp.MatchString("(?:tcp|udp)\\/?)\\d+", string(portEntry)); match {
fields := strings.Split("/", portEntry)
port_type = fields[0]
port = fields[1]
}
if port_type == "tcp" {
if !slices.Contains(tcpports, port) {
tcpports = append(tcpports, port)
}
}
if port_type == "udp" {
if !slices.Contains(udpports, port) {
tcpports = append(udpports, port)
}
}
}
var services corev1.ServiceList
if firewallGroup.Spec.MatchServicesInAllNamespaces {
if err := r.List(ctx, &services); err != nil {
log.Error(err, "unable to list services")
return reconcile.Result{}, err
}
} else {
// List Services only in the current namespace
if err := r.List(ctx, &services, client.InNamespace(req.Namespace)); err != nil {
log.Error(err, "unable to list services")
return reconcile.Result{}, err
}
}
serviceNamespaceNames := make(map[string]struct{})
for _, serviceEntry := range firewallGroup.Spec.ManualServices {
serviceNamespaceNames[serviceEntry.Namespace+"/"+serviceEntry.Name] = struct{}{}
}
log.Info(fmt.Sprintf("Manually specified: %+v", serviceNamespaceNames))
for _, service := range services.Items {
_, manually_specified := serviceNamespaceNames[service.Namespace+"/"+service.Name]
val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]
log.Info(fmt.Sprintf("%s %sv %+v %+v", service.Name, val, manually_specified, found))
// if val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]; (manually_specified || (found && val == firewallGroup.Name)) && service.Status.LoadBalancer.Ingress != nil {
if (manually_specified || (found && val == firewallGroup.Name)) && service.Status.LoadBalancer.Ingress != nil {
for _, ingress := range service.Status.LoadBalancer.Ingress {
if ingress.IP != "" {
ip := ingress.IP
if isIPv6(ip) {
ipv6 = append(ipv6, ip)
} else {
ipv4 = append(ipv4, ip)
}
}
}
if service.Spec.Ports != nil {
for _, portSpec := range service.Spec.Ports {
log.Info(fmt.Sprintf("portSpec: %+v", portSpec))
log.Info(fmt.Sprintf("Port: %s %d", strconv.Itoa(int(portSpec.Port)), portSpec.Port))
if portSpec.Protocol == "TCP" {
if !slices.Contains(tcpports, strconv.Itoa(int(portSpec.Port))) {
tcpports = append(tcpports, strconv.Itoa(int(portSpec.Port)))
}
}
if portSpec.Protocol == "UDP" {
if !slices.Contains(udpports, strconv.Itoa(int(portSpec.Port))) {
udpports = append(udpports, strconv.Itoa(int(portSpec.Port)))
}
}
}
}
}
}
firewallGroup.Status.ResolvedIPV4Addresses = ipv4
firewallGroup.Status.ResolvedIPV6Addresses = ipv6
firewallGroup.Status.ResolvedTCPPorts = tcpports
firewallGroup.Status.ResolvedUDPPorts = udpports
currentTime := metav1.Now()
firewallGroup.Status.LastSyncTime = &currentTime
firewallGroup.Status.SyncedWithUnifi = true
if firewallGroup.Status.ResourcesManaged == nil {
firewallGroup.Status.ResourcesManaged = &unifiv1beta1.FirewallGroupResourcesManaged{
IPV4Object: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
IPV6Object: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
TCPPortsObject: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
UDPPortsObject: &unifiv1beta1.NamedUnifiResource{
ID: "",
Name: "",
},
}
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
return reconcile.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
return reconcile.Result{}, err
}
ipv4_name := "k8s-" + nwObj.Spec.Name + "-ipv4"
ipv6_name := "k8s-" + nwObj.Spec.Name + "-ipv6"
ipv4_name := "k8s-" + firewallGroup.Spec.Name + "-ipv4"
ipv6_name := "k8s-" + firewallGroup.Spec.Name + "-ipv6"
tcpports_name := "k8s-" + firewallGroup.Spec.Name + "-tcpports"
udpports_name := "k8s-" + firewallGroup.Spec.Name + "-udpports"
ipv4_done := false
ipv6_done := false
tcpports_done := false
udpports_done := false
for _, firewall_group := range firewall_groups {
if firewall_group.Name == ipv4_name {
if len(ipv4) == 0 {
@@ -119,12 +384,17 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return ctrl.Result{}, updateerr
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return ctrl.Result{}, err
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = ""
}
ipv4_done = true
} else {
@@ -134,7 +404,7 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
_, 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
return reconcile.Result{}, err
}
}
ipv4_done = true
@@ -154,12 +424,17 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return ctrl.Result{}, updateerr
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return ctrl.Result{}, err
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = ""
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = ""
}
ipv6_done = true
} else {
@@ -169,12 +444,92 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
_, 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
return reconcile.Result{}, err
}
}
ipv6_done = true
}
}
if firewall_group.Name == tcpports_name {
if len(tcpports) == 0 {
log.Info(fmt.Sprintf("Delete %s", tcpports_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"0"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = ""
}
tcpports_done = true
} else {
if !reflect.DeepEqual(firewall_group.GroupMembers, tcpports) {
firewall_group.GroupMembers = tcpports
log.Info(fmt.Sprintf("Updating %s", tcpports_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 reconcile.Result{}, err
}
}
tcpports_done = true
}
}
if firewall_group.Name == udpports_name {
if len(udpports) == 0 {
log.Info(fmt.Sprintf("Delete %s", udpports_name))
err := r.UnifiClient.Client.DeleteFirewallGroup(context.Background(), r.UnifiClient.SiteID, firewall_group.ID)
if err != nil {
msg := strings.ToLower(err.Error())
log.Info(msg)
if strings.Contains(msg, "api.err.objectreferredby") {
log.Info("Firewall group is in use. Invoking workaround...!")
firewall_group.GroupMembers = []string{"127.0.0.1"}
firewall_group.Name = firewall_group.Name + "-deleted"
_, updateerr := r.UnifiClient.Client.UpdateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if updateerr != nil {
log.Error(updateerr, "Could neither delete or rename firewall group")
return reconcile.Result{}, updateerr
}
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = ""
} else {
log.Error(err, "Could not delete firewall group")
return reconcile.Result{}, err
}
} else {
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = ""
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = ""
}
udpports_done = true
} else {
if !reflect.DeepEqual(firewall_group.GroupMembers, udpports) {
firewall_group.GroupMembers = udpports
log.Info(fmt.Sprintf("Updating %s", udpports_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 reconcile.Result{}, err
}
}
udpports_done = true
}
}
if firewall_group.Name == ipv4_name+"-deleted" && len(ipv4) > 0 {
firewall_group.Name = ipv4_name
firewall_group.GroupMembers = ipv4
@@ -182,8 +537,10 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
_, 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
return reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = firewall_group.ID
ipv4_done = true
}
if firewall_group.Name == ipv6_name+"-deleted" && len(ipv6) > 0 {
@@ -193,10 +550,38 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
_, 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
return reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = firewall_group.ID
ipv6_done = true
}
if firewall_group.Name == tcpports_name+"-deleted" && len(tcpports) > 0 {
firewall_group.Name = tcpports_name
firewall_group.GroupMembers = tcpports
log.Info(fmt.Sprintf("Creating %s (from previously deleted)", tcpports_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 reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = firewall_group.ID
tcpports_done = true
}
if firewall_group.Name == udpports_name+"-deleted" && len(udpports) > 0 {
firewall_group.Name = udpports_name
firewall_group.GroupMembers = udpports
log.Info(fmt.Sprintf("Creating %s (from previously deleted)", udpports_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 reconcile.Result{}, err
}
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = firewall_group.Name
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = firewall_group.ID
udpports_done = true
}
}
if len(ipv4) > 0 && !ipv4_done {
log.Info(fmt.Sprintf("Creating %s", ipv4_name))
@@ -205,11 +590,19 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
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)
firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
log.Info(fmt.Sprintf("%+v", firewall_group_result))
if err != nil {
log.Error(err, "Could not create firewall group")
return ctrl.Result{}, err
return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
}
log.Info(fmt.Sprintf("ID and name: %s %s", firewall_group.ID, firewall_group.Name))
log.Info(fmt.Sprintf("%+v", firewall_group))
firewallGroup.Status.ResourcesManaged.IPV4Object.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.IPV4Object.Name = firewall_group.Name
}
if len(ipv6) > 0 && !ipv6_done {
log.Info(fmt.Sprintf("Creating %s", ipv6_name))
@@ -218,14 +611,97 @@ func (r *FirewallGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reques
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)
firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
log.Info(fmt.Sprintf("%+v", firewall_group_result))
if err != nil {
log.Error(err, "Could not create firewall group")
return ctrl.Result{}, err
return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
}
firewallGroup.Status.ResourcesManaged.IPV6Object.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.IPV6Object.Name = firewall_group.Name
}
if len(tcpports) > 0 && !tcpports_done {
log.Info(fmt.Sprintf("Creating %s", tcpports_name))
var firewall_group goUnifi.FirewallGroup
firewall_group.Name = tcpports_name
firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = tcpports
firewall_group.GroupType = "port-group"
log.Info(fmt.Sprintf("Trying to apply: %+v", firewall_group))
firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
if err != nil {
log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
}
firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.TCPPortsObject.Name = firewall_group.Name
}
if len(udpports) > 0 && !udpports_done {
log.Info(fmt.Sprintf("Creating %s", udpports_name))
var firewall_group goUnifi.FirewallGroup
firewall_group.Name = udpports_name
firewall_group.SiteID = r.UnifiClient.SiteID
firewall_group.GroupMembers = udpports
firewall_group.GroupType = "port-group"
log.Info(fmt.Sprintf("Trying to apply: %+v", firewall_group))
firewall_group_result, err := r.UnifiClient.Client.CreateFirewallGroup(context.Background(), r.UnifiClient.SiteID, &firewall_group)
log.Info(fmt.Sprintf("%+v", firewall_group_result))
if err != nil {
log.Error(err, "Could not create firewall group")
return reconcile.Result{}, err
} else {
firewall_group = *firewall_group_result
}
firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID = firewall_group.ID
firewallGroup.Status.ResourcesManaged.UDPPortsObject.Name = firewall_group.Name
}
log.Info(fmt.Sprintf("Updating status for %s: %+v", firewallGroup.Name, firewallGroup.Status))
if err := r.Status().Update(ctx, &firewallGroup); err != nil {
log.Error(err, "unable to update FirewallGroup status")
return reconcile.Result{}, err
}
log.Info("Successfully updated FirewallGroup status with collected IP addresses and ports")
return reconcile.Result{}, nil
}
func isIPv6(ip string) bool {
return strings.Contains(ip, ":")
}
func (r *FirewallGroupReconciler) mapServiceToFirewallGroups(ctx context.Context, obj client.Object) []reconcile.Request {
var requests []reconcile.Request
service, ok := obj.(*corev1.Service)
if !ok {
return requests
}
var allFirewallGroups unifiv1beta1.FirewallGroupList
if err := r.List(ctx, &allFirewallGroups); err != nil {
return nil
}
for _, firewallGroup := range allFirewallGroups.Items {
if firewallGroup.Spec.MatchServicesInAllNamespaces || firewallGroup.Namespace == service.Namespace {
annotationKey := "unifi.engen.priv.no/firewall-group"
annotationVal := firewallGroup.Name
if val, ok := service.Annotations[annotationKey]; ok && (annotationVal == "" || val == annotationVal) {
requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: firewallGroup.Name,
Namespace: firewallGroup.Namespace,
},
})
}
}
}
return ctrl.Result{}, nil
return requests
}
// SetupWithManager sets up the controller with the Manager.
@@ -233,5 +709,9 @@ func (r *FirewallGroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&unifiv1beta1.FirewallGroup{}).
Named("firewallgroup").
Watches(
&corev1.Service{},
handler.EnqueueRequestsFromMapFunc(r.mapServiceToFirewallGroups),
).
Complete(r)
}

View File

@@ -0,0 +1,826 @@
/*
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"
// "strings"
"encoding/json"
"time"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"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/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
)
// FirewallRuleReconciler reconciles a FirewallRule object
type FirewallRuleReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
const firewallRuleFinalizer = "finalizer.unifi.engen.priv.no/firewallrule"
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallrules,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallrules/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallrules/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// +kubebuilder:rbac:groups="",resources=services,verbs=list;get;watch
// 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 FirewallRule 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 fillDefaultRule() goUnifi.FirewallPolicy {
var firewallRule goUnifi.FirewallPolicy
firewallRule.Action = "ALLOW"
firewallRule.CreateAllowRespond = true
firewallRule.ConnectionStateType = "ALL"
firewallRule.ConnectionStates = []string{}
firewallRule.Destination = goUnifi.FirewallDestination{
MatchOppositePorts: false,
MatchingTarget: "IP",
MatchingTargetType: "OBJECT",
}
firewallRule.Enabled = true
firewallRule.ICMPTypename = "ANY"
firewallRule.ICMPV6Typename = "ANY"
firewallRule.MatchIPSec = false
firewallRule.MatchOppositeProtocol = false
firewallRule.Predefined = false
firewallRule.Schedule = goUnifi.FirewallSchedule{
Mode: "ALWAYS",
RepeatOnDays: []string{},
TimeAllDay: false,
}
firewallRule.Source = goUnifi.FirewallSource{
MatchMac: false,
MatchOppositePorts: false,
MatchOppositeNetworks: false,
}
return firewallRule
}
func (r *FirewallRuleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// TODO(user): your logic here
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
defaultNs := cfg.Data["defaultNamespace"]
kubernetesZone := cfg.Data["kubernetesUnifiZone"]
var kubernetesZoneID string
log.Info(defaultNs)
log.Info(kubernetesZone)
var firewallRule unifiv1beta1.FirewallRule
if err := r.Get(ctx, req.NamespacedName, &firewallRule); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(firewallRule.Spec.Name)
if firewallRule.DeletionTimestamp != nil {
if controllerutil.ContainsFinalizer(&firewallRule, firewallRuleFinalizer) {
err := r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
log.Info("Running finalizer logic for FirewallRule", "name", firewallRule.Name)
if len(firewallRule.Status.ResourcesManaged.UnifiFirewallRules) > 0 {
for i, UnifiFirewallRule := range firewallRule.Status.ResourcesManaged.UnifiFirewallRules {
log.Info(fmt.Sprintf("From: %s to: %s TcpIpv4: %s UdpIpv4: %s TcpIpv6: %s UdpIpv6: %s", UnifiFirewallRule.From, UnifiFirewallRule.To, UnifiFirewallRule.TcpIpv4ID, UnifiFirewallRule.UdpIpv4ID, UnifiFirewallRule.TcpIpv6ID, UnifiFirewallRule.UdpIpv6ID))
if len(UnifiFirewallRule.TcpIpv4ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallRule.TcpIpv4ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
} else {
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[i].TcpIpv4ID = ""
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
if len(UnifiFirewallRule.UdpIpv4ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallRule.UdpIpv4ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[i].UdpIpv4ID = ""
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
if len(UnifiFirewallRule.TcpIpv6ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallRule.TcpIpv6ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[i].TcpIpv6ID = ""
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
if len(UnifiFirewallRule.UdpIpv6ID) > 0 {
err := r.UnifiClient.Client.DeleteFirewallPolicy(context.Background(), r.UnifiClient.SiteID, UnifiFirewallRule.UdpIpv6ID)
if err != nil && !strings.Contains(err.Error(), "not found") {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[i].UdpIpv6ID = ""
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
}
if len(firewallRule.Status.ResourcesManaged.FirewallGroups) > 0 {
for i, firewallGroup := range firewallRule.Status.ResourcesManaged.FirewallGroups {
var firewallGroupCRD unifiv1beta1.FirewallGroup
if firewallGroup.Name != "" {
if err := r.Get(ctx, types.NamespacedName{Name: firewallGroup.Name, Namespace: firewallGroup.Namespace}, &firewallGroupCRD); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
if err := r.Delete(ctx, &firewallGroupCRD); err != nil {
log.Error(err, "Could not delete firewall group")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
firewallRule.Status.ResourcesManaged.FirewallGroups[i].Name = ""
firewallRule.Status.ResourcesManaged.FirewallGroups[i].Namespace = ""
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
controllerutil.RemoveFinalizer(&firewallRule, firewallRuleFinalizer)
if err := r.Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
log.Info("Successfully finalized FirewallGroup")
}
return ctrl.Result{}, nil
}
if !controllerutil.ContainsFinalizer(&firewallRule, firewallRuleFinalizer) {
controllerutil.AddFinalizer(&firewallRule, firewallRuleFinalizer)
if err := r.Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
}
firewallruleindex := make(map[string]int)
nextIndex := 0
if firewallRule.Status.ResourcesManaged == nil {
firewallGroupsManaged := []unifiv1beta1.FirewallGroupEntry{}
unifiFirewallRules := []unifiv1beta1.UnifiFirewallRuleEntry{}
firewallRule.Status.ResourcesManaged = &unifiv1beta1.FirewallRuleResourcesManaged{
UnifiFirewallRules: unifiFirewallRules,
FirewallGroups: firewallGroupsManaged,
}
} else {
for index, firewallRuleEntry := range firewallRule.Status.ResourcesManaged.UnifiFirewallRules {
firewallruleindex[firewallRuleEntry.From+"/"+firewallRuleEntry.To] = index
nextIndex = nextIndex + 1
}
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
var zoneCRDs unifiv1beta1.FirewallZoneList
var networkCRDs unifiv1beta1.NetworkconfigurationList
err = r.List(ctx, &zoneCRDs)
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
zoneCRDNames := make(map[string]int)
for i, zoneCRD := range zoneCRDs.Items {
namespace := defaultNs
if len(zoneCRD.Namespace) > 0 {
namespace = zoneCRD.Namespace
}
if kubernetesZone == zoneCRD.Name {
kubernetesZoneID = zoneCRD.Spec.ID
log.Info(fmt.Sprintf("Zone for kubernetes resources: %s with ID %s", kubernetesZone, kubernetesZoneID))
}
zoneCRDNames[namespace+"/"+zoneCRD.Name] = i
}
err = r.List(ctx, &networkCRDs)
if err != nil {
log.Error(err, "Could not list networks")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
networkCRDNames := make(map[string]int)
for i, networkCRD := range networkCRDs.Items {
namespace := defaultNs
if len(networkCRD.Namespace) > 0 {
namespace = networkCRD.Namespace
}
networkCRDNames[namespace+"/"+networkCRD.Name] = i
}
destination_services := make(map[string]struct{})
destination_groups := make(map[string]struct{})
for _, dest_group := range firewallRule.Spec.Destination.FirewallGroups {
namespace := defaultNs
if len(dest_group.Namespace) > 0 {
namespace = dest_group.Namespace
}
destination_groups[namespace+"/"+dest_group.Name] = struct{}{}
}
for _, dest_service := range firewallRule.Spec.Destination.Services {
namespace := defaultNs
if len(dest_service.Namespace) > 0 {
namespace = dest_service.Namespace
}
destination_services[namespace+"/"+dest_service.Name] = struct{}{}
}
log.Info(fmt.Sprintf("%+v", destination_services))
var firewallGroupCRDs unifiv1beta1.FirewallGroupList
var myFirewallGroups []unifiv1beta1.FirewallGroup
if err = r.List(ctx, &firewallGroupCRDs); err != nil {
log.Error(err, "Failed to list firewall groups")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
for _, firewallGroup := range firewallGroupCRDs.Items {
if val, found := firewallGroup.Annotations["unifi.engen.priv.no/firewall-rule"]; found && val == firewallRule.Name {
myFirewallGroups = append(myFirewallGroups, firewallGroup)
} else if _, found := destination_groups[firewallGroup.Namespace+"/"+firewallGroup.Name]; found {
myFirewallGroups = append(myFirewallGroups, firewallGroup)
}
}
myFirewallGroupNames := make(map[string]struct{})
for _, firewallGroup := range myFirewallGroups {
myFirewallGroupNames[firewallGroup.Name] = struct{}{}
}
var serviceCRDs corev1.ServiceList
var myServices []corev1.Service
if err = r.List(ctx, &serviceCRDs); err != nil {
log.Error(err, "Failed to list services")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
for _, service := range serviceCRDs.Items {
skipService := false
if val, found := service.Annotations["unifi.engen.priv.no/firewall-group"]; found {
if _, found := myFirewallGroupNames[val]; found {
skipService = true
}
}
if val, found := service.Annotations["unifi.engen.priv.no/firewall-rule"]; found && val == firewallRule.Name && !skipService {
myServices = append(myServices, service)
} else if _, found := destination_services[service.Namespace+"/"+service.Name]; found && !skipService {
myServices = append(myServices, service)
}
}
for _, service := range myServices {
log.Info(fmt.Sprintf("Should handle service %s", service.Name))
var firewallGroupCRD unifiv1beta1.FirewallGroup
if err := r.Get(ctx, types.NamespacedName{
Name: toKubeName("k8s-auto" + "_" + service.Namespace + "/" + service.Name),
Namespace: firewallRule.Namespace,
}, &firewallGroupCRD); err == nil {
myFirewallGroups = append(myFirewallGroups, firewallGroupCRD)
} else {
log.Info("Going to create firewall group")
var manualServices []unifiv1beta1.ServiceEntry
manualServices = append(manualServices, unifiv1beta1.ServiceEntry{
Name: service.Name,
Namespace: service.Namespace,
})
createdFirewallGroupCRD := &unifiv1beta1.FirewallGroup{
ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName("k8s-auto" + "_" + service.Namespace + "/" + service.Name),
Namespace: firewallRule.Namespace,
},
Spec: unifiv1beta1.FirewallGroupSpec{
Name: "auto-" + service.Namespace + "/" + service.Name,
AutoCreatedFrom: unifiv1beta1.FirewallRuleEntry{
Name: firewallRule.Name,
Namespace: firewallRule.Namespace,
},
ManualServices: manualServices,
MatchServicesInAllNamespaces: true,
},
}
log.Info(fmt.Sprintf("%+v", createdFirewallGroupCRD))
if err := r.Create(ctx, createdFirewallGroupCRD); err != nil {
log.Error(err, fmt.Sprintf("Failed to create %s", createdFirewallGroupCRD.Name))
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
} else {
time.Sleep(10 * time.Second)
_ = r.Get(ctx, types.NamespacedName{Name: createdFirewallGroupCRD.Name, Namespace: createdFirewallGroupCRD.Namespace}, &firewallGroupCRD)
}
log.Info(fmt.Sprintf("Adding %+v", firewallGroupCRD))
myFirewallGroups = append(myFirewallGroups, firewallGroupCRD)
found := false
for _, managedFirewallGroup := range firewallRule.Status.ResourcesManaged.FirewallGroups {
if managedFirewallGroup.Name == firewallGroupCRD.Name && managedFirewallGroup.Namespace == firewallGroupCRD.Namespace {
found = true
}
}
if !found {
firewallRule.Status.ResourcesManaged.FirewallGroups = append(firewallRule.Status.ResourcesManaged.FirewallGroups, unifiv1beta1.FirewallGroupEntry{Name: firewallGroupCRD.Name, Namespace: firewallGroupCRD.Namespace})
if err := r.Status().Update(ctx, &firewallRule); err != nil {
log.Error(err, "Failed to update status with added firewallgroup")
}
}
}
}
unifi_firewall_rules, err := r.UnifiClient.Client.ListFirewallPolicy(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list firewall rules")
return ctrl.Result{}, err
}
unifiFirewallruleNames := make(map[string]struct{})
for _, unifi_firewall_rule := range unifi_firewall_rules {
unifiFirewallruleNames[unifi_firewall_rule.Name] = struct{}{}
}
log.Info(fmt.Sprintf("Number of firewall rules: %d", len(unifi_firewall_rules)))
for _, zoneEntry := range firewallRule.Spec.Source.FirewallZones {
namespace := defaultNs
if len(zoneEntry.Namespace) > 0 {
namespace = zoneEntry.Namespace
}
if i, found := zoneCRDNames[namespace+"/"+zoneEntry.Name]; found {
log.Info(fmt.Sprintf("Creating firewallrules for %s", zoneCRDs.Items[i].Name))
for _, firewallGroup := range myFirewallGroups {
found := false
index, found := firewallruleindex["zone:"+zoneCRDs.Items[i].Name+"/"+firewallGroup.Name]
if !found {
firewallRuleEntry := unifiv1beta1.UnifiFirewallRuleEntry{
From: "zone:" + zoneCRDs.Items[i].Name,
To: firewallGroup.Name,
TcpIpv4ID: "",
UdpIpv4ID: "",
TcpIpv6ID: "",
UdpIpv6ID: "",
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules = append(firewallRule.Status.ResourcesManaged.UnifiFirewallRules, firewallRuleEntry)
index = nextIndex
nextIndex = nextIndex + 1
}
if len(firewallGroup.Status.ResolvedIPV4Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + zoneCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv4-tcp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv4 tcp firewallrule for %s to %s: %s", zoneCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = zoneCRDs.Items[i].Spec.ID
unifiFirewallRule.Source.MatchingTarget = "ANY"
unifiFirewallRule.Protocol = "tcp"
unifiFirewallRule.IPVersion = "IPV4"
unifiFirewallRule.Description = fmt.Sprintf("Allow tcp IPV4 from %s to %s", zoneCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from zone %s to %s: %+v", zoneCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].TcpIpv4ID = updatedRule.ID
if err = r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv4 tcp %s to %s already exists", zoneCRDs.Items[i].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + zoneCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv4-udp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv4 udp firewallrule for %s to %s: %s", zoneCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = zoneCRDs.Items[i].Spec.ID
unifiFirewallRule.Source.MatchingTarget = "ANY"
unifiFirewallRule.Protocol = "udp"
unifiFirewallRule.IPVersion = "IPV4"
unifiFirewallRule.Description = fmt.Sprintf("Allow udp IPV4 from %s to %s", zoneCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from zone %s to %s: %+v", zoneCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].UdpIpv4ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv4 udp %s to %s already exists", zoneCRDs.Items[i].Name, firewallGroup.Name))
}
}
}
if len(firewallGroup.Status.ResolvedIPV6Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + zoneCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv6-tcp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv6 tcp firewallrule for %s to %s: %s", zoneCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = zoneCRDs.Items[i].Spec.ID
unifiFirewallRule.Source.MatchingTarget = "ANY"
unifiFirewallRule.Protocol = "tcp"
unifiFirewallRule.IPVersion = "IPV6"
unifiFirewallRule.Description = fmt.Sprintf("Allow tcp IPV6 from %s to %s", zoneCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from zone %s to %s: %+v", zoneCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].TcpIpv6ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv6 tcp %s to %s already exists", zoneCRDs.Items[i].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + zoneCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv6-udp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv6 udp firewallrule for %s to %s: %s", zoneCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = zoneCRDs.Items[i].Spec.ID
unifiFirewallRule.Source.MatchingTarget = "ANY"
unifiFirewallRule.Protocol = "udp"
unifiFirewallRule.IPVersion = "IPV6"
unifiFirewallRule.Description = fmt.Sprintf("Allow udp IPV6 from %s to %s", zoneCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from zone %s to %s: %+v", zoneCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].UdpIpv6ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv6 udp %s to %s already exists", zoneCRDs.Items[i].Name, firewallGroup.Name))
}
}
}
}
}
}
for _, networkEntry := range firewallRule.Spec.Source.Networks {
namespace := defaultNs
if len(networkEntry.Namespace) > 0 {
namespace = networkEntry.Namespace
}
if i, found := networkCRDNames[namespace+"/"+networkEntry.Name]; found {
log.Info(fmt.Sprintf("Creating firewallrules for %s", networkCRDs.Items[i].Name))
for _, firewallGroup := range myFirewallGroups {
index, found := firewallruleindex["network:"+networkCRDs.Items[i].Name+"/"+firewallGroup.Name]
if !found {
firewallRuleEntry := unifiv1beta1.UnifiFirewallRuleEntry{
From: "zone:" + networkCRDs.Items[i].Name,
To: firewallGroup.Name,
TcpIpv4ID: "",
UdpIpv4ID: "",
TcpIpv6ID: "",
UdpIpv6ID: "",
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules = append(firewallRule.Status.ResourcesManaged.UnifiFirewallRules, firewallRuleEntry)
index = nextIndex
nextIndex = nextIndex + 1
}
if len(firewallGroup.Status.ResolvedIPV4Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + networkCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv4-tcp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv4 tcp firewallrule for %s to %s: %s", networkCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.NetworkIDs = []string{networkCRDs.Items[i].Spec.ID}
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = networkCRDs.Items[i].Status.FirewallZoneID
unifiFirewallRule.Source.MatchingTarget = "NETWORK"
unifiFirewallRule.Protocol = "tcp"
unifiFirewallRule.IPVersion = "IPV4"
unifiFirewallRule.Description = fmt.Sprintf("Allow tcp IPV4 from %s to %s", networkCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from network %s to %s: %+v", networkCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].TcpIpv4ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv4 tcp %s to %s already exists", networkCRDs.Items[i].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + networkCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv4-udp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv4 udp firewallrule for %s to %s: %s", networkCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.NetworkIDs = []string{networkCRDs.Items[i].Spec.ID}
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = networkCRDs.Items[i].Status.FirewallZoneID
unifiFirewallRule.Source.MatchingTarget = "NETWORK"
unifiFirewallRule.Protocol = "udp"
unifiFirewallRule.IPVersion = "IPV4"
unifiFirewallRule.Description = fmt.Sprintf("Allow udp IPV4 from %s to %s", networkCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV4Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from network %s to %s: %+v", networkCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].UdpIpv4ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv4 udp %s to %s already exists", networkCRDs.Items[i].Name, firewallGroup.Name))
}
}
}
if len(firewallGroup.Status.ResolvedIPV6Addresses) > 0 {
if len(firewallGroup.Status.ResolvedTCPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + networkCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv6-tcp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv6 tcp firewallrule for %s to %s: %s", networkCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.NetworkIDs = []string{networkCRDs.Items[i].Spec.ID}
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = networkCRDs.Items[i].Status.FirewallZoneID
unifiFirewallRule.Source.MatchingTarget = "NETWORK"
unifiFirewallRule.Protocol = "tcp"
unifiFirewallRule.IPVersion = "IPV6"
unifiFirewallRule.Description = fmt.Sprintf("Allow tcp IPV6 from %s to %s", networkCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.TCPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from network %s to %s: %+v", networkCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].TcpIpv6ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv6 tcp %s to %s already exists", networkCRDs.Items[i].Name, firewallGroup.Name))
}
}
if len(firewallGroup.Status.ResolvedUDPPorts) > 0 {
rulename := "k8s-fw-" + firewallRule.Name + "-" + networkCRDs.Items[i].Name + "-" + firewallGroup.Name + "-ipv6-udp"
if _, found := unifiFirewallruleNames[rulename]; !found {
log.Info(fmt.Sprintf("Creating ipv6 udp firewallrule for %s to %s: %s", networkCRDs.Items[i].Name, firewallGroup.Name, rulename))
unifiFirewallRule := fillDefaultRule()
unifiFirewallRule.Name = rulename
unifiFirewallRule.Source.NetworkIDs = []string{networkCRDs.Items[i].Spec.ID}
unifiFirewallRule.Source.PortMatchingType = "ANY"
unifiFirewallRule.Source.ZoneID = networkCRDs.Items[i].Status.FirewallZoneID
unifiFirewallRule.Source.MatchingTarget = "NETWORK"
unifiFirewallRule.Protocol = "udp"
unifiFirewallRule.IPVersion = "IPV6"
unifiFirewallRule.Description = fmt.Sprintf("Allow udp IPV6 from %s to %s", networkCRDs.Items[i].Name, firewallGroup.Name)
unifiFirewallRule.Destination.MatchingTargetType = "OBJECT"
unifiFirewallRule.Destination.IPGroupID = firewallGroup.Status.ResourcesManaged.IPV6Object.ID
unifiFirewallRule.Destination.MatchingTarget = "IP"
unifiFirewallRule.Destination.PortMatchingType = "OBJECT"
unifiFirewallRule.Destination.PortGroupID = firewallGroup.Status.ResourcesManaged.UDPPortsObject.ID
unifiFirewallRule.Destination.ZoneID = kubernetesZoneID
log.Info(fmt.Sprintf("Trying to create firewall rule from network %s to %s: %+v", networkCRDs.Items[i].Name, firewallGroup.Name, unifiFirewallRule))
pretty, _ := json.MarshalIndent(unifiFirewallRule, "", " ")
log.Info(string(pretty))
updatedRule, err := r.UnifiClient.Client.CreateFirewallPolicy(context.Background(), r.UnifiClient.SiteID, &unifiFirewallRule)
if err != nil {
log.Error(err, "Could not create firewall policy")
return ctrl.Result{}, err
}
firewallRule.Status.ResourcesManaged.UnifiFirewallRules[index].UdpIpv6ID = updatedRule.ID
if err := r.Status().Update(ctx, &firewallRule); err != nil {
return ctrl.Result{}, err
}
} else {
log.Info(fmt.Sprintf("Firewall rule for ipv6 udp %s to %s already exists", networkCRDs.Items[i].Name, firewallGroup.Name))
}
}
}
}
}
}
return ctrl.Result{}, nil
}
func (r *FirewallRuleReconciler) mapFirewallGroupToFirewallRules(ctx context.Context, obj client.Object) []ctrl.Request {
var requests []ctrl.Request
firewallGroup, ok := obj.(*unifiv1beta1.FirewallGroup)
if !ok {
return requests
}
var allFirewallRules unifiv1beta1.FirewallRuleList
if err := r.List(ctx, &allFirewallRules); err != nil {
return nil
}
for _, rule := range allFirewallRules.Items {
if rule.Spec.MatchFirewallGroupsInAllNamespaces || rule.Namespace == firewallGroup.Namespace {
annotationKey := "unifi.engen.priv.no/firewall-rule"
annotationVal := rule.Name
if val, ok := firewallGroup.Annotations[annotationKey]; ok && (annotationVal == "" || val == annotationVal) {
requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: rule.Name,
Namespace: rule.Namespace,
},
})
}
}
}
return requests
}
func (r *FirewallRuleReconciler) mapServiceToFirewallRules(ctx context.Context, obj client.Object) []ctrl.Request {
var requests []ctrl.Request
service, ok := obj.(*corev1.Service)
if !ok {
return requests
}
var allFirewallRules unifiv1beta1.FirewallRuleList
if err := r.List(ctx, &allFirewallRules); err != nil {
return nil
}
for _, rule := range allFirewallRules.Items {
if rule.Spec.MatchServicesInAllNamespaces || rule.Namespace == service.Namespace {
annotationKey := "unifi.engen.priv.no/firewall-rule"
annotationVal := rule.Name
if val, ok := service.Annotations[annotationKey]; ok && (annotationVal == "" || val == annotationVal) {
requests = append(requests, ctrl.Request{
NamespacedName: types.NamespacedName{
Name: rule.Name,
Namespace: rule.Namespace,
},
})
}
}
}
return requests
}
// SetupWithManager sets up the controller with the Manager.
func (r *FirewallRuleReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&unifiv1beta1.FirewallRule{}).
Named("firewallrule").
Watches(
&corev1.Service{},
handler.EnqueueRequestsFromMapFunc(r.mapServiceToFirewallRules),
).
Watches(
&unifiv1beta1.FirewallGroup{},
handler.EnqueueRequestsFromMapFunc(r.mapFirewallGroupToFirewallRules),
).
Complete(r)
}

View 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("FirewallRule 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
}
firewallrule := &unifiv1beta1.FirewallRule{}
BeforeEach(func() {
By("creating the custom resource for the Kind FirewallRule")
err := k8sClient.Get(ctx, typeNamespacedName, firewallrule)
if err != nil && errors.IsNotFound(err) {
resource := &unifiv1beta1.FirewallRule{
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.FirewallRule{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())
By("Cleanup the specific resource instance FirewallRule")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
It("should successfully reconcile the resource", func() {
By("Reconciling the created resource")
controllerReconciler := &FirewallRuleReconciler{
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.
})
})
})

View File

@@ -0,0 +1,225 @@
/*
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"
"regexp"
"strings"
"time"
"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"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
)
// FirewallZoneReconciler reconciles a FirewallZone object
type FirewallZoneReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
func toKubeName(input string) string {
// Lowercase the input
name := strings.ToLower(input)
// Replace any non-alphanumeric characters with dashes
re := regexp.MustCompile(`[^a-z0-9\-\.]+`)
name = re.ReplaceAllString(name, "-")
// Trim leading and trailing non-alphanumerics
name = strings.Trim(name, "-.")
// Ensure it's not empty and doesn't exceed 253 characters
if len(name) == 0 {
name = "default"
} else if len(name) > 253 {
name = name[:253]
}
return name
}
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=firewallzones/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// 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 FirewallZone 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 *FirewallZoneReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
defaultNs := cfg.Data["defaultNamespace"]
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
fullSyncZone := "gateway"
if cfg.Data["fullSyncZone"] != "" {
fullSyncZone = cfg.Data["fullSyncZone"]
}
fullSync := false
var zoneObj unifiv1beta1.FirewallZone
if err := r.Get(ctx, req.NamespacedName, &zoneObj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(fmt.Sprintf("fullSyncZone: %s Zone: %s", fullSyncZone, zoneObj.Name))
if zoneObj.Name == fullSyncZone {
fullSync = true
log.Info("Going into fullsync mode")
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
if !fullSync {
firewallzones, err := r.UnifiClient.Client.ListFirewallZones(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{}, err
}
found := false
for _, unifizone := range firewallzones {
if unifizone.Name == zoneObj.Spec.Name {
found = true
zoneSpec := unifiv1beta1.FirewallZoneSpec{
Name: unifizone.Name,
ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone,
NetworkIDs: unifizone.NetworkIDs,
}
zoneObj.Spec = zoneSpec
err := r.Update(ctx, &zoneObj)
if err != nil {
return ctrl.Result{}, err
}
}
}
if !found {
err := r.Delete(ctx, &zoneObj)
if err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
log.Info("Starting fullsync mode")
var fwzCRDs unifiv1beta1.FirewallZoneList
_ = r.List(ctx, &fwzCRDs, client.InNamespace(defaultNs))
firewall_zones, err := r.UnifiClient.Client.ListFirewallZones(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
log.Info(fmt.Sprintf("Number of resources: %d Number of zones in Unifi: %d", len(fwzCRDs.Items), len(firewall_zones)))
firewallZoneNamesUnifi := make(map[string]struct{})
for _, zone := range firewall_zones {
firewallZoneNamesUnifi[zone.Name] = struct{}{}
}
// Step 2: Collect zones in fwzCRDs that are NOT in firewall_zones
for _, zone := range fwzCRDs.Items {
if _, found := firewallZoneNamesUnifi[zone.Spec.Name]; !found {
err := r.Delete(ctx, &zone)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
firewallZoneNamesCRDs := make(map[string]struct{})
for _, zoneCRD := range fwzCRDs.Items {
firewallZoneNamesCRDs[zoneCRD.Spec.Name] = struct{}{}
}
for _, unifizone := range firewall_zones {
log.Info(fmt.Sprintf("%+v\n", unifizone))
if _, found := firewallZoneNamesCRDs[unifizone.Name]; !found {
zoneCRD := &unifiv1beta1.FirewallZone{
ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName(unifizone.Name),
Namespace: defaultNs,
},
Spec: unifiv1beta1.FirewallZoneSpec{
Name: unifizone.Name,
ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone,
ZoneKey: unifizone.ZoneKey,
NetworkIDs: unifizone.NetworkIDs,
},
}
err := r.Create(ctx, zoneCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
} else {
for _, zoneCRD := range fwzCRDs.Items {
if zoneCRD.Spec.Name == unifizone.Name {
zoneCRD.Spec = unifiv1beta1.FirewallZoneSpec{
Name: unifizone.Name,
ID: unifizone.ID,
DefaultZone: unifizone.DefaultZone,
ZoneKey: unifizone.ZoneKey,
NetworkIDs: unifizone.NetworkIDs,
}
err := r.Update(ctx, &zoneCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
}
return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil
}
// SetupWithManager sets up the controller with the Manager.
func (r *FirewallZoneReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&unifiv1beta1.FirewallZone{}).
Named("firewallzone").
Complete(r)
}

View 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("FirewallZone 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
}
firewallzone := &unifiv1beta1.FirewallZone{}
BeforeEach(func() {
By("creating the custom resource for the Kind FirewallZone")
err := k8sClient.Get(ctx, typeNamespacedName, firewallzone)
if err != nil && errors.IsNotFound(err) {
resource := &unifiv1beta1.FirewallZone{
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.FirewallZone{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())
By("Cleanup the specific resource instance FirewallZone")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
It("should successfully reconcile the resource", func() {
By("Reconciling the created resource")
controllerReconciler := &FirewallZoneReconciler{
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.
})
})
})

View File

@@ -19,13 +19,16 @@ package controller
import (
"context"
"fmt"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
unifiv1 "github.com/vegardengen/unifi-network-operator/api/v1beta1"
"github.com/vegardengen/unifi-network-operator/internal/config"
"github.com/vegardengen/unifi-network-operator/internal/unifi"
)
@@ -34,11 +37,13 @@ type NetworkconfigurationReconciler struct {
client.Client
Scheme *runtime.Scheme
UnifiClient *unifi.UnifiClient
ConfigLoader *config.ConfigLoaderType
}
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=unifi.engen.priv.no,resources=networkconfigurations/finalizers,verbs=update
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=list;get;watch
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
@@ -51,38 +56,175 @@ type NetworkconfigurationReconciler struct {
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.2/pkg/reconcile
func (r *NetworkconfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
var networkCRDs unifiv1.NetworkconfigurationList
if err := r.List(ctx, &networkCRDs); err != nil {
cfg, err := r.ConfigLoader.GetConfig(ctx, "unifi-operator-config")
if err != nil {
return ctrl.Result{}, err
}
k8sNetworks := make(map[string]*unifiv1.Networkconfiguration)
for i := range networkCRDs.Items {
log.Info(fmt.Sprintf("Inserting network %s\n", networkCRDs.Items[i].Spec.NetworkID))
k8sNetworks[networkCRDs.Items[i].Spec.NetworkID] = &networkCRDs.Items[i]
defaultNs := cfg.Data["defaultNamespace"]
log.Info(defaultNs)
fullSyncNetwork := "core"
if cfg.Data["fullSyncNetwork"] != "" {
fullSyncNetwork = cfg.Data["fullSyncNetwork"]
}
fullSync := false
var networkObj unifiv1.Networkconfiguration
if err := r.Get(ctx, req.NamespacedName, &networkObj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(fmt.Sprintf("fullSyncNetwork: %s Network: %s", fullSyncNetwork, networkObj.Name))
if networkObj.Name == fullSyncNetwork {
fullSync = true
log.Info("Going into fullsync mode")
}
err = r.UnifiClient.Reauthenticate()
if err != nil {
return ctrl.Result{}, err
}
if !fullSync {
networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Could not list netwrorks")
return ctrl.Result{}, err
}
found := false
for _, unifinetwork := range networks {
if unifinetwork.Name == networkObj.Spec.Name {
found = true
networkSpec := unifiv1.NetworkconfigurationSpec{
Name: unifinetwork.Name,
ID: unifinetwork.ID,
IPSubnet: unifinetwork.IPSubnet,
Ipv6InterfaceType: unifinetwork.IPV6InterfaceType,
Ipv6PdAutoPrefixidEnabled: unifinetwork.IPV6PDAutoPrefixidEnabled,
Ipv6RaEnabled: unifinetwork.IPV6RaEnabled,
Ipv6SettingPreference: unifinetwork.IPV6SettingPreference,
Ipv6Subnet: unifinetwork.IPV6Subnet,
Purpose: unifinetwork.Purpose,
Networkgroup: unifinetwork.NetworkGroup,
SettingPreference: unifinetwork.SettingPreference,
Vlan: int64(unifinetwork.VLAN),
VlanEnabled: unifinetwork.VLANEnabled,
}
networkObj.Spec = networkSpec
err := r.Update(ctx, &networkObj)
if err != nil {
return ctrl.Result{}, err
}
}
}
if !found {
err := r.Delete(ctx, &networkObj)
if err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
log.Info("Starting fullsync mode")
var networkCRDs unifiv1.NetworkconfigurationList
_ = r.List(ctx, &networkCRDs, client.InNamespace(defaultNs))
networks, err := r.UnifiClient.Client.ListNetwork(context.Background(), r.UnifiClient.SiteID)
if err != nil {
log.Error(err, "Failed to list Unifi Networks")
return ctrl.Result{}, err
log.Error(err, "Could not list netwrorks")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
log.Info(fmt.Sprintf("Number of resources: %d Number of networks in Unifi: %d", len(networkCRDs.Items), len(networks)))
var firewallZoneCRDs unifiv1.FirewallZoneList
err = r.List(ctx, &firewallZoneCRDs, client.InNamespace(defaultNs))
if err != nil {
log.Error(err, "Could not list firewall zones")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
seenNetworks := map[string]bool{}
networkNamesUnifi := make(map[string]struct{})
for _, unifinetwork := range networks {
networkNamesUnifi[unifinetwork.Name] = struct{}{}
}
for _, network := range networks {
networkID := network.ID
seenNetworks[networkID] = true
log.Info(fmt.Sprintf("Searching for %s\n", networkID))
if existing, found := k8sNetworks[networkID]; found {
log.Info(fmt.Sprintf("Found network match: %s/%s", existing.Spec.NetworkID, networkID))
// Step 2: Collect zones in fwzCRDs that are NOT in firewall_zones
for _, network := range networkCRDs.Items {
if _, found := networkNamesUnifi[network.Spec.Name]; !found {
err := r.Delete(ctx, &network)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
networkNamesCRDs := make(map[string]struct{})
for _, networkCRD := range networkCRDs.Items {
networkNamesCRDs[networkCRD.Spec.Name] = struct{}{}
}
for _, unifinetwork := range networks {
if unifinetwork.Purpose == "corporate" {
networkSpec := unifiv1.NetworkconfigurationSpec{
Name: unifinetwork.Name,
ID: unifinetwork.ID,
IPSubnet: unifinetwork.IPSubnet,
Ipv6InterfaceType: unifinetwork.IPV6InterfaceType,
Ipv6PdAutoPrefixidEnabled: unifinetwork.IPV6PDAutoPrefixidEnabled,
Ipv6RaEnabled: unifinetwork.IPV6RaEnabled,
Ipv6SettingPreference: unifinetwork.IPV6SettingPreference,
Ipv6Subnet: unifinetwork.IPV6Subnet,
Purpose: unifinetwork.Purpose,
Networkgroup: unifinetwork.NetworkGroup,
SettingPreference: unifinetwork.SettingPreference,
Vlan: int64(unifinetwork.VLAN),
VlanEnabled: unifinetwork.VLANEnabled,
}
networkStatus := unifiv1.NetworkconfigurationStatus{
FirewallZoneID: unifinetwork.FirewallZoneID,
}
log.Info(fmt.Sprintf("Network status %s: %+v", networkSpec.Name, networkStatus))
if _, found := networkNamesCRDs[unifinetwork.Name]; !found {
firewallZoneNamesCRDs := make(map[string]struct{})
firewallZoneIdsCRDs := make(map[string]struct{})
for _, firewallZoneCRD := range firewallZoneCRDs.Items {
firewallZoneNamesCRDs[firewallZoneCRD.Spec.Name] = struct{}{}
firewallZoneIdsCRDs[firewallZoneCRD.Spec.ID] = struct{}{}
}
networkCRD := &unifiv1.Networkconfiguration{
ObjectMeta: ctrl.ObjectMeta{
Name: toKubeName(unifinetwork.Name),
Namespace: defaultNs,
},
Spec: networkSpec,
Status: networkStatus,
}
err = r.Create(ctx, networkCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
err = r.Get(ctx, types.NamespacedName{Name: networkCRD.Name, Namespace: networkCRD.Namespace}, networkCRD)
networkCRD.Status = networkStatus
if err = r.Status().Update(ctx, networkCRD); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
} else {
log.Info(fmt.Sprintf("New network: %s with ID %s", network.Name, network.ID))
for _, networkCRD := range networkCRDs.Items {
if networkCRD.Spec.Name == unifinetwork.Name {
networkCRD.Spec = networkSpec
}
err := r.Update(ctx, &networkCRD)
if err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
if err = r.Status().Update(ctx, &networkCRD); err != nil {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, err
}
}
}
}
}
return ctrl.Result{}, nil
return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil
}
// SetupWithManager sets up the controller with the Manager.

View File

@@ -0,0 +1,33 @@
/* https://github.com/clbx/kube-port-forward-controller */
package unifi_network_operator_utils
import (
"regexp"
"strings"
)
func isIPv6(ip string) bool {
return strings.Contains(ip, ":")
}
func toKubeName(input string) string {
// Lowercase the input
name := strings.ToLower(input)
// Replace any non-alphanumeric characters with dashes
re := regexp.MustCompile(`[^a-z0-9\-\.]+`)
name = re.ReplaceAllString(name, "-")
// Trim leading and trailing non-alphanumerics
name = strings.Trim(name, "-.")
// Ensure it's not empty and doesn't exceed 253 characters
if len(name) == 0 {
name = "default"
} else if len(name) > 253 {
name = name[:253]
}
return name
}