Scaffold kea-operator: CRDs, controllers, config rendering, REST API, CI
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/test Pipeline failed

Replace the ISC dhcpd PXE-boot VM with a Kea DHCP Kubernetes operator, modelled
on bind-operator. The operator renders kea-dhcp4 config from CRs and runs an HA
pair of kea-dhcp4 + kea-ctrl-agent servers behind an anycast Service.

- add KeaCluster/KeaSubnet/KeaClientClass/KeaAPI CRDs (group kea.unkin.net)
- render deterministic kea-dhcp4.conf + kea-ctrl-agent.conf into a ConfigMap and
  roll the StatefulSet via a config-hash annotation; best-effort hot-reload via
  the kea-ctrl-agent REST channel
- run HA hot-standby (memfile leases) with stable per-peer DNS identity from a
  StatefulSet; expose an anycast LoadBalancer Service for PureLB
- represent the full legacy dhcpd config: 198.18.13-17.0/24 pools, pool-less
  198.18.25.0/24, and the Legacy/UEFI-64 PXE arch classes (option 93)
- add the KeaAPI-spawned REST service: Terraform-friendly CRUD over subnet and
  client-class CRs (stable IDs, PUT upsert, 404 drift, bearer-token auth)
- add Makefile (patch/minor/major tag targets), distroless operator/api images,
  an AlmaLinux+EPEL kea workload image, and woodpecker CI with k8s resources +
  serviceAccountName on every step
