diff --git a/chart/crds/network.harvesterhci.io_ippools.yaml b/chart/crds/network.harvesterhci.io_ippools.yaml index 613bf874..ef36115b 100644 --- a/chart/crds/network.harvesterhci.io_ippools.yaml +++ b/chart/crds/network.harvesterhci.io_ippools.yaml @@ -150,7 +150,7 @@ spec: type: object status: properties: - agentPodRef: + agentDeploymentRef: properties: image: type: string diff --git a/chart/templates/rbac.yaml b/chart/templates/rbac.yaml index 9d75474d..633698e5 100644 --- a/chart/templates/rbac.yaml +++ b/chart/templates/rbac.yaml @@ -18,9 +18,9 @@ rules: - apiGroups: [ "" ] resources: [ "namespaces" ] verbs: [ "get", "watch", "list" ] -- apiGroups: [ "" ] - resources: [ "pods" ] - verbs: [ "watch", "list" ] +- apiGroups: [ "apps" ] + resources: [ "deployments" ] + verbs: [ "get", "watch", "list" ] - apiGroups: [ "kubevirt.io" ] resources: [ "virtualmachines" ] verbs: [ "get", "watch", "list" ] @@ -122,11 +122,11 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: {{ include "harvester-vm-dhcp-controller.name" . }}-pod-manager + name: {{ include "harvester-vm-dhcp-controller.name" . }}-deployment-manager rules: -- apiGroups: [ "" ] - resources: [ "pods" ] - verbs: [ "get", "create", "delete" ] +- apiGroups: [ "apps" ] + resources: [ "deployments" ] + verbs: [ "get", "create", "update", "delete" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role @@ -157,13 +157,13 @@ subjects: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: {{ include "harvester-vm-dhcp-controller.name" . }}-manage-pods + name: {{ include "harvester-vm-dhcp-controller.name" . }}-manage-deployments labels: {{- include "harvester-vm-dhcp-controller.labels" . | nindent 4 }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: {{ include "harvester-vm-dhcp-controller.name" . }}-pod-manager + name: {{ include "harvester-vm-dhcp-controller.name" . }}-deployment-manager subjects: - kind: ServiceAccount name: {{ include "harvester-vm-dhcp-controller.serviceAccountName" . }} diff --git a/docs/ippool-agent-deployment.md b/docs/ippool-agent-deployment.md new file mode 100644 index 00000000..be4243d4 --- /dev/null +++ b/docs/ippool-agent-deployment.md @@ -0,0 +1,129 @@ +--- +name: ippool-agent-deployment-reconcile +description: Plan to move IPPool agents from Pods to Deployments and reconcile via operator pattern. +--- + +# Plan + +Obiettivo: passare da agent Pod a Deployment per ogni IPPool e riconciliare con pattern operator, aggiornando API, controller, RBAC e test. + +## Requirements +- Ogni IPPool crea/gestisce un Deployment agent (repliche=1) con la stessa logica di rete/affinity/args attuale. +- Reconcile idempotente: crea/aggiorna/monitor/cleanup del Deployment. +- Upgrade immagine rispettando `hold-ippool-agent-upgrade`. + +## Scope +- In: IPPool controller, API/CRD status, codegen, RBAC, test. +- Out: logica DHCP/IPAM/VMNetCfg non correlata. + +## Files and entry points +- `pkg/controller/ippool/controller.go` +- `pkg/controller/ippool/common.go` +- `pkg/apis/network.harvesterhci.io/v1alpha1/ippool.go` +- `pkg/codegen/main.go`, `pkg/config/context.go` +- `pkg/util/fakeclient`, `pkg/controller/ippool/controller_test.go` +- `chart/templates/rbac.yaml`, `chart/crds/network.harvesterhci.io_ippools.yaml` +- `pkg/data/data.go` + +## Data model / API changes +- Sostituire `status.agentPodRef` con `status.agentDeploymentRef` (nuovo `DeploymentReference`). + +## Action items +[ ] Aggiungere client/controller apps/v1 Deployment (codegen + Management) e fakeclient. +[ ] Implementare `prepareAgentDeployment` e aggiornare Deploy/Monitor/Cleanup per Deployment. +[ ] Aggiornare watch `relatedresource` su Deployment con label ippool. +[ ] Aggiornare builder/status helpers e test per Deployment. +[ ] Aggiornare RBAC per `deployments` (get/list/watch/create/update/delete). +[ ] Rigenerare codegen/CRD/bindata. + +## Testing and validation +- `go test ./...` (o `go test ./pkg/controller/ippool -run TestHandler_`). +- `go generate` per rigenerare CRD/client/bindata. + +## Risks and edge cases +- Breaking change CRD status. +- Strategia Deployment (Recreate vs RollingUpdate) impatta continuita DHCP. +- Namespace agent != IPPool: relazione via label, non ownerRef. + +## Open questions +- Posso usare `agentDeploymentRef` al posto di `agentPodRef` anche se breaking? +- Preferisci `Recreate` o `RollingUpdate` per i Deployment agent? + +--- + +# IPPool agent su Deployment + +Questo documento descrive logica e modifiche introdotte per spostare l'agent IPPool +da Pod singolo a Deployment, con riconciliazione di tipo operator e strategia +RollingUpdate. + +## Obiettivo +- Ogni IPPool crea e gestisce un Deployment dedicato (repliche=1). +- Riconciliazione idempotente che crea o aggiorna il Deployment quando cambia la configurazione. +- Upgrade immagine controllato dall'annotazione `network.harvesterhci.io/hold-ippool-agent-upgrade`. +- Migrazione centrata sul nuovo riferimento di stato `agentDeploymentRef`. + +## Flusso di riconciliazione +### DeployAgent +- Recupera ClusterNetwork dalla NetworkAttachmentDefinition (label `network.harvesterhci.io/clusternetwork`). +- Calcola l'immagine desiderata (rispetta l'annotazione di hold). +- Costruisce il Deployment desiderato via `prepareAgentDeployment`. +- Se `status.agentDeploymentRef` e valorizzato: + - Verifica UID e che il Deployment non sia in deletion. + - Richiede selector immutabile (errore se diverge). + - Aggiorna labels, strategy, replicas, template e container se differiscono. + - Aggiorna `agentDeploymentRef` con namespace/nome/UID. +- Se il Deployment non esiste o lo status e vuoto, crea il Deployment e registra lo status. + +### MonitorAgent +- Se `noAgent` e true, non fa nulla; se IPPool e in pausa, ritorna errore. +- Verifica esistenza, UID e immagine del container rispetto allo status. +- Verifica readiness usando `ObservedGeneration` e repliche `Updated/Available`. +- In caso di mismatch o non readiness, ritorna errore (non cancella il Deployment). + +### Cleanup +- Elimina il Deployment associato e pulisce IPAM/MAC/metriche. +- Usato su pause o rimozione IPPool. + +## Dettagli implementativi +- Nome Deployment derivato da `util.SafeAgentConcatName`. +- Labels: `network.harvesterhci.io/vm-dhcp-controller=agent` + labels IPPool namespace/nome. +- Pod template con annotazione Multus `k8s.v1.cni.cncf.io/networks`. +- Init container `ip-setter` per configurare `eth1`; container `agent` con probes `/healthz` e `/readyz`. +- Container defaults (ImagePullPolicy e TerminationMessage*) esplicitati per evitare reconcile loop. +- Strategia RollingUpdate: `maxUnavailable=0`, `maxSurge=1`. + +## Cambiamenti di stato/CRD +- `status.agentPodRef` sostituito da `status.agentDeploymentRef`. +- Nuova struct `DeploymentReference` con namespace, name, image, UID. +- CRD aggiornata e bindata rigenerata. + +## Watch e relazione risorse +- Watch su Deployment con label `vm-dhcp-controller=agent`. +- Mapping IPPool tramite label `ippool-namespace` e `ippool-name`. + +## RBAC +- Aggiunti permessi `deployments` per controller (get/list/watch). +- Role dedicato `*-deployment-manager` con create/update/delete. +- Binding aggiornato da `manage-pods` a `manage-deployments`. + +## Codegen e generated +- Aggiunto gruppo apps/v1 alla codegen per controller/cache Deployment. +- Nuovi controller generati in `pkg/generated/controllers/apps`. +- Nuovo fake client per Deployment in `pkg/util/fakeclient/deployment.go`. + +## Modifiche al codice (file principali) +- `pkg/controller/ippool/controller.go`: DeployAgent/MonitorAgent/cleanup aggiornati per Deployment. +- `pkg/controller/ippool/common.go`: `prepareAgentDeployment` e builder di supporto. +- `pkg/apis/network.harvesterhci.io/v1alpha1/ippool.go`: nuovo status field e tipo reference. +- `pkg/config/context.go`: AppsFactory aggiunta al bootstrap. +- `pkg/codegen/main.go`: codegen apps/v1 e rigenerazione dei controller. +- `chart/templates/rbac.yaml` e `chart/crds/network.harvesterhci.io_ippools.yaml`: RBAC/CRD aggiornate. + +## Compatibilita e migrazione +- `status.agentPodRef` non e piu letto; eventuali Pod legacy non sono piu gestiti. +- RollingUpdate con `maxSurge=1` puo generare un secondo agent temporaneo. +- Se esiste un Deployment con selector differente, la reconcile fallisce per evitare adozioni errate. + +## Test +- Eseguito `go test ./...`. diff --git a/pkg/apis/network.harvesterhci.io/v1alpha1/ippool.go b/pkg/apis/network.harvesterhci.io/v1alpha1/ippool.go index 7d09375e..44cac0bc 100644 --- a/pkg/apis/network.harvesterhci.io/v1alpha1/ippool.go +++ b/pkg/apis/network.harvesterhci.io/v1alpha1/ippool.go @@ -119,7 +119,7 @@ type IPPoolStatus struct { // +optional // +kubebuilder:validation:Optional - AgentPodRef *PodReference `json:"agentPodRef,omitempty"` + AgentDeploymentRef *DeploymentReference `json:"agentDeploymentRef,omitempty"` // +optional // +kubebuilder:validation:Optional @@ -132,7 +132,7 @@ type IPv4Status struct { Available int `json:"available"` } -type PodReference struct { +type DeploymentReference struct { Namespace string `json:"namespace,omitempty"` Name string `json:"name,omitempty"` Image string `json:"image,omitempty"` diff --git a/pkg/apis/network.harvesterhci.io/v1alpha1/zz_generated_deepcopy.go b/pkg/apis/network.harvesterhci.io/v1alpha1/zz_generated_deepcopy.go index bbcfeb71..5a483a2d 100644 --- a/pkg/apis/network.harvesterhci.io/v1alpha1/zz_generated_deepcopy.go +++ b/pkg/apis/network.harvesterhci.io/v1alpha1/zz_generated_deepcopy.go @@ -26,6 +26,22 @@ 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 *DeploymentReference) DeepCopyInto(out *DeploymentReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeploymentReference. +func (in *DeploymentReference) DeepCopy() *DeploymentReference { + if in == nil { + return nil + } + out := new(DeploymentReference) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPPool) DeepCopyInto(out *IPPool) { *out = *in @@ -118,9 +134,9 @@ func (in *IPPoolStatus) DeepCopyInto(out *IPPoolStatus) { *out = new(IPv4Status) (*in).DeepCopyInto(*out) } - if in.AgentPodRef != nil { - in, out := &in.AgentPodRef, &out.AgentPodRef - *out = new(PodReference) + if in.AgentDeploymentRef != nil { + in, out := &in.AgentDeploymentRef, &out.AgentDeploymentRef + *out = new(DeploymentReference) **out = **in } if in.Conditions != nil { @@ -243,22 +259,6 @@ func (in *NetworkConfigStatus) DeepCopy() *NetworkConfigStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PodReference) DeepCopyInto(out *PodReference) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodReference. -func (in *PodReference) DeepCopy() *PodReference { - if in == nil { - return nil - } - out := new(PodReference) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pool) DeepCopyInto(out *Pool) { *out = *in diff --git a/pkg/codegen/main.go b/pkg/codegen/main.go index 5ef411f2..902ca7ac 100644 --- a/pkg/codegen/main.go +++ b/pkg/codegen/main.go @@ -9,6 +9,7 @@ import ( controllergen "github.com/rancher/wrangler/v3/pkg/controller-gen" "github.com/rancher/wrangler/v3/pkg/controller-gen/args" "github.com/sirupsen/logrus" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" kubevirtv1 "kubevirt.io/api/core/v1" ) @@ -48,6 +49,11 @@ func main() { corev1.Pod{}, }, }, + appsv1.SchemeGroupVersion.Group: { + Types: []interface{}{ + appsv1.Deployment{}, + }, + }, cniv1.SchemeGroupVersion.Group: { Types: []interface{}{ cniv1.NetworkAttachmentDefinition{}, diff --git a/pkg/config/context.go b/pkg/config/context.go index b45be570..bd31e27b 100644 --- a/pkg/config/context.go +++ b/pkg/config/context.go @@ -24,6 +24,7 @@ import ( "github.com/harvester/vm-dhcp-controller/pkg/cache" "github.com/harvester/vm-dhcp-controller/pkg/crd" "github.com/harvester/vm-dhcp-controller/pkg/dhcp" + ctlapps "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/apps" ctlcore "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/core" ctlcni "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/k8s.cni.cncf.io" ctlkubevirt "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/kubevirt.io" @@ -96,6 +97,7 @@ type Management struct { HarvesterNetworkFactory *ctlnetwork.Factory + AppsFactory *ctlapps.Factory CniFactory *ctlcni.Factory CoreFactory *ctlcore.Factory KubeVirtFactory *ctlkubevirt.Factory @@ -169,6 +171,13 @@ func SetupManagement(ctx context.Context, restConfig *rest.Config, options *Cont management.CoreFactory = core management.starters = append(management.starters, core) + apps, err := ctlapps.NewFactoryFromConfigWithOptions(restConfig, opts) + if err != nil { + return nil, err + } + management.AppsFactory = apps + management.starters = append(management.starters, apps) + cni, err := ctlcni.NewFactoryFromConfigWithOptions(restConfig, opts) if err != nil { return nil, err diff --git a/pkg/controller/ippool/common.go b/pkg/controller/ippool/common.go index 98b20ab8..09d3ab0c 100644 --- a/pkg/controller/ippool/common.go +++ b/pkg/controller/ippool/common.go @@ -8,6 +8,7 @@ import ( cniv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" "github.com/rancher/wrangler/v3/pkg/kv" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -15,18 +16,17 @@ import ( "github.com/harvester/vm-dhcp-controller/pkg/apis/network.harvesterhci.io" networkv1 "github.com/harvester/vm-dhcp-controller/pkg/apis/network.harvesterhci.io/v1alpha1" - "github.com/harvester/vm-dhcp-controller/pkg/config" "github.com/harvester/vm-dhcp-controller/pkg/util" ) -func prepareAgentPod( +func prepareAgentDeployment( ipPool *networkv1.IPPool, noDHCP bool, agentNamespace string, clusterNetwork string, agentServiceAccountName string, - agentImage *config.Image, -) (*corev1.Pod, error) { + agentImage string, +) (*appsv1.Deployment, error) { name := util.SafeAgentConcatName(ipPool.Namespace, ipPool.Name) nadNamespace, nadName := kv.RSplit(ipPool.Spec.NetworkName, "/") @@ -56,94 +56,122 @@ func prepareAgentPod( args = append(args, "--dry-run") } - return &corev1.Pod{ + labels := map[string]string{ + vmDHCPControllerLabelKey: "agent", + util.IPPoolNamespaceLabelKey: ipPool.Namespace, + util.IPPoolNameLabelKey: ipPool.Name, + } + replicas := int32(1) + maxUnavailable := intstr.FromInt(0) + maxSurge := intstr.FromInt(1) + + return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - multusNetworksAnnotationKey: string(networksStr), - }, - Labels: map[string]string{ - vmDHCPControllerLabelKey: "agent", - util.IPPoolNamespaceLabelKey: ipPool.Namespace, - util.IPPoolNameLabelKey: ipPool.Name, - }, + Labels: labels, Name: name, Namespace: agentNamespace, }, - Spec: corev1.PodSpec{ - Affinity: &corev1.Affinity{ - NodeAffinity: &corev1.NodeAffinity{ - RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ - NodeSelectorTerms: []corev1.NodeSelectorTerm{ - { - MatchExpressions: []corev1.NodeSelectorRequirement{ + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RollingUpdateDeploymentStrategyType, + RollingUpdate: &appsv1.RollingUpdateDeployment{ + MaxUnavailable: &maxUnavailable, + MaxSurge: &maxSurge, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + multusNetworksAnnotationKey: string(networksStr), + }, + Labels: labels, + }, + Spec: corev1.PodSpec{ + Affinity: &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: []corev1.NodeSelectorTerm{ { - Key: network.GroupName + "/" + clusterNetwork, - Operator: corev1.NodeSelectorOpIn, - Values: []string{ - "true", + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: network.GroupName + "/" + clusterNetwork, + Operator: corev1.NodeSelectorOpIn, + Values: []string{ + "true", + }, + }, }, }, }, }, }, }, - }, - }, - ServiceAccountName: agentServiceAccountName, - InitContainers: []corev1.Container{ - { - Name: "ip-setter", - Image: agentImage.String(), - ImagePullPolicy: corev1.PullIfNotPresent, - Command: []string{ - "/bin/sh", - "-c", - fmt.Sprintf(setIPAddrScript, ipPool.Spec.IPv4Config.ServerIP, prefixLength), - }, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: &runAsUserID, - RunAsGroup: &runAsGroupID, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "NET_ADMIN", + ServiceAccountName: agentServiceAccountName, + InitContainers: []corev1.Container{ + { + Name: "ip-setter", + Image: agentImage, + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePath: corev1.TerminationMessagePathDefault, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + Command: []string{ + "/bin/sh", + "-c", + fmt.Sprintf(setIPAddrScript, ipPool.Spec.IPv4Config.ServerIP, prefixLength), + }, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: &runAsUserID, + RunAsGroup: &runAsGroupID, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_ADMIN", + }, + }, }, }, }, - }, - }, - Containers: []corev1.Container{ - { - Name: "agent", - Image: agentImage.String(), - Args: args, - Env: []corev1.EnvVar{ + Containers: []corev1.Container{ { - Name: "VM_DHCP_AGENT_NAME", - Value: name, - }, - }, - SecurityContext: &corev1.SecurityContext{ - RunAsUser: &runAsUserID, - RunAsGroup: &runAsGroupID, - Capabilities: &corev1.Capabilities{ - Add: []corev1.Capability{ - "NET_ADMIN", + Name: "agent", + Image: agentImage, + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePath: corev1.TerminationMessagePathDefault, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + Args: args, + Env: []corev1.EnvVar{ + { + Name: "VM_DHCP_AGENT_NAME", + Value: name, + }, }, - }, - }, - LivenessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/healthz", - Port: intstr.FromInt(8080), + SecurityContext: &corev1.SecurityContext{ + RunAsUser: &runAsUserID, + RunAsGroup: &runAsGroupID, + Capabilities: &corev1.Capabilities{ + Add: []corev1.Capability{ + "NET_ADMIN", + }, + }, }, - }, - }, - ReadinessProbe: &corev1.Probe{ - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/readyz", - Port: intstr.FromInt(8080), + LivenessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/healthz", + Port: intstr.FromInt(8080), + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/readyz", + Port: intstr.FromInt(8080), + }, + }, }, }, }, @@ -243,14 +271,14 @@ func (b *IPPoolBuilder) Exclude(ipAddressList ...string) *IPPoolBuilder { return b } -func (b *IPPoolBuilder) AgentPodRef(namespace, name, image, uid string) *IPPoolBuilder { - if b.ipPool.Status.AgentPodRef == nil { - b.ipPool.Status.AgentPodRef = new(networkv1.PodReference) +func (b *IPPoolBuilder) AgentDeploymentRef(namespace, name, image, uid string) *IPPoolBuilder { + if b.ipPool.Status.AgentDeploymentRef == nil { + b.ipPool.Status.AgentDeploymentRef = new(networkv1.DeploymentReference) } - b.ipPool.Status.AgentPodRef.Namespace = namespace - b.ipPool.Status.AgentPodRef.Name = name - b.ipPool.Status.AgentPodRef.Image = image - b.ipPool.Status.AgentPodRef.UID = types.UID(uid) + b.ipPool.Status.AgentDeploymentRef.Namespace = namespace + b.ipPool.Status.AgentDeploymentRef.Name = name + b.ipPool.Status.AgentDeploymentRef.Image = image + b.ipPool.Status.AgentDeploymentRef.UID = types.UID(uid) return b } @@ -315,14 +343,14 @@ func newIPPoolStatusBuilder() *ipPoolStatusBuilder { } } -func (b *ipPoolStatusBuilder) AgentPodRef(namespace, name, image, uid string) *ipPoolStatusBuilder { - if b.ipPoolStatus.AgentPodRef == nil { - b.ipPoolStatus.AgentPodRef = new(networkv1.PodReference) +func (b *ipPoolStatusBuilder) AgentDeploymentRef(namespace, name, image, uid string) *ipPoolStatusBuilder { + if b.ipPoolStatus.AgentDeploymentRef == nil { + b.ipPoolStatus.AgentDeploymentRef = new(networkv1.DeploymentReference) } - b.ipPoolStatus.AgentPodRef.Namespace = namespace - b.ipPoolStatus.AgentPodRef.Name = name - b.ipPoolStatus.AgentPodRef.Image = image - b.ipPoolStatus.AgentPodRef.UID = types.UID(uid) + b.ipPoolStatus.AgentDeploymentRef.Namespace = namespace + b.ipPoolStatus.AgentDeploymentRef.Name = name + b.ipPoolStatus.AgentDeploymentRef.Image = image + b.ipPoolStatus.AgentDeploymentRef.UID = types.UID(uid) return b } @@ -358,52 +386,55 @@ func (b *ipPoolStatusBuilder) Build() networkv1.IPPoolStatus { return b.ipPoolStatus } -type podBuilder struct { - pod *corev1.Pod +type deploymentBuilder struct { + deployment *appsv1.Deployment } -func newPodBuilder(namespace, name string) *podBuilder { - return &podBuilder{ - pod: &corev1.Pod{ +func newDeploymentBuilder(namespace, name string) *deploymentBuilder { + replicas := int32(1) + return &deploymentBuilder{ + deployment: &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: name, }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Template: corev1.PodTemplateSpec{}, + }, }, } } -func (b *podBuilder) Container(name, repository, tag string) *podBuilder { +func (b *deploymentBuilder) Container(name, repository, tag string) *deploymentBuilder { container := corev1.Container{ Name: name, Image: repository + ":" + tag, } - b.pod.Spec.Containers = append(b.pod.Spec.Containers, container) + b.deployment.Spec.Template.Spec.Containers = append(b.deployment.Spec.Template.Spec.Containers, container) return b } -func (b *podBuilder) PodReady(ready corev1.ConditionStatus) *podBuilder { - var found bool - if b.pod.Status.Conditions == nil { - b.pod.Status.Conditions = make([]corev1.PodCondition, 0, 1) +func (b *deploymentBuilder) DeploymentReady(ready bool) *deploymentBuilder { + if !ready { + return b } - for i := range b.pod.Status.Conditions { - if b.pod.Status.Conditions[i].Type == corev1.PodReady { - b.pod.Status.Conditions[i].Status = corev1.ConditionTrue - break - } + if b.deployment.Generation == 0 { + b.deployment.Generation = 1 } - if !found { - b.pod.Status.Conditions = append(b.pod.Status.Conditions, corev1.PodCondition{ - Type: corev1.PodReady, - Status: corev1.ConditionTrue, - }) + desired := int32(1) + if b.deployment.Spec.Replicas != nil { + desired = *b.deployment.Spec.Replicas } + b.deployment.Status.UpdatedReplicas = desired + b.deployment.Status.AvailableReplicas = desired + b.deployment.Status.ReadyReplicas = desired + b.deployment.Status.ObservedGeneration = b.deployment.Generation return b } -func (b *podBuilder) Build() *corev1.Pod { - return b.pod +func (b *deploymentBuilder) Build() *appsv1.Deployment { + return b.deployment } type NetworkAttachmentDefinitionBuilder struct { diff --git a/pkg/controller/ippool/controller.go b/pkg/controller/ippool/controller.go index a8f5ee16..4bf0681a 100644 --- a/pkg/controller/ippool/controller.go +++ b/pkg/controller/ippool/controller.go @@ -8,7 +8,7 @@ import ( "github.com/rancher/wrangler/v3/pkg/kv" "github.com/rancher/wrangler/v3/pkg/relatedresource" "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" + appsv1 "k8s.io/api/apps/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -18,7 +18,7 @@ import ( networkv1 "github.com/harvester/vm-dhcp-controller/pkg/apis/network.harvesterhci.io/v1alpha1" "github.com/harvester/vm-dhcp-controller/pkg/cache" "github.com/harvester/vm-dhcp-controller/pkg/config" - ctlcorev1 "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/core/v1" + ctlappsv1 "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/apps/v1" ctlcniv1 "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/k8s.cni.cncf.io/v1" ctlnetworkv1 "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/network.harvesterhci.io/v1alpha1" "github.com/harvester/vm-dhcp-controller/pkg/ipam" @@ -69,15 +69,15 @@ type Handler struct { ippoolController ctlnetworkv1.IPPoolController ippoolClient ctlnetworkv1.IPPoolClient ippoolCache ctlnetworkv1.IPPoolCache - podClient ctlcorev1.PodClient - podCache ctlcorev1.PodCache + deploymentClient ctlappsv1.DeploymentClient + deploymentCache ctlappsv1.DeploymentCache nadClient ctlcniv1.NetworkAttachmentDefinitionClient nadCache ctlcniv1.NetworkAttachmentDefinitionCache } func Register(ctx context.Context, management *config.Management) error { ippools := management.HarvesterNetworkFactory.Network().V1alpha1().IPPool() - pods := management.CoreFactory.Core().V1().Pod() + deployments := management.AppsFactory.Apps().V1().Deployment() nads := management.CniFactory.K8s().V1().NetworkAttachmentDefinition() handler := &Handler{ @@ -94,8 +94,8 @@ func Register(ctx context.Context, management *config.Management) error { ippoolController: ippools, ippoolClient: ippools, ippoolCache: ippools.Cache(), - podClient: pods, - podCache: pods.Cache(), + deploymentClient: deployments, + deploymentCache: deployments.Cache(), nadClient: nads, nadCache: nads.Cache(), } @@ -125,21 +125,21 @@ func Register(ctx context.Context, management *config.Management) error { relatedresource.Watch(ctx, "ippool-trigger", func(namespace, name string, obj runtime.Object) ([]relatedresource.Key, error) { var keys []relatedresource.Key sets := labels.Set{ - "network.harvesterhci.io/vm-dhcp-controller": "agent", + vmDHCPControllerLabelKey: "agent", } - pods, err := handler.podCache.List(namespace, sets.AsSelector()) + deployments, err := handler.deploymentCache.List(namespace, sets.AsSelector()) if err != nil { return nil, err } - for _, pod := range pods { + for _, deployment := range deployments { key := relatedresource.Key{ - Namespace: pod.Labels[util.IPPoolNamespaceLabelKey], - Name: pod.Labels[util.IPPoolNameLabelKey], + Namespace: deployment.Labels[util.IPPoolNamespaceLabelKey], + Name: deployment.Labels[util.IPPoolNameLabelKey], } keys = append(keys, key) } return keys, nil - }, ippools, pods) + }, ippools, deployments) ippools.OnChange(ctx, controllerName, handler.OnChange) ippools.OnRemove(ctx, controllerName, handler.OnRemove) @@ -167,7 +167,7 @@ func (h *Handler) OnChange(key string, ipPool *networkv1.IPPool) (*networkv1.IPP if err := h.cleanup(ipPool); err != nil { return ipPool, err } - ipPoolCpy.Status.AgentPodRef = nil + ipPoolCpy.Status.AgentDeploymentRef = nil networkv1.Stopped.True(ipPoolCpy) if !reflect.DeepEqual(ipPoolCpy, ipPool) { return h.ippoolClient.UpdateStatus(ipPoolCpy) @@ -266,8 +266,8 @@ func (h *Handler) OnRemove(key string, ipPool *networkv1.IPPool) (*networkv1.IPP return ipPool, nil } -// DeployAgent reconciles ipPool and ensures there's an agent pod for it. The -// returned status reports whether an agent pod is registered. +// DeployAgent reconciles ipPool and ensures there's an agent deployment for it. The +// returned status reports whether an agent deployment is registered. func (h *Handler) DeployAgent(ipPool *networkv1.IPPool, status networkv1.IPPoolStatus) (networkv1.IPPoolStatus, error) { logrus.Debugf("(ippool.DeployAgent) deploy agent for ippool %s/%s", ipPool.Namespace, ipPool.Name) @@ -294,40 +294,99 @@ func (h *Handler) DeployAgent(ipPool *networkv1.IPPool, status networkv1.IPPoolS return status, fmt.Errorf("could not find clusternetwork for nad %s", ipPool.Spec.NetworkName) } - if ipPool.Status.AgentPodRef != nil { - status.AgentPodRef.Image = h.getAgentImage(ipPool) - pod, err := h.podCache.Get(ipPool.Status.AgentPodRef.Namespace, ipPool.Status.AgentPodRef.Name) + desiredImage := h.getAgentImage(ipPool) + + if ipPool.Status.AgentDeploymentRef != nil { + status.AgentDeploymentRef.Image = desiredImage + deployment, err := h.deploymentCache.Get(ipPool.Status.AgentDeploymentRef.Namespace, ipPool.Status.AgentDeploymentRef.Name) if err != nil { if !apierrors.IsNotFound(err) { return status, err } - logrus.Warningf("(ippool.DeployAgent) agent pod %s missing, redeploying", ipPool.Status.AgentPodRef.Name) + logrus.Warningf("(ippool.DeployAgent) agent deployment %s missing, redeploying", ipPool.Status.AgentDeploymentRef.Name) } else { - if pod.DeletionTimestamp != nil { - return status, fmt.Errorf("agent pod %s marked for deletion", ipPool.Status.AgentPodRef.Name) + if deployment.DeletionTimestamp != nil { + return status, fmt.Errorf("agent deployment %s marked for deletion", deployment.Name) + } + + if deployment.GetUID() != ipPool.Status.AgentDeploymentRef.UID { + return status, fmt.Errorf("agent deployment %s uid mismatch", deployment.Name) + } + + desiredDeployment, err := prepareAgentDeployment(ipPool, h.noDHCP, h.agentNamespace, clusterNetwork, h.agentServiceAccountName, desiredImage) + if err != nil { + return status, err + } + + updated := false + deploymentCpy := deployment.DeepCopy() + if !reflect.DeepEqual(deploymentCpy.Labels, desiredDeployment.Labels) { + deploymentCpy.Labels = desiredDeployment.Labels + updated = true + } + if !reflect.DeepEqual(deploymentCpy.Spec.Strategy, desiredDeployment.Spec.Strategy) { + deploymentCpy.Spec.Strategy = desiredDeployment.Spec.Strategy + updated = true + } + if deploymentCpy.Spec.Replicas == nil || desiredDeployment.Spec.Replicas == nil || *deploymentCpy.Spec.Replicas != *desiredDeployment.Spec.Replicas { + deploymentCpy.Spec.Replicas = desiredDeployment.Spec.Replicas + updated = true + } + if !reflect.DeepEqual(deploymentCpy.Spec.Selector, desiredDeployment.Spec.Selector) { + return status, fmt.Errorf("agent deployment %s selector mismatch", deployment.Name) + } + if !reflect.DeepEqual(deploymentCpy.Spec.Template.Labels, desiredDeployment.Spec.Template.Labels) { + deploymentCpy.Spec.Template.Labels = desiredDeployment.Spec.Template.Labels + updated = true + } + if !reflect.DeepEqual(deploymentCpy.Spec.Template.Annotations, desiredDeployment.Spec.Template.Annotations) { + deploymentCpy.Spec.Template.Annotations = desiredDeployment.Spec.Template.Annotations + updated = true + } + if deploymentCpy.Spec.Template.Spec.ServiceAccountName != desiredDeployment.Spec.Template.Spec.ServiceAccountName { + deploymentCpy.Spec.Template.Spec.ServiceAccountName = desiredDeployment.Spec.Template.Spec.ServiceAccountName + updated = true + } + if !reflect.DeepEqual(deploymentCpy.Spec.Template.Spec.Affinity, desiredDeployment.Spec.Template.Spec.Affinity) { + deploymentCpy.Spec.Template.Spec.Affinity = desiredDeployment.Spec.Template.Spec.Affinity + updated = true + } + if !reflect.DeepEqual(deploymentCpy.Spec.Template.Spec.InitContainers, desiredDeployment.Spec.Template.Spec.InitContainers) { + deploymentCpy.Spec.Template.Spec.InitContainers = desiredDeployment.Spec.Template.Spec.InitContainers + updated = true + } + if !reflect.DeepEqual(deploymentCpy.Spec.Template.Spec.Containers, desiredDeployment.Spec.Template.Spec.Containers) { + deploymentCpy.Spec.Template.Spec.Containers = desiredDeployment.Spec.Template.Spec.Containers + updated = true } - if pod.GetUID() != ipPool.Status.AgentPodRef.UID { - return status, fmt.Errorf("agent pod %s uid mismatch", ipPool.Status.AgentPodRef.Name) + if updated { + if _, err := h.deploymentClient.Update(deploymentCpy); err != nil { + return status, err + } } + status.AgentDeploymentRef.Namespace = deployment.Namespace + status.AgentDeploymentRef.Name = deployment.Name + status.AgentDeploymentRef.UID = deployment.GetUID() + return status, nil } } - agent, err := prepareAgentPod(ipPool, h.noDHCP, h.agentNamespace, clusterNetwork, h.agentServiceAccountName, h.agentImage) + agent, err := prepareAgentDeployment(ipPool, h.noDHCP, h.agentNamespace, clusterNetwork, h.agentServiceAccountName, desiredImage) if err != nil { return status, err } - if status.AgentPodRef == nil { - status.AgentPodRef = new(networkv1.PodReference) + if status.AgentDeploymentRef == nil { + status.AgentDeploymentRef = new(networkv1.DeploymentReference) } - status.AgentPodRef.Image = h.agentImage.String() + status.AgentDeploymentRef.Image = desiredImage - agentPod, err := h.podClient.Create(agent) + agentDeployment, err := h.deploymentClient.Create(agent) if err != nil { if apierrors.IsAlreadyExists(err) { return status, nil @@ -337,18 +396,13 @@ func (h *Handler) DeployAgent(ipPool *networkv1.IPPool, status networkv1.IPPoolS logrus.Infof("(ippool.DeployAgent) agent for ippool %s/%s has been deployed", ipPool.Namespace, ipPool.Name) - status.AgentPodRef.Namespace = agentPod.Namespace - status.AgentPodRef.Name = agentPod.Name - status.AgentPodRef.UID = agentPod.GetUID() + status.AgentDeploymentRef.Namespace = agentDeployment.Namespace + status.AgentDeploymentRef.Name = agentDeployment.Name + status.AgentDeploymentRef.UID = agentDeployment.GetUID() return status, nil } -// BuildCache reconciles ipPool and initializes the IPAM and MAC caches for it. -// The source information comes from both ipPool's spec and status. Since -// IPPool objects are deemed source of truths, BuildCache honors the state and -// use it to load up internal caches. The returned status reports whether both -// caches are fully initialized. func (h *Handler) BuildCache(ipPool *networkv1.IPPool, status networkv1.IPPoolStatus) (networkv1.IPPoolStatus, error) { logrus.Debugf("(ippool.BuildCache) build ipam for ippool %s/%s", ipPool.Namespace, ipPool.Name) @@ -416,10 +470,10 @@ func (h *Handler) BuildCache(ipPool *networkv1.IPPool, status networkv1.IPPoolSt return status, nil } -// MonitorAgent reconciles ipPool and keeps an eye on the agent pod. If the -// running agent pod does not match to the one record in ipPool's status, -// MonitorAgent tries to delete it. The returned status reports whether the -// agent pod is ready. +// MonitorAgent reconciles ipPool and keeps an eye on the agent deployment. If the +// running agent deployment does not match the one recorded in ipPool's status, +// MonitorAgent reports the mismatch. The returned status reports whether the +// agent deployment is ready. func (h *Handler) MonitorAgent(ipPool *networkv1.IPPool, status networkv1.IPPoolStatus) (networkv1.IPPoolStatus, error) { logrus.Debugf("(ippool.MonitorAgent) monitor agent for ippool %s/%s", ipPool.Namespace, ipPool.Name) @@ -431,58 +485,68 @@ func (h *Handler) MonitorAgent(ipPool *networkv1.IPPool, status networkv1.IPPool return status, nil } - if ipPool.Status.AgentPodRef == nil { + if ipPool.Status.AgentDeploymentRef == nil { return status, fmt.Errorf("agent for ippool %s/%s is not deployed", ipPool.Namespace, ipPool.Name) } - agentPod, err := h.podCache.Get(ipPool.Status.AgentPodRef.Namespace, ipPool.Status.AgentPodRef.Name) + agentDeployment, err := h.deploymentCache.Get(ipPool.Status.AgentDeploymentRef.Namespace, ipPool.Status.AgentDeploymentRef.Name) if err != nil { return status, err } - if agentPod.GetUID() != ipPool.Status.AgentPodRef.UID || agentPod.Spec.Containers[0].Image != ipPool.Status.AgentPodRef.Image { - if agentPod.DeletionTimestamp != nil { - return status, fmt.Errorf("agent pod %s marked for deletion", agentPod.Name) - } + if agentDeployment.DeletionTimestamp != nil { + return status, fmt.Errorf("agent deployment %s marked for deletion", agentDeployment.Name) + } - if err := h.podClient.Delete(agentPod.Namespace, agentPod.Name, &metav1.DeleteOptions{}); err != nil { - return status, err - } + if agentDeployment.GetUID() != ipPool.Status.AgentDeploymentRef.UID { + return status, fmt.Errorf("agent deployment %s uid mismatch", agentDeployment.Name) + } - return status, fmt.Errorf("agent pod %s obsolete and purged", agentPod.Name) + if len(agentDeployment.Spec.Template.Spec.Containers) == 0 || agentDeployment.Spec.Template.Spec.Containers[0].Image != ipPool.Status.AgentDeploymentRef.Image { + return status, fmt.Errorf("agent deployment %s image mismatch", agentDeployment.Name) } - if !isPodReady(agentPod) { - return status, fmt.Errorf("agent pod %s not ready", agentPod.Name) + if !isDeploymentReady(agentDeployment) { + return status, fmt.Errorf("agent deployment %s not ready", agentDeployment.Name) } return status, nil } -func isPodReady(pod *corev1.Pod) bool { - for _, c := range pod.Status.Conditions { - if c.Type == corev1.PodReady { - return c.Status == corev1.ConditionTrue - } +func isDeploymentReady(deployment *appsv1.Deployment) bool { + desired := int32(1) + if deployment.Spec.Replicas != nil { + desired = *deployment.Spec.Replicas + } + if deployment.Status.ObservedGeneration < deployment.Generation { + return false } - return false + if deployment.Status.UpdatedReplicas < desired { + return false + } + if deployment.Status.AvailableReplicas < desired { + return false + } + return true } func (h *Handler) getAgentImage(ipPool *networkv1.IPPool) string { _, ok := ipPool.Annotations[holdIPPoolAgentUpgradeAnnotationKey] if ok { - return ipPool.Status.AgentPodRef.Image + if ipPool.Status.AgentDeploymentRef != nil && ipPool.Status.AgentDeploymentRef.Image != "" { + return ipPool.Status.AgentDeploymentRef.Image + } } return h.agentImage.String() } func (h *Handler) cleanup(ipPool *networkv1.IPPool) error { - if ipPool.Status.AgentPodRef == nil { + if ipPool.Status.AgentDeploymentRef == nil { return nil } - logrus.Infof("(ippool.cleanup) remove the backing agent %s/%s for ippool %s/%s", ipPool.Status.AgentPodRef.Namespace, ipPool.Status.AgentPodRef.Name, ipPool.Namespace, ipPool.Name) - if err := h.podClient.Delete(ipPool.Status.AgentPodRef.Namespace, ipPool.Status.AgentPodRef.Name, &metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { + logrus.Infof("(ippool.cleanup) remove the backing agent %s/%s for ippool %s/%s", ipPool.Status.AgentDeploymentRef.Namespace, ipPool.Status.AgentDeploymentRef.Name, ipPool.Namespace, ipPool.Name) + if err := h.deploymentClient.Delete(ipPool.Status.AgentDeploymentRef.Namespace, ipPool.Status.AgentDeploymentRef.Name, &metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { return err } diff --git a/pkg/controller/ippool/controller_test.go b/pkg/controller/ippool/controller_test.go index 4aee580e..2852da28 100644 --- a/pkg/controller/ippool/controller_test.go +++ b/pkg/controller/ippool/controller_test.go @@ -20,33 +20,33 @@ import ( ) const ( - testNADNamespace = "default" - testNADName = "net-1" - testNADNameLong = "fi6cx9ca1kt1faq80k3ro9cowyumyjb67qdmg8fb9ydmz27rbk5btlg2m5avv3n" - testIPPoolNamespace = testNADNamespace - testIPPoolName = testNADName - testIPPoolNameLong = testNADNameLong - testKey = testIPPoolNamespace + "/" + testIPPoolName - testPodNamespace = "harvester-system" - testPodName = testNADNamespace + "-" + testNADName + "-agent" - testUID = "3a955369-9eaa-43db-94f3-9153289d7dc2" - testClusterNetwork = "provider" - testServerIP1 = "192.168.0.2" - testServerIP2 = "192.168.0.110" - testNetworkName = testNADNamespace + "/" + testNADName - testNetworkNameLong = testNADNamespace + "/" + testNADNameLong - testCIDR = "192.168.0.0/24" - testRouter1 = "192.168.0.1" - testRouter2 = "192.168.0.120" - testStartIP = "192.168.0.101" - testEndIP = "192.168.0.200" - testServiceAccountName = "vdca" - testImageRepository = "rancher/harvester-vm-dhcp-agent" - testImageTag = "main" - testImageTagNew = "dev" - testImage = testImageRepository + ":" + testImageTag - testImageNew = testImageRepository + ":" + testImageTagNew - testContainerName = "agent" + testNADNamespace = "default" + testNADName = "net-1" + testNADNameLong = "fi6cx9ca1kt1faq80k3ro9cowyumyjb67qdmg8fb9ydmz27rbk5btlg2m5avv3n" + testIPPoolNamespace = testNADNamespace + testIPPoolName = testNADName + testIPPoolNameLong = testNADNameLong + testKey = testIPPoolNamespace + "/" + testIPPoolName + testDeploymentNamespace = "harvester-system" + testDeploymentName = testNADNamespace + "-" + testNADName + "-agent" + testUID = "3a955369-9eaa-43db-94f3-9153289d7dc2" + testClusterNetwork = "provider" + testServerIP1 = "192.168.0.2" + testServerIP2 = "192.168.0.110" + testNetworkName = testNADNamespace + "/" + testNADName + testNetworkNameLong = testNADNamespace + "/" + testNADNameLong + testCIDR = "192.168.0.0/24" + testRouter1 = "192.168.0.1" + testRouter2 = "192.168.0.120" + testStartIP = "192.168.0.101" + testEndIP = "192.168.0.200" + testServiceAccountName = "vdca" + testImageRepository = "rancher/harvester-vm-dhcp-agent" + testImageTag = "main" + testImageTagNew = "dev" + testImage = testImageRepository + ":" + testImageTag + testImageNew = testImageRepository + ":" + testImageTagNew + testContainerName = "agent" testExcludedIP1 = "192.168.0.150" testExcludedIP2 = "192.168.0.187" @@ -60,7 +60,7 @@ const ( ) var ( - testPodNameLong = util.SafeAgentConcatName(testNADNamespace, testNADNameLong) + testDeploymentNameLong = util.SafeAgentConcatName(testNADNamespace, testNADNameLong) ) func newTestCacheAllocatorBuilder() *cache.CacheAllocatorBuilder { @@ -75,8 +75,8 @@ func newTestIPPoolBuilder() *IPPoolBuilder { return NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName) } -func newTestPodBuilder() *podBuilder { - return newPodBuilder(testPodNamespace, testPodName) +func newTestDeploymentBuilder() *deploymentBuilder { + return newDeploymentBuilder(testDeploymentNamespace, testDeploymentName) } func newTestIPPoolStatusBuilder() *ipPoolStatusBuilder { @@ -210,8 +210,8 @@ func TestHandler_OnChange(t *testing.T) { givenIPPool := newTestIPPoolBuilder(). NetworkName(testNetworkName). Paused(). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() - givenPod := newTestPodBuilder().Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() + givenDeployment := newTestDeploymentBuilder().Build() givenNAD := newTestNetworkAttachmentDefinitionBuilder().Build() expectedIPAllocator := newTestIPAllocatorBuilder().Build() @@ -236,7 +236,7 @@ func TestHandler_OnChange(t *testing.T) { } k8sclientset := k8sfake.NewSimpleClientset() - err = k8sclientset.Tracker().Add(givenPod) + err = k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ @@ -249,7 +249,7 @@ func TestHandler_OnChange(t *testing.T) { cacheAllocator: cache.New(), metricsAllocator: metrics.New(), ippoolClient: fakeclient.IPPoolClient(clientset.NetworkV1alpha1().IPPools), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), nadClient: fakeclient.NetworkAttachmentDefinitionClient(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), } @@ -264,8 +264,9 @@ func TestHandler_OnChange(t *testing.T) { assert.Equal(t, expectedIPAllocator, handler.ipAllocator) - _, err = handler.podClient.Get(testPodNamespace, testPodName, metav1.GetOptions{}) - assert.Equal(t, fmt.Sprintf("pods \"%s\" not found", testPodName), err.Error()) + _, err = handler.deploymentClient.Get(testDeploymentNamespace, testDeploymentName, metav1.GetOptions{}) + assert.Equal(t, fmt.Sprintf("deployments.apps \"%s\" not found", testDeploymentName), err.Error()) + }) t.Run("resume ippool", func(t *testing.T) { @@ -335,20 +336,17 @@ func TestHandler_DeployAgent(t *testing.T) { Label(clusterNetworkLabelKey, testClusterNetwork).Build() expectedStatus := newTestIPPoolStatusBuilder(). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() - expectedPod, _ := prepareAgentPod( + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() + expectedDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) nadGVR := schema.GroupVersionResource{ @@ -364,24 +362,25 @@ func TestHandler_DeployAgent(t *testing.T) { k8sclientset := k8sfake.NewSimpleClientset() handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTag, }, agentServiceAccountName: testServiceAccountName, nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } status, err := handler.DeployAgent(givenIPPool, givenIPPool.Status) assert.Nil(t, err) assert.Equal(t, expectedStatus, status) - pod, err := handler.podClient.Get(testPodNamespace, testPodName, metav1.GetOptions{}) + deployment, err := handler.deploymentClient.Get(testDeploymentNamespace, testDeploymentName, metav1.GetOptions{}) assert.Nil(t, err) - assert.Equal(t, expectedPod, pod) + + assert.Equal(t, expectedDeployment, deployment) }) t.Run("ippool paused", func(t *testing.T) { @@ -389,7 +388,7 @@ func TestHandler_DeployAgent(t *testing.T) { Paused().Build() handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTag, @@ -425,44 +424,38 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Equal(t, fmt.Sprintf("network-attachment-definitions.k8s.cni.cncf.io \"%s\" not found", "you-cant-find-me"), err.Error()) }) - t.Run("agent pod already exists", func(t *testing.T) { + t.Run("agent deployment already exists", func(t *testing.T) { givenIPPool := newTestIPPoolBuilder(). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() givenNAD := newTestNetworkAttachmentDefinitionBuilder(). Label(clusterNetworkLabelKey, testClusterNetwork).Build() - givenPod, _ := prepareAgentPod( + givenDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) expectedStatus := newTestIPPoolStatusBuilder(). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() - expectedPod, _ := prepareAgentPod( + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() + expectedDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) nadGVR := schema.GroupVersionResource{ @@ -476,28 +469,29 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Nil(t, err, "mock resource should add into fake controller tracker") k8sclientset := k8sfake.NewSimpleClientset() - err = k8sclientset.Tracker().Add(givenPod) + err = k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTag, }, agentServiceAccountName: testServiceAccountName, nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } status, err := handler.DeployAgent(givenIPPool, givenIPPool.Status) assert.Nil(t, err) assert.Equal(t, expectedStatus, status) - pod, err := handler.podClient.Get(testPodNamespace, testPodName, metav1.GetOptions{}) + deployment, err := handler.deploymentClient.Get(testDeploymentNamespace, testDeploymentName, metav1.GetOptions{}) assert.Nil(t, err) - assert.Equal(t, expectedPod, pod) + + assert.Equal(t, expectedDeployment, deployment) }) t.Run("very long name ippool created", func(t *testing.T) { @@ -509,20 +503,17 @@ func TestHandler_DeployAgent(t *testing.T) { Label(clusterNetworkLabelKey, testClusterNetwork).Build() expectedStatus := newTestIPPoolStatusBuilder(). - AgentPodRef(testPodNamespace, testPodNameLong, testImage, "").Build() - expectedPod, _ := prepareAgentPod( + AgentDeploymentRef(testDeploymentNamespace, testDeploymentNameLong, testImage, "").Build() + expectedDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolNameLong). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkNameLong).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) nadGVR := schema.GroupVersionResource{ @@ -538,51 +529,49 @@ func TestHandler_DeployAgent(t *testing.T) { k8sclientset := k8sfake.NewSimpleClientset() handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTag, }, agentServiceAccountName: testServiceAccountName, nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } status, err := handler.DeployAgent(givenIPPool, givenIPPool.Status) assert.Nil(t, err) assert.Equal(t, expectedStatus, status) - pod, err := handler.podClient.Get(testPodNamespace, testPodNameLong, metav1.GetOptions{}) + deployment, err := handler.deploymentClient.Get(testDeploymentNamespace, testDeploymentNameLong, metav1.GetOptions{}) assert.Nil(t, err) - assert.Equal(t, expectedPod, pod) + + assert.Equal(t, expectedDeployment, deployment) }) - t.Run("agent pod upgrade (from main to dev)", func(t *testing.T) { + t.Run("agent deployment upgrade (from main to dev)", func(t *testing.T) { givenIPPool := newTestIPPoolBuilder(). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() givenNAD := newTestNetworkAttachmentDefinitionBuilder(). Label(clusterNetworkLabelKey, testClusterNetwork).Build() - givenPod, _ := prepareAgentPod( + givenDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) expectedStatus := newTestIPPoolStatusBuilder(). - AgentPodRef(testPodNamespace, testPodName, testImageNew, "").Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImageNew, "").Build() nadGVR := schema.GroupVersionResource{ Group: "k8s.cni.cncf.io", @@ -595,19 +584,19 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Nil(t, err, "mock resource should add into fake controller tracker") k8sclientset := k8sfake.NewSimpleClientset() - err = k8sclientset.Tracker().Add(givenPod) + err = k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTagNew, }, agentServiceAccountName: testServiceAccountName, nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } status, err := handler.DeployAgent(givenIPPool, givenIPPool.Status) @@ -615,32 +604,29 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Equal(t, expectedStatus, status) }) - t.Run("agent pod upgrade held back", func(t *testing.T) { + t.Run("agent deployment upgrade held back", func(t *testing.T) { givenIPPool := newTestIPPoolBuilder(). Annotation(holdIPPoolAgentUpgradeAnnotationKey, "true"). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() givenNAD := newTestNetworkAttachmentDefinitionBuilder(). Label(clusterNetworkLabelKey, testClusterNetwork).Build() - givenPod, _ := prepareAgentPod( + givenDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) expectedStatus := newTestIPPoolStatusBuilder(). - AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() nadGVR := schema.GroupVersionResource{ Group: "k8s.cni.cncf.io", @@ -653,19 +639,19 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Nil(t, err, "mock resource should add into fake controller tracker") k8sclientset := k8sfake.NewSimpleClientset() - err = k8sclientset.Tracker().Add(givenPod) + err = k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTagNew, }, agentServiceAccountName: testServiceAccountName, nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } status, err := handler.DeployAgent(givenIPPool, givenIPPool.Status) @@ -673,27 +659,24 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Equal(t, expectedStatus, status) }) - t.Run("existing agent pod uid mismatch", func(t *testing.T) { + t.Run("existing agent deployment uid mismatch", func(t *testing.T) { givenIPPool := newTestIPPoolBuilder(). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName). - AgentPodRef(testPodNamespace, testPodName, testImage, testUID).Build() + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, testUID).Build() givenNAD := newTestNetworkAttachmentDefinitionBuilder(). Label(clusterNetworkLabelKey, testClusterNetwork).Build() - givenPod, _ := prepareAgentPod( + givenDeployment, _ := prepareAgentDeployment( NewIPPoolBuilder(testIPPoolNamespace, testIPPoolName). ServerIP(testServerIP1). CIDR(testCIDR). NetworkName(testNetworkName).Build(), false, - testPodNamespace, + testDeploymentNamespace, testClusterNetwork, testServiceAccountName, - &config.Image{ - Repository: testImageRepository, - Tag: testImageTag, - }, + testImage, ) nadGVR := schema.GroupVersionResource{ @@ -707,23 +690,23 @@ func TestHandler_DeployAgent(t *testing.T) { assert.Nil(t, err, "mock resource should add into fake controller tracker") k8sclientset := k8sfake.NewSimpleClientset() - err = k8sclientset.Tracker().Add(givenPod) + err = k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - agentNamespace: testPodNamespace, + agentNamespace: testDeploymentNamespace, agentImage: &config.Image{ Repository: testImageRepository, Tag: testImageTagNew, }, agentServiceAccountName: testServiceAccountName, nadCache: fakeclient.NetworkAttachmentDefinitionCache(clientset.K8sCniCncfIoV1().NetworkAttachmentDefinitions), - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } _, err = handler.DeployAgent(givenIPPool, givenIPPool.Status) - assert.Equal(t, fmt.Sprintf("agent pod %s uid mismatch", testPodName), err.Error()) + assert.Equal(t, fmt.Sprintf("agent deployment %s uid mismatch", testDeploymentName), err.Error()) }) } @@ -838,54 +821,54 @@ func TestHandler_BuildCache(t *testing.T) { } func TestHandler_MonitorAgent(t *testing.T) { - t.Run("agent pod not found", func(t *testing.T) { - givenIPPool := newTestIPPoolBuilder().AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() - givenPod := newPodBuilder("default", "nginx").Build() + t.Run("agent deployment not found", func(t *testing.T) { + givenIPPool := newTestIPPoolBuilder().AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() + givenDeployment := newDeploymentBuilder("default", "nginx").Build() k8sclientset := k8sfake.NewSimpleClientset() - err := k8sclientset.Tracker().Add(givenPod) + err := k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } _, err = handler.MonitorAgent(givenIPPool, givenIPPool.Status) - assert.Equal(t, fmt.Sprintf("pods \"%s\" not found", testPodName), err.Error()) + assert.Equal(t, fmt.Sprintf("deployments.apps \"%s\" not found", testDeploymentName), err.Error()) }) - t.Run("agent pod unready", func(t *testing.T) { - givenIPPool := newTestIPPoolBuilder().AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() - givenPod := newTestPodBuilder(). + t.Run("agent deployment unready", func(t *testing.T) { + givenIPPool := newTestIPPoolBuilder().AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() + givenDeployment := newTestDeploymentBuilder(). Container(testContainerName, testImageRepository, testImageTag).Build() k8sclientset := k8sfake.NewSimpleClientset() - err := k8sclientset.Tracker().Add(givenPod) + err := k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } _, err = handler.MonitorAgent(givenIPPool, givenIPPool.Status) - assert.Equal(t, fmt.Sprintf("agent pod %s not ready", testPodName), err.Error()) + assert.Equal(t, fmt.Sprintf("agent deployment %s not ready", testDeploymentName), err.Error()) }) - t.Run("agent pod ready", func(t *testing.T) { - givenIPPool := newTestIPPoolBuilder().AgentPodRef(testPodNamespace, testPodName, testImage, "").Build() - givenPod := newTestPodBuilder(). + t.Run("agent deployment ready", func(t *testing.T) { + givenIPPool := newTestIPPoolBuilder().AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImage, "").Build() + givenDeployment := newTestDeploymentBuilder(). Container(testContainerName, testImageRepository, testImageTag). - PodReady(corev1.ConditionTrue).Build() + DeploymentReady(true).Build() k8sclientset := k8sfake.NewSimpleClientset() - err := k8sclientset.Tracker().Add(givenPod) + err := k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } _, err = handler.MonitorAgent(givenIPPool, givenIPPool.Status) @@ -912,7 +895,7 @@ func TestHandler_MonitorAgent(t *testing.T) { assert.Nil(t, err) }) - t.Run("agentpodref not set", func(t *testing.T) { + t.Run("agent deployment ref not set", func(t *testing.T) { givenIPPool := newTestIPPoolBuilder().Build() handler := Handler{} @@ -921,27 +904,25 @@ func TestHandler_MonitorAgent(t *testing.T) { assert.Equal(t, fmt.Sprintf("agent for ippool %s is not deployed", testIPPoolNamespace+"/"+testIPPoolName), err.Error()) }) - t.Run("outdated agent pod", func(t *testing.T) { + t.Run("outdated agent deployment", func(t *testing.T) { givenIPPool := newTestIPPoolBuilder(). - AgentPodRef(testPodNamespace, testPodName, testImageNew, "").Build() - givenPod := newTestPodBuilder(). + AgentDeploymentRef(testDeploymentNamespace, testDeploymentName, testImageNew, "").Build() + givenDeployment := newTestDeploymentBuilder(). Container(testContainerName, testImageRepository, testImageTag). - PodReady(corev1.ConditionTrue).Build() + DeploymentReady(true).Build() k8sclientset := k8sfake.NewSimpleClientset() - err := k8sclientset.Tracker().Add(givenPod) + err := k8sclientset.Tracker().Add(givenDeployment) assert.Nil(t, err, "mock resource should add into fake controller tracker") handler := Handler{ - podClient: fakeclient.PodClient(k8sclientset.CoreV1().Pods), - podCache: fakeclient.PodCache(k8sclientset.CoreV1().Pods), + deploymentClient: fakeclient.DeploymentClient(k8sclientset.AppsV1().Deployments), + deploymentCache: fakeclient.DeploymentCache(k8sclientset.AppsV1().Deployments), } _, err = handler.MonitorAgent(givenIPPool, givenIPPool.Status) - assert.Equal(t, fmt.Sprintf("agent pod %s obsolete and purged", testPodName), err.Error()) + assert.Equal(t, fmt.Sprintf("agent deployment %s image mismatch", testDeploymentName), err.Error()) - _, err = handler.podClient.Get(testPodNamespace, testPodName, metav1.GetOptions{}) - assert.Equal(t, fmt.Sprintf("pods \"%s\" not found", testPodName), err.Error()) }) } diff --git a/pkg/data/data.go b/pkg/data/data.go index 70e46c8a..f24ef86e 100644 --- a/pkg/data/data.go +++ b/pkg/data/data.go @@ -79,7 +79,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _chartCrdsNetworkHarvesterhciIo_ippoolsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x19\x5d\x6f\xe3\xc6\xf1\x5d\xbf\x62\x8a\x3e\x38\x01\x4c\x19\x87\x14\x45\x21\xe0\xd0\x3a\xb2\x9a\x08\x71\x1d\x43\xb2\xaf\x08\x8a\x3e\x8c\xb8\x23\x69\xe3\xe5\x2e\xb3\x1f\xb2\xdd\x5c\xfe\x7b\x31\x4b\xd2\xa2\x28\x92\xa6\x75\x77\x41\xf6\x49\x9a\xdd\x9d\xef\x99\x9d\x19\x26\x49\x32\xc2\x5c\x7e\x20\xeb\xa4\xd1\x13\xc0\x5c\xd2\x93\x27\xcd\xff\xdc\xf8\xe1\x6f\x6e\x2c\xcd\xc5\xee\xdd\xe8\x41\x6a\x31\x81\x69\x70\xde\x64\x0b\x72\x26\xd8\x94\xae\x68\x2d\xb5\xf4\xd2\xe8\x51\x46\x1e\x05\x7a\x9c\x8c\x00\x50\x6b\xe3\x91\xc1\x8e\xff\x02\xfc\xfa\xdb\x08\x40\x63\x46\x13\x90\x79\x6e\x8c\x72\x63\x4d\xfe\xd1\xd8\x87\xf1\x16\xed\x8e\x9c\x27\xbb\x4d\xe5\x58\x9a\x91\xcb\x29\xe5\x4b\x1b\x6b\x42\x3e\x81\xae\x63\x05\xba\x12\x7d\xc1\xda\xfc\xf6\xd6\x18\x15\x01\x4a\x3a\xff\x43\x0d\x78\x2d\x9d\x8f\x1b\xb9\x0a\x16\xd5\x0b\x17\x11\xe6\xb6\xc6\xfa\x9b\x3d\xb6\x84\x77\x55\xed\x67\x79\x4c\xea\x4d\x50\x68\xab\xcb\x23\x00\x97\x9a\x9c\x26\x10\xef\xe6\x98\x92\x18\x01\xec\x0a\x3d\x46\x5c\x09\xa0\x10\x51\x3d\xa8\x6e\xad\xd4\x9e\xec\xd4\xa8\x90\xe9\x17\x4a\x3f\x3b\xa3\x6f\xd1\x6f\x27\x30\x66\xc1\x2b\xad\x30\xc6\x78\xa2\xd2\xda\xcd\xec\xee\xdf\x3f\x2e\x7e\x28\x61\xfe\x99\xc9\x3a\x6f\xa5\xde\xb4\x20\xf2\xe8\x83\x1b\xcb\x7c\xf7\x97\x31\xee\x50\x2a\x5c\xa9\x43\x6c\x97\x1f\x2e\xe7\xd7\x97\xdf\x5e\xcf\x0e\xf0\x31\x7f\x1b\xb2\xfd\x08\x83\x8b\x52\xee\x71\xdd\x2f\x67\x57\x6f\x42\x93\x1a\x5d\xe8\xc4\xfd\xe7\xef\x5f\xfd\x63\xcc\x97\xde\xbf\x3f\x5b\xd0\x46\xb2\x79\x49\x9c\x7d\xfd\xdf\xf2\xe8\x01\x9d\xc5\xec\xbb\xf9\xf2\x6e\xb6\x68\x50\x7b\x45\x09\xed\xc4\xa6\x98\x6e\x69\x41\x28\x9e\x3b\x88\x4d\x2f\xa7\xdf\xcf\x16\xb3\xcb\xab\x9f\x3e\x9d\xd8\xe5\x86\xb4\xef\x23\x76\xf9\xdd\xec\xe6\x6e\x38\xb1\x2a\xd0\xc6\xa9\xa5\x18\x63\x77\x32\x23\xe7\x31\xcb\x9b\x58\x0f\xd0\x09\xf4\x85\x13\x14\xdb\xbb\x77\xa8\xf2\x2d\xbe\x2b\x5c\x3b\xdd\x52\x86\x93\xf2\xbc\xc9\x49\x5f\xde\xce\x3f\x7c\xb3\x3c\x00\x03\xe4\xd6\xe4\x64\xbd\xac\x02\xa5\x58\xb5\xdc\x51\x83\x02\x08\x72\xa9\x95\xb9\x8f\x49\xe5\x63\x72\xb0\x07\xc0\x04\x8a\x5b\x20\x38\x89\x90\x03\xbf\xa5\x2a\x7a\x48\x94\x3c\x81\x59\x83\xdf\x4a\x07\x96\x72\x4b\x8e\x74\x91\x56\x18\x8c\x1a\xcc\xea\x67\x4a\xfd\xb8\x81\x7a\x49\x96\xd1\x70\x5c\x07\x25\x20\x35\x7a\x47\xd6\x83\xa5\xd4\x6c\xb4\xfc\xdf\x0b\x6e\x07\xde\x44\xa2\x0a\x3d\x39\x1f\x1d\xd7\x6a\x54\xb0\x43\x15\xe8\x1c\x50\x8b\x06\xe6\x0c\x9f\xc1\x12\xd3\x84\xa0\x6b\xf8\xe2\x05\xd7\xe4\xe3\x5f\xc6\x12\x48\xbd\x36\x13\xd8\x7a\x9f\xbb\xc9\xc5\xc5\x46\xfa\x2a\xa3\xa6\x26\xcb\x82\x96\xfe\xf9\x22\x35\xda\x5b\xb9\x0a\xde\x58\x77\x21\x68\x47\xea\xc2\xc9\x4d\x82\x36\xdd\x4a\x4f\xa9\x0f\x96\x2e\x30\x97\x49\x14\x44\x47\xff\x1a\x67\xe2\xcf\xb6\xcc\xc1\xee\x80\xec\x91\xef\x14\x2b\x66\xc8\x37\x98\x87\x93\x27\x48\x07\x58\xa2\x2a\x44\xdc\x5b\x81\x41\xac\xba\xc5\x6c\x79\x07\x15\x27\x85\xa5\x0a\xa3\xec\x8f\x1e\xe9\xa5\xb2\x0f\x6b\x53\xea\x35\xd9\xe2\xde\xda\x9a\x2c\xe2\x24\x2d\x72\x23\xb5\x8f\x7f\x52\x25\x49\x7b\x70\x61\x95\x49\xcf\x6e\xf0\x4b\x20\xe7\xd9\x74\x4d\xb4\xd3\xf8\xea\xc0\x8a\x20\xe4\xec\xec\xa2\x79\x60\xae\x61\x8a\x19\xa9\x29\x3a\xfa\x9d\x6d\xc5\x56\x71\x09\x1b\x61\x90\xb5\xea\x6f\x69\xf3\x70\xa1\xde\xda\x46\xf5\x60\xee\x57\x7b\x9c\xf2\xe2\x14\x3e\x35\x7a\x2d\x37\xcd\x9d\xbe\x5b\xbc\x52\x29\x6c\x1b\xbc\x53\x86\xfd\x7a\x4a\x1e\xc2\x8a\xac\x26\x4f\x2e\xd9\xa1\x92\xa2\x5e\x1a\x34\x57\x02\x19\x39\x87\x1b\xce\xc2\xf3\xab\x05\x3b\xa1\xcc\xb2\xe0\x6b\x8f\x58\x73\xd9\xa0\x98\x03\x52\x6b\x78\xff\x1e\x8c\x12\x4b\x52\xeb\x96\xb3\xa2\x8b\xe6\xda\xd8\x0c\xfd\x24\xaa\xa7\xf5\x80\xf4\x94\x75\xdc\x1d\xa0\x80\x0c\x9f\xe6\x11\x01\x7c\xd3\xa3\x41\xb4\x16\x9f\xdb\xb8\x36\x19\x4a\xcd\x15\xc1\x49\xfa\x2f\xae\x2f\x89\x5d\xb4\x1d\xc1\xa7\x09\xd7\xcf\xbc\x22\x74\xc4\x0f\x54\x1f\xef\xf5\x8a\xe1\x70\x69\x9f\x7f\x09\x9e\xf7\x06\x69\x37\x77\xbf\x4c\x5c\xfc\xb5\x93\xee\x0f\x21\x5e\xd4\x4c\xc3\xf5\xf5\xaa\x1b\x0e\x12\x0e\xde\x1c\x72\x70\x18\x76\xb3\x22\xf5\xbf\x16\x75\xf0\x86\xc8\x8b\xa2\x3f\xa5\x2a\x88\x0e\x47\x18\x2c\x7e\xaf\xe1\x61\xa8\x7e\xfa\x0d\x5c\xac\x4f\xd3\x61\x21\xec\x97\xd0\xa3\xf3\x68\xfd\x1f\xde\x89\x96\xcc\xe5\xe7\x17\x9f\xdf\x7f\x69\xa9\x23\x88\x12\x8e\xaf\x8e\x9d\xa8\xb6\x9e\x68\x3f\x7a\x57\xf7\xeb\xe4\x07\xac\x8c\xa4\x8a\x69\x30\x3a\x25\x70\xd4\x4e\xa5\x52\xc3\xd9\x9f\xb6\xe8\xbe\x2a\x95\x30\x2e\xa3\xe6\x6b\xf8\xf8\x11\x18\xee\xea\xc0\xb3\x16\x44\xd6\x04\x4f\x1d\x4f\xf5\xab\xbe\xf1\xe5\xde\xf2\x45\x64\xeb\x73\xbe\xe6\x2e\x96\x91\xf3\xdb\x3f\x9c\xa8\xcb\x92\xb1\xcf\x27\x6c\xb7\xd7\x27\xb1\x30\x6b\x01\xe7\xd5\x28\xe4\x10\x5c\x29\xed\x68\xab\x37\x08\x86\xab\xa2\xd5\xe2\x43\xfc\xbf\xcd\xf7\x0b\x57\x3e\x74\xfd\x12\xd6\xf4\xfc\xda\xdc\xe4\x98\xa9\x0c\x9f\xae\x49\x6f\xb8\x7b\xfe\xeb\xb1\x33\xf4\x3a\xc2\x49\x92\xdf\xec\x99\x79\xcd\x07\x86\xd8\x3f\xc7\xe0\xda\x6c\x5f\x30\xbe\x32\x46\x11\xea\x83\xdd\x76\x7f\x49\xe0\x78\xba\x54\xc7\x74\xdc\x59\xc4\x59\xc5\xd0\xde\x02\x37\xa4\xfd\xad\x11\x0b\x5a\xbf\xb5\xb9\x90\x19\xeb\xed\x94\x30\xd5\xa7\x96\xc5\xba\x9a\xd9\x9d\x74\x3b\xc8\x8e\x17\xa8\xbf\xab\xae\xd6\xfd\xfc\xaa\x68\xae\x99\x0c\xf8\x2d\x7a\xd8\x1a\x25\x1c\x04\x2d\x7f\x09\x04\xf3\xab\x72\xa4\x70\x0e\x52\x73\xaa\xe7\x76\xfb\xfe\x7e\x7e\xe5\xc6\x00\xdf\x52\xca\x0e\x01\x8f\x5d\x39\x45\x18\x7d\xe6\xe1\xc7\x9b\xeb\x9f\x80\xcf\xc5\x7b\xe7\x45\x8f\xcd\x44\x35\xa0\x92\xc5\xf0\xa3\x90\x2f\xe2\x64\x0a\x25\x3f\x29\xe6\xdc\xc7\xba\x0e\xf4\x5c\xad\x6b\x0f\xa8\x05\x6c\x49\xe5\xdc\xc7\x3f\x10\xb8\x60\x4b\x49\x98\x5c\xdc\x8d\x2a\x06\x61\x80\xdb\xf2\x0d\x79\x48\x8d\x5e\xab\xb6\xce\x7c\x80\xce\x7b\xf2\xd3\x7e\xec\x76\x6c\x93\xce\x72\xf1\xb5\x52\x5d\xa1\xf3\x77\x16\xb5\x93\xd5\x88\xad\xab\xec\x39\x30\xf9\x35\x3a\x0f\x5e\x66\x54\x0c\x2f\x2a\xce\xc0\xbf\xa0\x22\x51\x4c\x3a\x8c\x26\x38\x18\x06\xb6\x28\xc4\x00\x6a\xe3\xb7\x64\xdb\x15\x36\xe0\x11\x63\x31\xee\xe3\x38\x64\xb0\x08\x77\x71\x22\xb6\x17\x43\xba\x9a\x1c\x8f\xe8\xba\xc6\x2b\x83\x79\xaa\xf2\xe4\x10\x66\xbe\x0f\x19\xea\xc4\x12\x0a\x4e\xa0\xd5\x55\x90\x5a\xc8\x14\xe3\x14\x4a\x90\x47\xa9\x1c\xe0\xca\x84\xae\xc2\x0a\x4a\x81\x5e\x8c\x70\x2a\xeb\x96\xd0\x35\xe7\x9c\x1d\x9c\xb3\x1a\x8b\xe3\x5c\x8b\x1c\xba\xc3\x99\x6b\x32\x74\xb2\x32\xdb\x72\x74\x07\x47\xcb\x78\xb4\x98\xa8\xd6\x98\x39\x8f\xae\x68\xd6\x70\x67\x03\x9d\xc3\x3f\x51\x39\x3a\x87\x7b\xfd\xa0\xcd\xe3\xe9\x7c\xc5\x03\x83\xf4\xc4\x29\xc7\xac\x21\x55\xc1\x71\xbd\xf0\xc2\xd7\x89\xa4\xfb\x3a\x84\xa4\x3b\xe2\x92\x88\xb7\x65\xa3\xb7\x30\xea\xee\x23\xb9\xee\x7c\xeb\x33\x88\x4a\x99\x94\x43\xab\x5d\x71\xf5\x6f\x49\xaf\xcd\x19\x06\x8e\x6d\x3a\x9b\x9e\x97\xef\x46\xa7\xcd\x6d\xda\x0b\x96\xd7\x6f\xf6\x15\xba\xcd\x4f\x59\xf5\xbd\xda\x57\xa9\x41\x22\xee\xd3\xe2\x31\xa5\xaa\x73\xe0\xdd\x84\x73\xe0\xf0\x8a\xb1\x95\xe2\x11\x30\xd6\xe0\x62\x02\xde\x86\x02\xb7\xf3\xc6\xc6\xc2\x71\x0f\x09\xab\x97\xf1\x7e\xc5\x61\x19\xe9\xf0\xeb\x6f\xa3\xff\x07\x00\x00\xff\xff\x51\x01\x92\x53\xb3\x1d\x00\x00") +var _chartCrdsNetworkHarvesterhciIo_ippoolsYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x19\x5d\x6f\xe3\xc6\xf1\x5d\xbf\x62\x8a\x3e\x38\x01\x4c\x19\x87\x14\x45\x21\xe0\xd0\x3a\xb2\x9a\x08\x71\x1d\x43\xb2\xaf\x08\x8a\x3e\x8c\xc8\x91\xb8\xf1\x72\x97\xd9\x0f\xd9\x6e\x2e\xff\xbd\x98\x25\x29\x51\x14\x49\xd1\xba\xbb\x20\xfb\x24\xcd\xee\xce\xf7\xcc\xce\x0c\xa3\x28\x1a\x61\x2e\x3e\x90\xb1\x42\xab\x09\x60\x2e\xe8\xc5\x91\xe2\x7f\x76\xfc\xf4\x37\x3b\x16\xfa\x6a\xfb\x6e\xf4\x24\x54\x32\x81\xa9\xb7\x4e\x67\x0b\xb2\xda\x9b\x98\x6e\x68\x2d\x94\x70\x42\xab\x51\x46\x0e\x13\x74\x38\x19\x01\xa0\x52\xda\x21\x83\x2d\xff\x05\xf8\xf5\xb7\x11\x80\xc2\x8c\x26\x20\xf2\x5c\x6b\x69\xc7\x8a\xdc\xb3\x36\x4f\xe3\x14\xcd\x96\xac\x23\x93\xc6\x62\x2c\xf4\xc8\xe6\x14\xf3\xa5\x8d\xd1\x3e\x9f\x40\xd7\xb1\x02\x5d\x89\xbe\x60\x6d\x7e\x7f\xaf\xb5\x0c\x00\x29\xac\xfb\xa1\x06\xbc\x15\xd6\x85\x8d\x5c\x7a\x83\x72\xc7\x45\x80\xd9\x54\x1b\x77\xb7\xc7\x16\xf1\xae\xac\xfd\x2c\x8f\x09\xb5\xf1\x12\x4d\x75\x79\x04\x60\x63\x9d\xd3\x04\xc2\xdd\x1c\x63\x4a\x46\x00\xdb\x42\x8f\x01\x57\x04\x98\x24\x41\x3d\x28\xef\x8d\x50\x8e\xcc\x54\x4b\x9f\xa9\x1d\xa5\x9f\xad\x56\xf7\xe8\xd2\x09\x8c\x59\xf0\x4a\x2b\x8c\x31\x9c\xa8\xb4\x76\x37\x7b\xf8\xf7\x8f\x8b\x1f\x4a\x98\x7b\x65\xb2\xd6\x19\xa1\x36\x2d\x88\x1c\x3a\x6f\xc7\x22\xdf\xfe\x65\x8c\x5b\x14\x12\x57\xf2\x10\xdb\xf5\x87\xeb\xf9\xed\xf5\xb7\xb7\xb3\x03\x7c\xcc\xdf\x86\x4c\x3f\x42\x6f\x83\x94\x7b\x5c\x8f\xcb\xd9\xcd\x9b\xd0\xc4\x5a\x15\x3a\xb1\xff\xf9\xfb\x57\xff\x18\xf3\xa5\xf7\xef\x2f\x16\xb4\x11\x6c\x5e\x4a\x2e\xbe\xfe\x6f\x79\xf4\x80\xce\x62\xf6\xdd\x7c\xf9\x30\x5b\x34\xa8\x9d\x50\x42\x3b\xb1\x29\xc6\x29\x2d\x08\x93\xd7\x0e\x62\xd3\xeb\xe9\xf7\xb3\xc5\xec\xfa\xe6\xa7\x4f\x27\x76\xbd\x21\xe5\xfa\x88\x5d\x7f\x37\xbb\x7b\x18\x4e\xac\x0a\xb4\x71\x6c\x28\xc4\xd8\x83\xc8\xc8\x3a\xcc\xf2\x26\xd6\x03\x74\x09\xba\xc2\x09\x8a\xed\xed\x3b\x94\x79\x8a\xef\x0a\xd7\x8e\x53\xca\x70\x52\x9e\xd7\x39\xa9\xeb\xfb\xf9\x87\x6f\x96\x07\x60\x80\xdc\xe8\x9c\x8c\x13\x55\xa0\x14\xab\x96\x3b\x6a\x50\x80\x84\x6c\x6c\x44\xee\x42\x52\xf9\x18\x1d\xec\x01\x30\x81\xe2\x16\x24\x9c\x44\xc8\x82\x4b\xa9\x8a\x1e\x4a\x4a\x9e\x40\xaf\xc1\xa5\xc2\x82\xa1\xdc\x90\x25\x55\xa4\x15\x06\xa3\x02\xbd\xfa\x99\x62\x37\x6e\xa0\x5e\x92\x61\x34\x1c\xd7\x5e\x26\x10\x6b\xb5\x25\xe3\xc0\x50\xac\x37\x4a\xfc\x6f\x87\xdb\x82\xd3\x81\xa8\x44\x47\xd6\x05\xc7\x35\x0a\x25\x6c\x51\x7a\xba\x04\x54\x49\x03\x73\x86\xaf\x60\x88\x69\x82\x57\x35\x7c\xe1\x82\x6d\xf2\xf1\x2f\x6d\x08\x84\x5a\xeb\x09\xa4\xce\xe5\x76\x72\x75\xb5\x11\xae\xca\xa8\xb1\xce\x32\xaf\x84\x7b\xbd\x8a\xb5\x72\x46\xac\xbc\xd3\xc6\x5e\x25\xb4\x25\x79\x65\xc5\x26\x42\x13\xa7\xc2\x51\xec\xbc\xa1\x2b\xcc\x45\x14\x04\x51\xc1\xbf\xc6\x59\xf2\x67\x53\xe6\x60\x7b\x40\xf6\xc8\x77\x8a\x15\x32\xe4\x1b\xcc\xc3\xc9\x13\x84\x05\x2c\x51\x15\x22\xee\xad\xc0\x20\x56\xdd\x62\xb6\x7c\x80\x8a\x93\xc2\x52\x85\x51\xf6\x47\x8f\xf4\x52\xd9\x87\xb5\x29\xd4\x9a\x4c\x71\x6f\x6d\x74\x16\x70\x92\x4a\x72\x2d\x94\x0b\x7f\x62\x29\x48\x39\xb0\x7e\x95\x09\xc7\x6e\xf0\x8b\x27\xeb\xd8\x74\x4d\xb4\xd3\xf0\xea\xc0\x8a\xc0\xe7\xec\xec\x49\xf3\xc0\x5c\xc1\x14\x33\x92\x53\xb4\xf4\x3b\xdb\x8a\xad\x62\x23\x36\xc2\x20\x6b\xd5\xdf\xd2\xe6\xe1\x42\xbd\xb5\x8d\xea\xc1\xdc\xaf\xf6\x38\xe5\xc5\x29\x7c\xaa\xd5\x5a\x6c\x9a\x3b\x7d\xb7\x78\xc5\x22\x31\x6d\xf0\x4e\x19\xf6\xeb\x25\x7a\xf2\x2b\x32\x8a\x1c\xd9\x68\x8b\x52\x24\xf5\xd2\xa0\xb9\x22\xc8\xc8\x5a\xdc\x70\x16\x9e\xdf\x2c\xd8\x09\x45\x96\x79\x57\x7b\xc4\x9a\xcb\x78\xc9\x1c\x90\x5c\xc3\xfb\xf7\xa0\x65\xb2\x24\xb9\x6e\x39\x9b\x74\xd1\x5c\x6b\x93\xa1\x9b\x04\xf5\xb4\x1e\x10\x8e\xb2\x8e\xbb\x03\x14\x90\xe1\xcb\x3c\x20\x80\x6f\x7a\x34\x88\xc6\xe0\x6b\x1b\xd7\x3a\x43\xa1\xb8\x22\x38\x4b\xff\xc5\xf5\x25\xb1\x8b\xb6\x23\xf8\x34\xe1\xfa\x99\x97\x84\x96\xf8\x81\xea\xe3\xbd\x5e\x31\x1c\x2e\xe5\xf2\x2f\xc1\xf3\xde\x20\xed\xe6\xee\x97\x89\x8b\xbf\x76\xd2\xfd\x21\xc4\x8b\x9a\x69\xb8\xbe\x4e\xba\xe1\x20\xe1\xe0\xcd\x21\x07\x87\x61\x37\x2b\x52\xff\xa9\xa8\x83\x37\x44\x5e\x10\xfd\x25\x96\x3e\xe9\x70\x84\xc1\xe2\xf7\x1a\x1e\x86\xea\xa7\xdf\xc0\xc5\xfa\x34\x1d\x16\xc2\x7e\x09\x3d\x5a\x87\xc6\xfd\xe1\x9d\x68\xc9\x5c\x7e\x7e\xf1\xf9\xfd\x17\x86\x3a\x82\x28\xe2\xf8\xea\xd8\x09\x6a\xeb\x89\xf6\xa3\x77\x75\xbf\xce\x7e\xc0\xca\x48\xaa\x98\x06\xad\x62\x02\x4b\xed\x54\x2a\x35\x5c\xfc\x29\x45\xfb\x55\xa9\x84\x71\x19\x35\x5f\xc3\xc7\x8f\xc0\x70\x5b\x07\x5e\xb4\x20\x32\xda\x3b\xea\x78\xaa\x4f\xfa\xc6\x97\x7b\xcb\x17\x81\xad\xcf\xf9\x9a\xdb\x50\x46\xce\xef\xff\x70\xa2\x2e\x4b\xc6\x3e\x9f\xb0\xdd\x5e\x1f\x85\xc2\xac\x05\x9c\x57\xa3\x90\x43\x70\xa5\xb4\xa3\xad\xde\x20\x18\xae\x8a\x56\x8b\x0f\xf1\xff\x36\xdf\x2f\x5c\xf9\xd0\xf5\x4b\x58\xd3\xf3\x6b\x73\x93\x63\xa6\x32\x7c\xb9\x25\xb5\xe1\xee\xf9\xaf\xc7\xce\xd0\xeb\x08\x67\x49\x7e\xb7\x67\xe6\x94\x0f\x0c\xb1\x7f\x8e\xde\xb6\xd9\xbe\x60\x7c\xa5\xb5\x24\x54\x07\xbb\xed\xfe\x12\xc1\xf1\x74\xa9\x8e\xe9\xb8\xb3\x08\xb3\x8a\xa1\xbd\x05\x6e\x48\xb9\x1b\xca\xa5\x7e\xcd\xc2\xc4\x63\xfd\xd6\x1e\x43\x64\xac\xbe\x73\xa2\x55\x9d\x5b\x1d\xab\x6a\x74\x77\xd6\x6d\x2f\x3a\x1e\xa2\xfe\xe6\xba\x5a\x8f\xf3\x9b\xa2\xc7\x66\x32\xe0\x52\x74\x90\x6a\x99\x58\xf0\x4a\xfc\xe2\x09\xe6\x37\xe5\x64\xe1\x12\x84\xe2\x8c\xcf\x5d\xf7\xe3\xe3\xfc\xc6\x8e\x01\xbe\xa5\x98\xfd\x02\x9e\xbb\x52\x4b\xa2\xd5\x85\x83\x1f\xef\x6e\x7f\x02\x3e\x17\xee\x5d\x16\xad\x36\x13\x55\x80\x52\x14\x33\x90\x42\xbe\x80\x93\x29\x94\xfc\xc4\x98\x73\x3b\x6b\x3b\xd0\x73\xd1\xae\x1c\xa0\x4a\x20\x25\x99\x73\x3b\xff\x44\x60\xbd\x29\x25\x61\x72\x61\x37\xa8\x18\x12\x0d\xdc\x9d\x6f\xc8\x41\xac\xd5\x5a\xb6\x35\xe8\x03\x74\xde\x93\xa6\xf6\xd3\xb7\x63\x9b\x74\x56\x8d\xa7\x2a\x76\x89\xd6\x3d\x18\x54\x56\x54\x93\xb6\xae\xea\xe7\xc0\xe4\xb7\x68\x1d\x38\x91\x51\x31\xc3\xa8\x38\x03\xb7\x43\x45\x49\x31\xf0\xd0\x8a\xe0\x60\x26\xd8\xa2\x10\x0d\xa8\xb4\x4b\xc9\xb4\x2b\x6c\xc0\x5b\xc6\x62\x3c\x86\xa9\xc8\x60\x11\x1e\xc2\x60\x6c\x2f\x86\xb0\x35\x39\x9e\xd1\x76\x4d\x59\x06\xf3\x54\xa5\xcb\x21\xcc\x7c\xef\x33\x54\x91\x21\x4c\x38\x8f\x56\x57\x41\xa8\x44\xc4\x18\x86\x51\x09\x39\x14\xd2\x02\xae\xb4\xef\xaa\xaf\xa0\x14\x68\x67\x84\x73\x59\x37\x84\xb6\x39\xee\xec\xe0\x9c\xd5\x58\x1c\xe7\x92\xe4\xd0\x1d\x2e\x6c\x93\xa1\xb3\x95\xd9\x96\xaa\x3b\x38\x5a\x86\xa3\xc5\x60\xb5\xc6\xcc\x65\x70\x45\xbd\x86\x07\xe3\xe9\x12\xfe\x89\xd2\xd2\x25\x3c\xaa\x27\xa5\x9f\xcf\xe7\x2b\x1c\x18\xa4\x27\x4e\x39\x7a\x0d\xb1\xf4\x96\xcb\x86\x1d\x5f\x67\x92\xee\x6b\x14\xa2\xee\x88\x8b\x02\xde\x96\x8d\xde\xfa\xa8\xbb\x9d\xe4\xf2\xf3\xad\xcf\x20\x4a\xa9\x63\x0e\xad\x76\xc5\xd5\x3f\x29\x9d\x1a\x37\x0c\x9c\xde\x74\xf6\x3e\xbb\xcf\x47\xe7\x8d\x6f\xda\xeb\x96\xd3\x37\xfb\xea\xdd\xe6\x17\xad\xfa\x5e\xed\xe3\xd4\x20\x11\xf7\x69\xf1\x98\x52\xd5\x40\xf0\x6e\xc4\x39\x70\x78\xe1\xd8\x4a\xf1\x08\x18\x4a\xf1\x64\x02\xce\xf8\x02\xb7\x75\xda\x84\xfa\x71\x0f\xf1\xab\xdd\x94\xbf\xe2\xb0\x8c\x74\xf8\xf5\xb7\xd1\xff\x03\x00\x00\xff\xff\x4f\x07\xfa\x94\xba\x1d\x00\x00") func chartCrdsNetworkHarvesterhciIo_ippoolsYamlBytes() ([]byte, error) { return bindataRead( @@ -94,7 +94,7 @@ func chartCrdsNetworkHarvesterhciIo_ippoolsYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "chart/crds/network.harvesterhci.io_ippools.yaml", size: 7603, mode: os.FileMode(420), modTime: time.Unix(1750154346, 0)} + info := bindataFileInfo{name: "chart/crds/network.harvesterhci.io_ippools.yaml", size: 7610, mode: os.FileMode(420), modTime: time.Unix(1766266591, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -114,7 +114,7 @@ func chartCrdsNetworkHarvesterhciIo_virtualmachinenetworkconfigsYaml() (*asset, return nil, err } - info := bindataFileInfo{name: "chart/crds/network.harvesterhci.io_virtualmachinenetworkconfigs.yaml", size: 4603, mode: os.FileMode(420), modTime: time.Unix(1750154346, 0)} + info := bindataFileInfo{name: "chart/crds/network.harvesterhci.io_virtualmachinenetworkconfigs.yaml", size: 4603, mode: os.FileMode(420), modTime: time.Unix(1766266591, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/fake/fake_virtualmachineinstance.go b/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/fake/fake_virtualmachineinstance.go index e05e3759..2bed5f5e 100644 --- a/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/fake/fake_virtualmachineinstance.go +++ b/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/fake/fake_virtualmachineinstance.go @@ -105,19 +105,6 @@ func (c *FakeVirtualMachineInstances) Update(ctx context.Context, virtualMachine return obj.(*v1.VirtualMachineInstance), err } -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeVirtualMachineInstances) UpdateStatus(ctx context.Context, virtualMachineInstance *v1.VirtualMachineInstance, opts metav1.UpdateOptions) (result *v1.VirtualMachineInstance, err error) { - emptyResult := &v1.VirtualMachineInstance{} - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceActionWithOptions(virtualmachineinstancesResource, "status", c.ns, virtualMachineInstance, opts), emptyResult) - - if obj == nil { - return emptyResult, err - } - return obj.(*v1.VirtualMachineInstance), err -} - // Delete takes name of the virtualMachineInstance and deletes it. Returns an error if one occurs. func (c *FakeVirtualMachineInstances) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { _, err := c.Fake. diff --git a/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/virtualmachineinstance.go b/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/virtualmachineinstance.go index 289f10bd..acedc0a3 100644 --- a/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/virtualmachineinstance.go +++ b/pkg/generated/clientset/versioned/typed/kubevirt.io/v1/virtualmachineinstance.go @@ -39,8 +39,6 @@ type VirtualMachineInstancesGetter interface { type VirtualMachineInstanceInterface interface { Create(ctx context.Context, virtualMachineInstance *v1.VirtualMachineInstance, opts metav1.CreateOptions) (*v1.VirtualMachineInstance, error) Update(ctx context.Context, virtualMachineInstance *v1.VirtualMachineInstance, opts metav1.UpdateOptions) (*v1.VirtualMachineInstance, error) - // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, virtualMachineInstance *v1.VirtualMachineInstance, opts metav1.UpdateOptions) (*v1.VirtualMachineInstance, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.VirtualMachineInstance, error) diff --git a/pkg/generated/controllers/apps/factory.go b/pkg/generated/controllers/apps/factory.go new file mode 100644 index 00000000..b84233bd --- /dev/null +++ b/pkg/generated/controllers/apps/factory.go @@ -0,0 +1,72 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +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. +*/ + +// Code generated by main. DO NOT EDIT. + +package apps + +import ( + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/v3/pkg/generic" + "k8s.io/client-go/rest" +) + +type Factory struct { + *generic.Factory +} + +func NewFactoryFromConfigOrDie(config *rest.Config) *Factory { + f, err := NewFactoryFromConfig(config) + if err != nil { + panic(err) + } + return f +} + +func NewFactoryFromConfig(config *rest.Config) (*Factory, error) { + return NewFactoryFromConfigWithOptions(config, nil) +} + +func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) { + return NewFactoryFromConfigWithOptions(config, &FactoryOptions{ + Namespace: namespace, + }) +} + +type FactoryOptions = generic.FactoryOptions + +func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) { + f, err := generic.NewFactoryFromConfigWithOptions(config, opts) + return &Factory{ + Factory: f, + }, err +} + +func NewFactoryFromConfigWithOptionsOrDie(config *rest.Config, opts *FactoryOptions) *Factory { + f, err := NewFactoryFromConfigWithOptions(config, opts) + if err != nil { + panic(err) + } + return f +} + +func (c *Factory) Apps() Interface { + return New(c.ControllerFactory()) +} + +func (c *Factory) WithAgent(userAgent string) Interface { + return New(controller.NewSharedControllerFactoryWithAgent(userAgent, c.ControllerFactory())) +} diff --git a/pkg/generated/controllers/apps/interface.go b/pkg/generated/controllers/apps/interface.go new file mode 100644 index 00000000..bb05033d --- /dev/null +++ b/pkg/generated/controllers/apps/interface.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +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. +*/ + +// Code generated by main. DO NOT EDIT. + +package apps + +import ( + v1 "github.com/harvester/vm-dhcp-controller/pkg/generated/controllers/apps/v1" + "github.com/rancher/lasso/pkg/controller" +) + +type Interface interface { + V1() v1.Interface +} + +type group struct { + controllerFactory controller.SharedControllerFactory +} + +// New returns a new Interface. +func New(controllerFactory controller.SharedControllerFactory) Interface { + return &group{ + controllerFactory: controllerFactory, + } +} + +func (g *group) V1() v1.Interface { + return v1.New(g.controllerFactory) +} diff --git a/pkg/generated/controllers/apps/v1/deployment.go b/pkg/generated/controllers/apps/v1/deployment.go new file mode 100644 index 00000000..feedeabc --- /dev/null +++ b/pkg/generated/controllers/apps/v1/deployment.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +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. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1 + +import ( + "context" + "sync" + "time" + + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// DeploymentController interface for managing Deployment resources. +type DeploymentController interface { + generic.ControllerInterface[*v1.Deployment, *v1.DeploymentList] +} + +// DeploymentClient interface for managing Deployment resources in Kubernetes. +type DeploymentClient interface { + generic.ClientInterface[*v1.Deployment, *v1.DeploymentList] +} + +// DeploymentCache interface for retrieving Deployment resources in memory. +type DeploymentCache interface { + generic.CacheInterface[*v1.Deployment] +} + +// DeploymentStatusHandler is executed for every added or modified Deployment. Should return the new status to be updated +type DeploymentStatusHandler func(obj *v1.Deployment, status v1.DeploymentStatus) (v1.DeploymentStatus, error) + +// DeploymentGeneratingHandler is the top-level handler that is executed for every Deployment event. It extends DeploymentStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type DeploymentGeneratingHandler func(obj *v1.Deployment, status v1.DeploymentStatus) ([]runtime.Object, v1.DeploymentStatus, error) + +// RegisterDeploymentStatusHandler configures a DeploymentController to execute a DeploymentStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterDeploymentStatusHandler(ctx context.Context, controller DeploymentController, condition condition.Cond, name string, handler DeploymentStatusHandler) { + statusHandler := &deploymentStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterDeploymentGeneratingHandler configures a DeploymentController to execute a DeploymentGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterDeploymentGeneratingHandler(ctx context.Context, controller DeploymentController, apply apply.Apply, + condition condition.Cond, name string, handler DeploymentGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &deploymentGeneratingHandler{ + DeploymentGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterDeploymentStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type deploymentStatusHandler struct { + client DeploymentClient + condition condition.Cond + handler DeploymentStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *deploymentStatusHandler) sync(key string, obj *v1.Deployment) (*v1.Deployment, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type deploymentGeneratingHandler struct { + DeploymentGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *deploymentGeneratingHandler) Remove(key string, obj *v1.Deployment) (*v1.Deployment, error) { + if obj != nil { + return obj, nil + } + + obj = &v1.Deployment{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured DeploymentGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *deploymentGeneratingHandler) Handle(obj *v1.Deployment, status v1.DeploymentStatus) (v1.DeploymentStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.DeploymentGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *deploymentGeneratingHandler) isNewResourceVersion(obj *v1.Deployment) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *deploymentGeneratingHandler) storeResourceVersion(obj *v1.Deployment) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/pkg/generated/controllers/apps/v1/interface.go b/pkg/generated/controllers/apps/v1/interface.go new file mode 100644 index 00000000..c6ca22fd --- /dev/null +++ b/pkg/generated/controllers/apps/v1/interface.go @@ -0,0 +1,49 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +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. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1 + +import ( + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/schemes" + v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func init() { + schemes.Register(v1.AddToScheme) +} + +type Interface interface { + Deployment() DeploymentController +} + +func New(controllerFactory controller.SharedControllerFactory) Interface { + return &version{ + controllerFactory: controllerFactory, + } +} + +type version struct { + controllerFactory controller.SharedControllerFactory +} + +func (v *version) Deployment() DeploymentController { + return generic.NewController[*v1.Deployment, *v1.DeploymentList](schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, "deployments", true, v.controllerFactory) +} diff --git a/pkg/util/fakeclient/deployment.go b/pkg/util/fakeclient/deployment.go new file mode 100644 index 00000000..3e7a1968 --- /dev/null +++ b/pkg/util/fakeclient/deployment.go @@ -0,0 +1,69 @@ +package fakeclient + +import ( + "context" + + "github.com/rancher/wrangler/v3/pkg/generic" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + typeappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1" + "k8s.io/client-go/rest" +) + +type DeploymentClient func(string) typeappsv1.DeploymentInterface + +func (c DeploymentClient) Update(deployment *appsv1.Deployment) (*appsv1.Deployment, error) { + return c(deployment.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{}) +} +func (c DeploymentClient) Get(namespace, name string, options metav1.GetOptions) (*appsv1.Deployment, error) { + return c(namespace).Get(context.TODO(), name, metav1.GetOptions{}) +} +func (c DeploymentClient) Create(deployment *appsv1.Deployment) (*appsv1.Deployment, error) { + return c(deployment.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}) +} +func (c DeploymentClient) Delete(namespace, name string, options *metav1.DeleteOptions) error { + return c(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) +} +func (c DeploymentClient) List(namespace string, opts metav1.ListOptions) (*appsv1.DeploymentList, error) { + panic("implement me") +} +func (c DeploymentClient) UpdateStatus(deployment *appsv1.Deployment) (*appsv1.Deployment, error) { + return c(deployment.Namespace).UpdateStatus(context.TODO(), deployment, metav1.UpdateOptions{}) +} +func (c DeploymentClient) Watch(namespace string, opts metav1.ListOptions) (watch.Interface, error) { + panic("implement me") +} +func (c DeploymentClient) Patch(namespace, name string, pt types.PatchType, data []byte, subresources ...string) (result *appsv1.Deployment, err error) { + panic("implement me") +} + +func (c DeploymentClient) WithImpersonation(config rest.ImpersonationConfig) (generic.ClientInterface[*appsv1.Deployment, *appsv1.DeploymentList], error) { + panic("implement me") +} + +type DeploymentCache func(string) typeappsv1.DeploymentInterface + +func (c DeploymentCache) Get(namespace, name string) (*appsv1.Deployment, error) { + return c(namespace).Get(context.TODO(), name, metav1.GetOptions{}) +} +func (c DeploymentCache) List(namespace string, selector labels.Selector) ([]*appsv1.Deployment, error) { + list, err := c(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return nil, err + } + result := make([]*appsv1.Deployment, 0, len(list.Items)) + for _, deployment := range list.Items { + dep := deployment + result = append(result, &dep) + } + return result, err +} +func (c DeploymentCache) AddIndexer(indexName string, indexer generic.Indexer[*appsv1.Deployment]) { + panic("implement me") +} +func (c DeploymentCache) GetByIndex(indexName, key string) ([]*appsv1.Deployment, error) { + panic("implement me") +}