- unit tests for config rendering, controller reconcile/config-hash, and the API

Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
This commit is contained in:
unkinben
2026-08-02 17:19:53 +10:00
parent 9d471b0bff
commit d3fb5dcd1a
50 changed files with 10379 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
// +kubebuilder:object:generate=true
// +groupName=kea.unkin.net
package v1alpha1
+18
View File
@@ -0,0 +1,18 @@
// Package v1alpha1 contains the kea.unkin.net API types.
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is the group/version used to register these objects.
GroupVersion = schema.GroupVersion{Group: "kea.unkin.net", Version: "v1alpha1"}
// SchemeBuilder registers the kea.unkin.net types with a scheme.
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the kea.unkin.net types to a scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
+99
View File
@@ -0,0 +1,99 @@
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// KeaAPISpec defines the REST API service that manages KeaSubnet and
// KeaClientClass CRs (a Terraform-friendly alternative to argocd-managed CRs).
type KeaAPISpec struct {
// Replicas of the API server.
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=1
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// Image is the kea-api container image.
// +optional
Image string `json:"image,omitempty"`
// TokenSecretName is a Secret holding the bearer token under key "token".
// If the Secret does not exist the operator creates one with a random
// token (so it may instead be pre-seeded, e.g. by a Vault static secret).
// +optional
TokenSecretName string `json:"tokenSecretName,omitempty"`
// Service configures how the API is exposed.
// +optional
Service KeaAPIServiceSpec `json:"service,omitempty"`
// Resources for the API container.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
// +optional
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`
}
// KeaAPIServiceSpec configures the API Service.
type KeaAPIServiceSpec struct {
// +kubebuilder:default=ClusterIP
// +optional
Type corev1.ServiceType `json:"type,omitempty"`
// +kubebuilder:default=8080
// +optional
Port int32 `json:"port,omitempty"`
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
}
// KeaAPIStatus captures observed state.
type KeaAPIStatus struct {
// +optional
Phase string `json:"phase,omitempty"`
// Endpoint is the in-cluster base URL of the API.
// +optional
Endpoint string `json:"endpoint,omitempty"`
// +optional
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=kapi
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.status.endpoint`
// +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=`.status.readyReplicas`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// KeaAPI spawns the REST API service Deployment.
type KeaAPI struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KeaAPISpec `json:"spec,omitempty"`
Status KeaAPIStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KeaAPIList contains a list of KeaAPI.
type KeaAPIList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []KeaAPI `json:"items"`
}
func init() {
SchemeBuilder.Register(&KeaAPI{}, &KeaAPIList{})
}
+82
View File
@@ -0,0 +1,82 @@
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// KeaClientClassSpec defines a PXE boot client-class, typically matching the
// DHCP client architecture option (code 93).
type KeaClientClassSpec struct {
// ClusterRef selects the owning KeaCluster by name. Empty means every
// KeaCluster in the namespace.
// +optional
ClusterRef string `json:"clusterRef,omitempty"`
// Test is a raw Kea class-match expression. When empty it is generated
// from ArchHex.
// +optional
Test string `json:"test,omitempty"`
// ArchHex is a convenience list of client-architecture values (option 93),
// e.g. ["0x0000"] or ["0x0007","0x0009"]. Rendered into a Test expression
// of the form: option[93].hex == 0x0007 or option[93].hex == 0x0009.
// +optional
ArchHex []string `json:"archHex,omitempty"`
// BootFileName handed to matching clients (option 67 / boot-file-name).
// +optional
BootFileName string `json:"bootFileName,omitempty"`
// NextServer overrides siaddr for matching clients.
// +optional
NextServer string `json:"nextServer,omitempty"`
// ServerHostname (sname) for matching clients.
// +optional
ServerHostname string `json:"serverHostname,omitempty"`
// OptionData carries additional options set for matching clients.
// +optional
OptionData []OptionData `json:"optionData,omitempty"`
}
// KeaClientClassStatus captures observed state.
type KeaClientClassStatus struct {
// +optional
Phase string `json:"phase,omitempty"`
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=kcc
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
// +kubebuilder:printcolumn:name="BootFile",type=string,JSONPath=`.spec.bootFileName`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// KeaClientClass is a PXE boot class matched on the client architecture.
type KeaClientClass struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KeaClientClassSpec `json:"spec,omitempty"`
Status KeaClientClassStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KeaClientClassList contains a list of KeaClientClass.
type KeaClientClassList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []KeaClientClass `json:"items"`
}
func init() {
SchemeBuilder.Register(&KeaClientClass{}, &KeaClientClassList{})
}
+209
View File
@@ -0,0 +1,209 @@
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// HAMode selects the Kea High Availability hook operating mode.
// +kubebuilder:validation:Enum=hot-standby;load-balancing
type HAMode string
const (
// HAHotStandby runs one active (primary) server; the standby only serves
// when the primary is detected down. Preferred for PXE-only, low-volume,
// ephemeral leases where split-pool double allocation must be avoided.
HAHotStandby HAMode = "hot-standby"
// HALoadBalancing splits clients across both peers by a hash.
HALoadBalancing HAMode = "load-balancing"
)
// HASpec configures the libdhcp_ha hook shared between the cluster replicas.
type HASpec struct {
// Mode is the HA operating mode.
// +kubebuilder:default=hot-standby
// +optional
Mode HAMode `json:"mode,omitempty"`
// HeartbeatDelay is the interval (ms) between HA heartbeats.
// +kubebuilder:default=10000
// +kubebuilder:validation:Minimum=1000
// +optional
HeartbeatDelay int `json:"heartbeatDelay,omitempty"`
// MaxResponseDelay (ms) before a peer is considered unresponsive.
// +kubebuilder:default=60000
// +kubebuilder:validation:Minimum=1000
// +optional
MaxResponseDelay int `json:"maxResponseDelay,omitempty"`
// MaxAckDelay (ms) a client may wait before HA considers it unacked.
// +kubebuilder:default=5000
// +kubebuilder:validation:Minimum=0
// +optional
MaxAckDelay int `json:"maxAckDelay,omitempty"`
// MaxUnackedClients before a partner is declared to be in a failure state.
// +kubebuilder:default=5
// +kubebuilder:validation:Minimum=0
// +optional
MaxUnackedClients int `json:"maxUnackedClients,omitempty"`
}
// OptionDef is a custom DHCPv4 option definition (e.g. the PXE client
// architecture option, code 93).
type OptionDef struct {
Name string `json:"name"`
Code int `json:"code"`
// Type is a Kea option data type, e.g. "uint16", "string", "ipv4-address".
Type string `json:"type"`
// Space defaults to "dhcp4".
// +kubebuilder:default=dhcp4
// +optional
Space string `json:"space,omitempty"`
// Array marks the option as an array of Type.
// +optional
Array bool `json:"array,omitempty"`
// RecordTypes is a comma-separated list for record-type options.
// +optional
RecordTypes string `json:"recordTypes,omitempty"`
// Encapsulate names an option space this option encapsulates.
// +optional
Encapsulate string `json:"encapsulate,omitempty"`
}
// ClusterServiceSpec configures the anycast LoadBalancer Service that fronts
// the DHCP replicas. DHCP traffic arrives unicast via router relays to the
// anycast address (typically a PureLB-assigned IP), so no host networking is
// required.
type ClusterServiceSpec struct {
// Type of the DHCP Service. Defaults to LoadBalancer for anycast exposure.
// +kubebuilder:default=LoadBalancer
// +optional
Type corev1.ServiceType `json:"type,omitempty"`
// LoadBalancerIP requests a specific (anycast) address.
// +optional
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
// LoadBalancerClass selects the LB implementation (e.g. purelb.io/purelb).
// +optional
LoadBalancerClass *string `json:"loadBalancerClass,omitempty"`
// IPAddressPool is a convenience for the PureLB service-group annotation.
// +optional
IPAddressPool string `json:"ipAddressPool,omitempty"`
// Annotations are merged onto the Service (e.g. PureLB pool selection).
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
}
// KeaClusterSpec defines the desired state of a KeaCluster.
type KeaClusterSpec struct {
// Replicas is the number of kea-dhcp4 servers. HA is designed around 2.
// +kubebuilder:default=2
// +kubebuilder:validation:Minimum=1
// +optional
Replicas *int32 `json:"replicas,omitempty"`
// Image is the kea-dhcp4 + kea-ctrl-agent container image.
// +optional
Image string `json:"image,omitempty"`
// DomainName is the global domain-name option handed to clients.
// +optional
DomainName string `json:"domainName,omitempty"`
// DefaultLeaseTime in seconds (valid-lifetime).
// +kubebuilder:default=1200
// +kubebuilder:validation:Minimum=1
// +optional
DefaultLeaseTime int `json:"defaultLeaseTime,omitempty"`
// MaxLeaseTime in seconds (max-valid-lifetime).
// +kubebuilder:default=86400
// +kubebuilder:validation:Minimum=1
// +optional
MaxLeaseTime int `json:"maxLeaseTime,omitempty"`
// NTPServers are handed to clients as the ntp-servers option.
// +optional
NTPServers []string `json:"ntpServers,omitempty"`
// OptionDefs are custom option definitions (e.g. PXE client-arch, code 93).
// +optional
OptionDefs []OptionDef `json:"optionDefs,omitempty"`
// HA configures the libdhcp_ha hook between replicas.
// +optional
HA HASpec `json:"ha,omitempty"`
// Service configures the anycast LoadBalancer fronting the replicas.
// +optional
Service ClusterServiceSpec `json:"service,omitempty"`
// Resources for the kea containers.
// +optional
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
// +optional
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
// +optional
Tolerations []corev1.Toleration `json:"tolerations,omitempty"`
// +optional
Affinity *corev1.Affinity `json:"affinity,omitempty"`
}
// KeaClusterStatus captures the observed state of a KeaCluster.
type KeaClusterStatus struct {
// +optional
Phase string `json:"phase,omitempty"`
// +optional
Replicas int32 `json:"replicas,omitempty"`
// +optional
ReadyReplicas int32 `json:"readyReplicas,omitempty"`
// ActivePeer is the pod currently acting as the HA primary.
// +optional
ActivePeer string `json:"activePeer,omitempty"`
// ServiceIP is the anycast address assigned to the DHCP Service.
// +optional
ServiceIP string `json:"serviceIP,omitempty"`
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=kc
// +kubebuilder:printcolumn:name="Mode",type=string,JSONPath=`.spec.ha.mode`
// +kubebuilder:printcolumn:name="Ready",type=integer,JSONPath=`.status.readyReplicas`
// +kubebuilder:printcolumn:name="ServiceIP",type=string,JSONPath=`.status.serviceIP`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// KeaCluster spawns a StatefulSet of kea-dhcp4 servers with an HA control
// channel and an anycast DHCP Service.
type KeaCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KeaClusterSpec `json:"spec,omitempty"`
Status KeaClusterStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KeaClusterList contains a list of KeaCluster.
type KeaClusterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []KeaCluster `json:"items"`
}
func init() {
SchemeBuilder.Register(&KeaCluster{}, &KeaClusterList{})
}
+121
View File
@@ -0,0 +1,121 @@
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// OptionData is a rendered DHCPv4 option value (subnet- or class-scoped).
type OptionData struct {
// Name of the option, e.g. "routers", "domain-name-servers".
// +optional
Name string `json:"name,omitempty"`
// Code of the option (alternative to Name).
// +optional
Code int `json:"code,omitempty"`
// Data is the option value(s), comma-separated per Kea convention.
Data string `json:"data"`
// Space defaults to "dhcp4".
// +optional
Space string `json:"space,omitempty"`
// CSVFormat controls whether Data is parsed as CSV (default true in Kea).
// +optional
CSVFormat *bool `json:"csvFormat,omitempty"`
}
// KeaSubnetSpec defines a single DHCPv4 subnet served by a KeaCluster.
type KeaSubnetSpec struct {
// ClusterRef selects the owning KeaCluster by name. Empty means every
// KeaCluster in the namespace.
// +optional
ClusterRef string `json:"clusterRef,omitempty"`
// Subnet is the CIDR, e.g. "198.18.13.0/24".
Subnet string `json:"subnet"`
// ID is the stable Kea subnet id. When zero the operator assigns one
// deterministically from the sorted set of subnets.
// +optional
ID int `json:"id,omitempty"`
// Pools are dynamic ranges, e.g. "198.18.13.200 - 198.18.13.220". A subnet
// with no pool is still declared so relayed requests on that network are
// serviced (matching, option delivery) without dynamic allocation.
// +optional
Pools []string `json:"pools,omitempty"`
// Routers is the default-gateway list (routers option).
// +optional
Routers []string `json:"routers,omitempty"`
// DNSServers is the domain-name-servers option.
// +optional
DNSServers []string `json:"dnsServers,omitempty"`
// DomainName is the per-subnet domain-name option.
// +optional
DomainName string `json:"domainName,omitempty"`
// NextServer is the TFTP server address for PXE (siaddr / next-server).
// +optional
NextServer string `json:"nextServer,omitempty"`
// BootFileName sets the PXE boot file for this subnet (overridden by class).
// +optional
BootFileName string `json:"bootFileName,omitempty"`
// ClientClasses restricts the subnet to the listed client classes.
// +optional
ClientClasses []string `json:"clientClasses,omitempty"`
// ValidLifetime overrides the cluster default lease time for this subnet.
// +optional
ValidLifetime int `json:"validLifetime,omitempty"`
// OptionData carries any additional option values for the subnet.
// +optional
OptionData []OptionData `json:"optionData,omitempty"`
}
// KeaSubnetStatus captures observed state.
type KeaSubnetStatus struct {
// +optional
Phase string `json:"phase,omitempty"`
// AssignedID is the subnet id that was rendered into kea config.
// +optional
AssignedID int `json:"assignedID,omitempty"`
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=ksn
// +kubebuilder:printcolumn:name="Cluster",type=string,JSONPath=`.spec.clusterRef`
// +kubebuilder:printcolumn:name="Subnet",type=string,JSONPath=`.spec.subnet`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// KeaSubnet is one DHCPv4 subnet declaration referenced to a KeaCluster.
type KeaSubnet struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec KeaSubnetSpec `json:"spec,omitempty"`
Status KeaSubnetStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// KeaSubnetList contains a list of KeaSubnet.
type KeaSubnetList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []KeaSubnet `json:"items"`
}
func init() {
SchemeBuilder.Register(&KeaSubnet{}, &KeaSubnetList{})
}
+596
View File
@@ -0,0 +1,596 @@
//go:build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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 *ClusterServiceSpec) DeepCopyInto(out *ClusterServiceSpec) {
*out = *in
if in.LoadBalancerClass != nil {
in, out := &in.LoadBalancerClass, &out.LoadBalancerClass
*out = new(string)
**out = **in
}
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterServiceSpec.
func (in *ClusterServiceSpec) DeepCopy() *ClusterServiceSpec {
if in == nil {
return nil
}
out := new(ClusterServiceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HASpec) DeepCopyInto(out *HASpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HASpec.
func (in *HASpec) DeepCopy() *HASpec {
if in == nil {
return nil
}
out := new(HASpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaAPI) DeepCopyInto(out *KeaAPI) {
*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 KeaAPI.
func (in *KeaAPI) DeepCopy() *KeaAPI {
if in == nil {
return nil
}
out := new(KeaAPI)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaAPI) 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 *KeaAPIList) DeepCopyInto(out *KeaAPIList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KeaAPI, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaAPIList.
func (in *KeaAPIList) DeepCopy() *KeaAPIList {
if in == nil {
return nil
}
out := new(KeaAPIList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaAPIList) 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 *KeaAPIServiceSpec) DeepCopyInto(out *KeaAPIServiceSpec) {
*out = *in
if in.Annotations != nil {
in, out := &in.Annotations, &out.Annotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaAPIServiceSpec.
func (in *KeaAPIServiceSpec) DeepCopy() *KeaAPIServiceSpec {
if in == nil {
return nil
}
out := new(KeaAPIServiceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaAPISpec) DeepCopyInto(out *KeaAPISpec) {
*out = *in
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(int32)
**out = **in
}
in.Service.DeepCopyInto(&out.Service)
in.Resources.DeepCopyInto(&out.Resources)
if in.NodeSelector != nil {
in, out := &in.NodeSelector, &out.NodeSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Tolerations != nil {
in, out := &in.Tolerations, &out.Tolerations
*out = make([]v1.Toleration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Affinity != nil {
in, out := &in.Affinity, &out.Affinity
*out = new(v1.Affinity)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaAPISpec.
func (in *KeaAPISpec) DeepCopy() *KeaAPISpec {
if in == nil {
return nil
}
out := new(KeaAPISpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaAPIStatus) DeepCopyInto(out *KeaAPIStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaAPIStatus.
func (in *KeaAPIStatus) DeepCopy() *KeaAPIStatus {
if in == nil {
return nil
}
out := new(KeaAPIStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaClientClass) DeepCopyInto(out *KeaClientClass) {
*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 KeaClientClass.
func (in *KeaClientClass) DeepCopy() *KeaClientClass {
if in == nil {
return nil
}
out := new(KeaClientClass)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaClientClass) 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 *KeaClientClassList) DeepCopyInto(out *KeaClientClassList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KeaClientClass, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaClientClassList.
func (in *KeaClientClassList) DeepCopy() *KeaClientClassList {
if in == nil {
return nil
}
out := new(KeaClientClassList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaClientClassList) 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 *KeaClientClassSpec) DeepCopyInto(out *KeaClientClassSpec) {
*out = *in
if in.ArchHex != nil {
in, out := &in.ArchHex, &out.ArchHex
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.OptionData != nil {
in, out := &in.OptionData, &out.OptionData
*out = make([]OptionData, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaClientClassSpec.
func (in *KeaClientClassSpec) DeepCopy() *KeaClientClassSpec {
if in == nil {
return nil
}
out := new(KeaClientClassSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaClientClassStatus) DeepCopyInto(out *KeaClientClassStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaClientClassStatus.
func (in *KeaClientClassStatus) DeepCopy() *KeaClientClassStatus {
if in == nil {
return nil
}
out := new(KeaClientClassStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaCluster) DeepCopyInto(out *KeaCluster) {
*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 KeaCluster.
func (in *KeaCluster) DeepCopy() *KeaCluster {
if in == nil {
return nil
}
out := new(KeaCluster)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaCluster) 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 *KeaClusterList) DeepCopyInto(out *KeaClusterList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KeaCluster, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaClusterList.
func (in *KeaClusterList) DeepCopy() *KeaClusterList {
if in == nil {
return nil
}
out := new(KeaClusterList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaClusterList) 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 *KeaClusterSpec) DeepCopyInto(out *KeaClusterSpec) {
*out = *in
if in.Replicas != nil {
in, out := &in.Replicas, &out.Replicas
*out = new(int32)
**out = **in
}
if in.NTPServers != nil {
in, out := &in.NTPServers, &out.NTPServers
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.OptionDefs != nil {
in, out := &in.OptionDefs, &out.OptionDefs
*out = make([]OptionDef, len(*in))
copy(*out, *in)
}
out.HA = in.HA
in.Service.DeepCopyInto(&out.Service)
in.Resources.DeepCopyInto(&out.Resources)
if in.NodeSelector != nil {
in, out := &in.NodeSelector, &out.NodeSelector
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Tolerations != nil {
in, out := &in.Tolerations, &out.Tolerations
*out = make([]v1.Toleration, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Affinity != nil {
in, out := &in.Affinity, &out.Affinity
*out = new(v1.Affinity)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaClusterSpec.
func (in *KeaClusterSpec) DeepCopy() *KeaClusterSpec {
if in == nil {
return nil
}
out := new(KeaClusterSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaClusterStatus) DeepCopyInto(out *KeaClusterStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaClusterStatus.
func (in *KeaClusterStatus) DeepCopy() *KeaClusterStatus {
if in == nil {
return nil
}
out := new(KeaClusterStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaSubnet) DeepCopyInto(out *KeaSubnet) {
*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 KeaSubnet.
func (in *KeaSubnet) DeepCopy() *KeaSubnet {
if in == nil {
return nil
}
out := new(KeaSubnet)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaSubnet) 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 *KeaSubnetList) DeepCopyInto(out *KeaSubnetList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KeaSubnet, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaSubnetList.
func (in *KeaSubnetList) DeepCopy() *KeaSubnetList {
if in == nil {
return nil
}
out := new(KeaSubnetList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *KeaSubnetList) 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 *KeaSubnetSpec) DeepCopyInto(out *KeaSubnetSpec) {
*out = *in
if in.Pools != nil {
in, out := &in.Pools, &out.Pools
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Routers != nil {
in, out := &in.Routers, &out.Routers
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DNSServers != nil {
in, out := &in.DNSServers, &out.DNSServers
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ClientClasses != nil {
in, out := &in.ClientClasses, &out.ClientClasses
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.OptionData != nil {
in, out := &in.OptionData, &out.OptionData
*out = make([]OptionData, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaSubnetSpec.
func (in *KeaSubnetSpec) DeepCopy() *KeaSubnetSpec {
if in == nil {
return nil
}
out := new(KeaSubnetSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KeaSubnetStatus) DeepCopyInto(out *KeaSubnetStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeaSubnetStatus.
func (in *KeaSubnetStatus) DeepCopy() *KeaSubnetStatus {
if in == nil {
return nil
}
out := new(KeaSubnetStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OptionData) DeepCopyInto(out *OptionData) {
*out = *in
if in.CSVFormat != nil {
in, out := &in.CSVFormat, &out.CSVFormat
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionData.
func (in *OptionData) DeepCopy() *OptionData {
if in == nil {
return nil
}
out := new(OptionData)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OptionDef) DeepCopyInto(out *OptionDef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionDef.
func (in *OptionDef) DeepCopy() *OptionDef {
if in == nil {
return nil
}
out := new(OptionDef)
in.DeepCopyInto(out)
return out
}