···22name: hsm-secrets-operator
33description: A Kubernetes operator that bridges Pico HSM binary data storage with Kubernetes Secrets
44type: application
55-version: 0.5.14
66-appVersion: v0.5.14
55+version: 0.5.15
66+appVersion: v0.5.15
77icon: https://raw.githubusercontent.com/cncf/artwork/master/projects/kubernetes/icon/color/kubernetes-icon-color.svg
88home: https://github.com/evanjarrett/hsm-secrets-operator
99sources:
+224-196
internal/agent/deployment.go
···1919import (
2020 "context"
2121 "fmt"
2222+ "slices"
2323+ "strings"
2224 "sync"
2325 "time"
2426···4042// ManagerInterface defines the interface for HSM agent management
4143// This allows for easier testing with mocks
4244type ManagerInterface interface {
4343- EnsureAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret) (string, error)
4545+ EnsureAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret) error
4446 CleanupAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice) error
4547}
4648···131133 return m
132134}
133135134134-// EnsureAgent ensures an HSM agent pod exists for the given HSM device
135135-func (m *Manager) EnsureAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret) (string, error) {
136136+// EnsureAgent ensures HSM agents are deployed for all available devices in the pool
137137+func (m *Manager) EnsureAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret) error {
138138+ // Get the HSMPool for this device to find all aggregated devices
139139+ poolName := hsmDevice.Name + "-pool"
140140+ var hsmPool hsmv1alpha1.HSMPool
141141+ if err := m.Get(ctx, types.NamespacedName{
142142+ Name: poolName,
143143+ Namespace: hsmDevice.Namespace,
144144+ }, &hsmPool); err != nil {
145145+ return fmt.Errorf("failed to get HSMPool %s: %w", poolName, err)
146146+ }
136147 m.mu.Lock()
137148 defer m.mu.Unlock()
138149139139- deviceName := hsmDevice.Name
140140- agentName := m.generateAgentName(hsmDevice)
141141- agentNamespace := hsmDevice.Namespace
150150+ // Ensure agents for each available aggregated device in the pool
151151+ for i, aggregatedDevice := range hsmPool.Status.AggregatedDevices {
152152+ if !aggregatedDevice.Available {
153153+ continue
154154+ }
155155+156156+ // Create unique agent name for each physical device
157157+ agentName := fmt.Sprintf("%s-%s-%d", AgentNamePrefix, hsmDevice.Name, i)
158158+ agentKey := fmt.Sprintf("%s-%d", hsmDevice.Name, i) // Unique key for tracking
142159143143- // Check if we already have this agent tracked
144144- if agentInfo, exists := m.activeAgents[deviceName]; exists {
145145- // Agent exists in tracking, verify it's still healthy
146146- if m.isAgentHealthy(ctx, agentInfo) {
147147- // Return the first pod IP as endpoint for backward compatibility
148148- if len(agentInfo.PodIPs) > 0 {
149149- return fmt.Sprintf("http://%s:%d", agentInfo.PodIPs[0], AgentPort), nil
160160+ // Check if we already have this specific agent tracked
161161+ if agentInfo, exists := m.activeAgents[agentKey]; exists {
162162+ // Agent exists in tracking, verify it's still healthy
163163+ if m.isAgentHealthy(ctx, agentInfo) {
164164+ continue // Agent is healthy, skip
150165 }
166166+ // Agent unhealthy, remove from tracking and recreate
167167+ m.removeAgentFromTracking(agentKey)
151168 }
152152- // Agent unhealthy, remove from tracking and recreate
153153- m.removeAgentFromTracking(deviceName)
154154- }
155169156156- // Check if deployment exists in Kubernetes
157157- var deployment appsv1.Deployment
158158- err := m.Get(ctx, types.NamespacedName{
159159- Name: agentName,
160160- Namespace: agentNamespace,
161161- }, &deployment)
170170+ // Check if deployment exists in Kubernetes
171171+ var deployment appsv1.Deployment
172172+ err := m.Get(ctx, types.NamespacedName{
173173+ Name: agentName,
174174+ Namespace: hsmDevice.Namespace,
175175+ }, &deployment)
162176163163- if err == nil {
164164- // Agent exists, but check if volume mounts need updating due to device path changes
165165- needsUpdate, err := m.agentNeedsUpdate(ctx, &deployment, hsmDevice)
166166- if err != nil {
167167- return "", fmt.Errorf("failed to check if agent needs update: %w", err)
168168- }
177177+ if err == nil {
178178+ // Agent exists, but check if it's still targeting the right device/node
179179+ needsUpdate := m.deploymentNeedsUpdateForDevice(&deployment, &aggregatedDevice)
169180170170- if needsUpdate {
171171- // Delete existing deployment to trigger recreation with new volume mounts
172172- if err := m.Delete(ctx, &deployment); err != nil {
173173- return "", fmt.Errorf("failed to delete outdated agent deployment: %w", err)
174174- }
175175- // Continue to create new deployment below
176176- } else {
177177- // Agent exists in K8s but not tracked - wait for it and track it
178178- podIPs, err := m.waitForAgentReady(ctx, agentName, agentNamespace)
179179- if err != nil {
180180- return "", fmt.Errorf("failed waiting for existing agent pods: %w", err)
181181- }
181181+ if needsUpdate {
182182+ // Delete existing deployment to trigger recreation
183183+ if err := m.Delete(ctx, &deployment); err != nil {
184184+ return fmt.Errorf("failed to delete outdated agent deployment %s: %w", agentName, err)
185185+ }
186186+ } else {
187187+ // Agent exists and is correct - wait for it and track it
188188+ podIPs, err := m.waitForAgentReady(ctx, agentName, hsmDevice.Namespace)
189189+ if err != nil {
190190+ return fmt.Errorf("failed waiting for existing agent pods %s: %w", agentName, err)
191191+ }
182192183183- // Track the existing agent
184184- agentInfo := &AgentInfo{
185185- DeviceName: deviceName,
186186- PodIPs: podIPs,
187187- CreatedAt: time.Now(),
188188- LastHealthCheck: time.Now(),
189189- Status: AgentStatusReady,
190190- AgentName: agentName,
191191- Namespace: agentNamespace,
192192- }
193193+ // Track the existing agent
194194+ agentInfo := &AgentInfo{
195195+ DeviceName: agentKey,
196196+ PodIPs: podIPs,
197197+ CreatedAt: time.Now(),
198198+ LastHealthCheck: time.Now(),
199199+ Status: AgentStatusReady,
200200+ AgentName: agentName,
201201+ Namespace: hsmDevice.Namespace,
202202+ }
193203194194- m.activeAgents[deviceName] = agentInfo
195195- return fmt.Sprintf("http://%s:%d", podIPs[0], AgentPort), nil
204204+ m.activeAgents[agentKey] = agentInfo
205205+ continue
206206+ }
207207+ } else if !errors.IsNotFound(err) {
208208+ return fmt.Errorf("failed to check agent deployment %s: %w", agentName, err)
196209 }
197197- }
198210199199- if !errors.IsNotFound(err) {
200200- return "", fmt.Errorf("failed to check agent deployment: %w", err)
201201- }
211211+ // Create agent deployment for this specific device
212212+ if err := m.createAgentDeployment(ctx, hsmDevice, nil, hsmDevice.Namespace, &aggregatedDevice, agentName); err != nil {
213213+ return fmt.Errorf("failed to create agent deployment %s: %w", agentName, err)
214214+ }
202215203203- // Create agent deployment
204204- if err := m.createAgentDeployment(ctx, hsmDevice, hsmSecret, agentNamespace); err != nil {
205205- return "", fmt.Errorf("failed to create agent deployment: %w", err)
206206- }
216216+ // Wait for agent pods to be ready and get their IPs
217217+ podIPs, err := m.waitForAgentReady(ctx, agentName, hsmDevice.Namespace)
218218+ if err != nil {
219219+ return fmt.Errorf("failed waiting for agent pods %s: %w", agentName, err)
220220+ }
207221208208- // Wait for agent pods to be ready and get their IPs
209209- podIPs, err := m.waitForAgentReady(ctx, agentName, agentNamespace)
210210- if err != nil {
211211- return "", fmt.Errorf("failed waiting for agent pods: %w", err)
212212- }
222222+ // Track the new agent
223223+ agentInfo := &AgentInfo{
224224+ DeviceName: agentKey,
225225+ PodIPs: podIPs,
226226+ CreatedAt: time.Now(),
227227+ LastHealthCheck: time.Now(),
228228+ Status: AgentStatusReady,
229229+ AgentName: agentName,
230230+ Namespace: hsmDevice.Namespace,
231231+ }
213232214214- // Track the new agent
215215- agentInfo := &AgentInfo{
216216- DeviceName: deviceName,
217217- PodIPs: podIPs,
218218- CreatedAt: time.Now(),
219219- LastHealthCheck: time.Now(),
220220- Status: AgentStatusReady,
221221- AgentName: agentName,
222222- Namespace: agentNamespace,
233233+ m.activeAgents[agentKey] = agentInfo
223234 }
224235225225- m.activeAgents[deviceName] = agentInfo
226226-227227- // For backward compatibility, still return HTTP endpoint (will change to gRPC later)
228228- return fmt.Sprintf("http://%s:%d", podIPs[0], AgentPort), nil
236236+ return nil
229237}
230238231231-// CleanupAgent removes the HSM agent for the given device when no longer needed
239239+// CleanupAgent removes all HSM agents for the given device when no longer needed
232240func (m *Manager) CleanupAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice) error {
233241 m.mu.Lock()
234242 defer m.mu.Unlock()
235235-236236- agentName := m.generateAgentName(hsmDevice)
237243238244 // Check if any HSMSecrets still reference this device
239245 var hsmSecretList hsmv1alpha1.HSMSecretList
···254260 return nil
255261 }
256262257257- // Remove from internal tracking
258258- m.removeAgentFromTracking(hsmDevice.Name)
263263+ // Get the HSMPool to find all agent deployments to clean up
264264+ poolName := hsmDevice.Name + "-pool"
265265+ var hsmPool hsmv1alpha1.HSMPool
266266+ if err := m.Get(ctx, types.NamespacedName{
267267+ Name: poolName,
268268+ Namespace: hsmDevice.Namespace,
269269+ }, &hsmPool); err != nil {
270270+ // If pool doesn't exist, try to clean up any remaining tracked agents
271271+ return m.cleanupTrackedAgents(ctx, hsmDevice)
272272+ }
259273260260- // Delete deployment
261261- deployment := &appsv1.Deployment{
262262- ObjectMeta: metav1.ObjectMeta{
263263- Name: agentName,
264264- Namespace: hsmDevice.Namespace,
265265- },
274274+ // Clean up all agent deployments (one per aggregated device)
275275+ for i := range hsmPool.Status.AggregatedDevices {
276276+ agentName := fmt.Sprintf("%s-%s-%d", AgentNamePrefix, hsmDevice.Name, i)
277277+ agentKey := fmt.Sprintf("%s-%d", hsmDevice.Name, i)
278278+279279+ // Remove from internal tracking
280280+ m.removeAgentFromTracking(agentKey)
281281+282282+ // Delete deployment
283283+ deployment := &appsv1.Deployment{
284284+ ObjectMeta: metav1.ObjectMeta{
285285+ Name: agentName,
286286+ Namespace: hsmDevice.Namespace,
287287+ },
288288+ }
289289+ if err := m.Delete(ctx, deployment); err != nil && !errors.IsNotFound(err) {
290290+ return fmt.Errorf("failed to delete agent deployment %s: %w", agentName, err)
291291+ }
266292 }
267267- if err := m.Delete(ctx, deployment); err != nil && !errors.IsNotFound(err) {
268268- return fmt.Errorf("failed to delete agent deployment: %w", err)
293293+294294+ return nil
295295+}
296296+297297+// cleanupTrackedAgents cleans up any remaining tracked agents when HSMPool is not available
298298+func (m *Manager) cleanupTrackedAgents(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice) error {
299299+ // Find all tracked agents for this device
300300+ var agentsToCleanup []string
301301+ devicePrefix := hsmDevice.Name + "-"
302302+303303+ for agentKey := range m.activeAgents {
304304+ if strings.HasPrefix(agentKey, devicePrefix) {
305305+ agentsToCleanup = append(agentsToCleanup, agentKey)
306306+ }
307307+ }
308308+309309+ // Clean up each tracked agent
310310+ for _, agentKey := range agentsToCleanup {
311311+ agentInfo := m.activeAgents[agentKey]
312312+313313+ // Remove from tracking
314314+ m.removeAgentFromTracking(agentKey)
315315+316316+ // Delete deployment
317317+ deployment := &appsv1.Deployment{
318318+ ObjectMeta: metav1.ObjectMeta{
319319+ Name: agentInfo.AgentName,
320320+ Namespace: agentInfo.Namespace,
321321+ },
322322+ }
323323+ if err := m.Delete(ctx, deployment); err != nil && !errors.IsNotFound(err) {
324324+ return fmt.Errorf("failed to delete agent deployment %s: %w", agentInfo.AgentName, err)
325325+ }
269326 }
270327271328 return nil
···276333 return fmt.Sprintf("%s-%s", AgentNamePrefix, hsmDevice.Name)
277334}
278335279279-// createAgentDeployment creates the HSM agent deployment
280280-func (m *Manager) createAgentDeployment(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret, namespace string) error {
281281- agentName := m.generateAgentName(hsmDevice)
336336+// createAgentDeployment creates the HSM agent deployment for a specific device
337337+func (m *Manager) createAgentDeployment(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret, namespace string, specificDevice *hsmv1alpha1.DiscoveredDevice, customAgentName string) error {
338338+ if specificDevice == nil {
339339+ return fmt.Errorf("specificDevice is required")
340340+ }
282341283283- // Find the node where the HSM device is located
284284- targetNode := m.findTargetNode(hsmDevice)
285285- if targetNode == "" {
286286- return fmt.Errorf("no target node found for HSM device %s", hsmDevice.Name)
342342+ var agentName string
343343+ if customAgentName != "" {
344344+ agentName = customAgentName
345345+ } else {
346346+ agentName = m.generateAgentName(hsmDevice)
287347 }
348348+349349+ targetNode := specificDevice.NodeName
350350+ devicePath := specificDevice.DevicePath
288351289352 // Get discovery image from environment, manager image, or use default
290353 agentImage := m.ImageResolver.GetImage(ctx, "AGENT_IMAGE")
···420483 RunAsNonRoot: boolPtr(false),
421484 RunAsUser: int64Ptr(0),
422485 },
423423- VolumeMounts: m.buildAgentVolumeMounts(hsmDevice),
486486+ VolumeMounts: m.buildAgentVolumeMounts(),
424487 },
425488 },
426426- Volumes: m.buildAgentVolumes(hsmDevice),
489489+ Volumes: m.buildAgentVolumes(devicePath),
427490 },
428491 },
429492 },
···439502 return m.Create(ctx, deployment)
440503}
441504442442-// findTargetNode finds the node where the HSM device is located by checking the HSMPool
443443-func (m *Manager) findTargetNode(hsmDevice *hsmv1alpha1.HSMDevice) string {
444444- // Find the HSMPool for this device
445445- poolName := hsmDevice.Name + "-pool"
446446- pool := &hsmv1alpha1.HSMPool{}
447447-448448- ctx := context.Background()
449449- err := m.Get(ctx, types.NamespacedName{
450450- Name: poolName,
451451- Namespace: hsmDevice.Namespace,
452452- }, pool)
453453-454454- if err != nil {
455455- // Fallback: if no pool found, use node selector if present
456456- if hsmDevice.Spec.NodeSelector != nil {
457457- // This would need more sophisticated logic to map selectors to actual nodes
458458- // For now, return empty to indicate no target found
459459- return ""
460460- }
461461- return ""
462462- }
463463-464464- // Look for discovered devices in the pool status
465465- for _, device := range pool.Status.AggregatedDevices {
466466- if device.Available && device.NodeName != "" {
467467- return device.NodeName
468468- }
469469- }
470470-471471- // Fallback: if no specific node found, use node selector if present
472472- if hsmDevice.Spec.NodeSelector != nil {
473473- // This would need more sophisticated logic to map selectors to actual nodes
474474- // For now, return empty to indicate no target found
475475- return ""
476476- }
477477-478478- return ""
479479-}
480480-481505// secretReferencesDevice checks if an HSMSecret references the given device
482506func (m *Manager) secretReferencesDevice(hsmSecret *hsmv1alpha1.HSMSecret, hsmDevice *hsmv1alpha1.HSMDevice) bool {
483507 // This is a simplified check - in practice, you might want more sophisticated logic
···544568}
545569546570// buildAgentVolumeMounts builds volume mounts for the HSM agent
547547-func (m *Manager) buildAgentVolumeMounts(hsmDevice *hsmv1alpha1.HSMDevice) []corev1.VolumeMount {
548548- mounts := []corev1.VolumeMount{
571571+func (m *Manager) buildAgentVolumeMounts() []corev1.VolumeMount {
572572+ return []corev1.VolumeMount{
549573 {
550574 Name: "tmp",
551575 MountPath: "/tmp",
552576 },
553553- }
554554-555555- // Add device mounts if needed - get from HSMPool
556556- poolName := hsmDevice.Name + "-pool"
557557- pool := &hsmv1alpha1.HSMPool{}
558558-559559- ctx := context.Background()
560560- err := m.Get(ctx, types.NamespacedName{
561561- Name: poolName,
562562- Namespace: hsmDevice.Namespace,
563563- }, pool)
564564-565565- if err == nil {
566566- for _, device := range pool.Status.AggregatedDevices {
567567- if device.DevicePath != "" {
568568- mounts = append(mounts, corev1.VolumeMount{
569569- Name: "hsm-device",
570570- MountPath: "/dev/hsm",
571571- })
572572- break // Only need one mount point
573573- }
574574- }
577577+ {
578578+ Name: "hsm-device",
579579+ MountPath: "/dev/hsm",
580580+ },
575581 }
576576-577577- return mounts
578582}
579583580584// buildAgentVolumes builds volumes for the HSM agent
581581-func (m *Manager) buildAgentVolumes(hsmDevice *hsmv1alpha1.HSMDevice) []corev1.Volume {
582582- volumes := []corev1.Volume{
585585+func (m *Manager) buildAgentVolumes(devicePath string) []corev1.Volume {
586586+ return []corev1.Volume{
583587 {
584588 Name: "tmp",
585589 VolumeSource: corev1.VolumeSource{
586590 EmptyDir: &corev1.EmptyDirVolumeSource{},
587591 },
588592 },
589589- }
590590-591591- // Add device volumes if needed - get from HSMPool
592592- poolName := hsmDevice.Name + "-pool"
593593- pool := &hsmv1alpha1.HSMPool{}
594594-595595- ctx := context.Background()
596596- err := m.Get(ctx, types.NamespacedName{
597597- Name: poolName,
598598- Namespace: hsmDevice.Namespace,
599599- }, pool)
600600-601601- if err == nil {
602602- for _, device := range pool.Status.AggregatedDevices {
603603- if device.DevicePath != "" {
604604- volumes = append(volumes, corev1.Volume{
605605- Name: "hsm-device",
606606- VolumeSource: corev1.VolumeSource{
607607- HostPath: &corev1.HostPathVolumeSource{
608608- Path: device.DevicePath,
609609- Type: hostPathTypePtr(corev1.HostPathCharDev),
610610- },
611611- },
612612- })
613613- break // Only need one volume
614614- }
615615- }
593593+ {
594594+ Name: "hsm-device",
595595+ VolumeSource: corev1.VolumeSource{
596596+ HostPath: &corev1.HostPathVolumeSource{
597597+ Path: devicePath,
598598+ Type: hostPathTypePtr(corev1.HostPathCharDev),
599599+ },
600600+ },
601601+ },
616602 }
617617-618618- return volumes
619603}
620604621605// agentNeedsUpdate checks if the agent deployment needs to be updated due to device path or image changes
···701685 }
702686703687 return false, nil
688688+}
689689+690690+// deploymentNeedsUpdateForDevice checks if a deployment needs to be updated for a specific device
691691+// This is a simplified check that only validates device-specific configuration
692692+func (m *Manager) deploymentNeedsUpdateForDevice(deployment *appsv1.Deployment, aggregatedDevice *hsmv1alpha1.DiscoveredDevice) bool {
693693+ // Check node affinity - ensure agent is pinned to the correct node
694694+ if deployment.Spec.Template.Spec.Affinity == nil ||
695695+ deployment.Spec.Template.Spec.Affinity.NodeAffinity == nil ||
696696+ deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil {
697697+ return true // Missing required node affinity
698698+ }
699699+700700+ // Check if the node name matches the aggregated device's node
701701+ nodeSelector := deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution
702702+ if len(nodeSelector.NodeSelectorTerms) == 0 {
703703+ return true
704704+ }
705705+706706+ // Check if hostname requirement matches the device's node
707707+ nodeMatches := false
708708+ for _, term := range nodeSelector.NodeSelectorTerms {
709709+ for _, expr := range term.MatchExpressions {
710710+ if expr.Key == "kubernetes.io/hostname" && expr.Operator == corev1.NodeSelectorOpIn {
711711+ if slices.Contains(expr.Values, aggregatedDevice.NodeName) {
712712+ nodeMatches = true
713713+ }
714714+ }
715715+ }
716716+ }
717717+718718+ if !nodeMatches {
719719+ return true // Node doesn't match
720720+ }
721721+722722+ // Check device path in volume mounts
723723+ for _, vol := range deployment.Spec.Template.Spec.Volumes {
724724+ if vol.Name == "hsm-device" && vol.HostPath != nil {
725725+ if vol.HostPath.Path != aggregatedDevice.DevicePath {
726726+ return true // Device path changed
727727+ }
728728+ }
729729+ }
730730+731731+ return false
704732}
705733706734// Helper functions
+3-21
internal/controller/hsmpool_agent_controller.go
···64646565 // Only deploy agents for ready pools with discovered hardware
6666 if hsmPool.Status.Phase == hsmv1alpha1.HSMPoolPhaseReady && len(hsmPool.Status.AggregatedDevices) > 0 {
6767- // For each HSMDevice referenced by this pool, ensure an agent exists
6767+ // For each HSMDevice referenced by this pool, ensure agents exist for all aggregated devices
6868 for _, deviceRef := range hsmPool.Spec.HSMDeviceRefs {
6969 // Get the HSMDevice to pass to agent manager
7070 var hsmDevice hsmv1alpha1.HSMDevice
···7676 continue
7777 }
78787979- if err := r.ensureHSMAgent(ctx, &hsmDevice); err != nil {
8080- logger.Error(err, "Failed to ensure HSM agent", "device", deviceRef)
7979+ if err := r.AgentManager.EnsureAgent(ctx, &hsmDevice, nil); err != nil {
8080+ logger.Error(err, "Failed to ensure HSM agents for pool", "device", deviceRef)
8181 }
8282 }
8383 } else {
···9393 }
94949595 return ctrl.Result{}, nil
9696-}
9797-9898-// ensureHSMAgent ensures an HSM agent pod is running for the given device
9999-func (r *HSMPoolAgentReconciler) ensureHSMAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice) error {
100100- logger := log.FromContext(ctx)
101101-102102- if r.AgentManager == nil {
103103- return fmt.Errorf("agent manager not configured")
104104- }
105105-106106- // Ensure agent pod is running for this device
107107- agentEndpoint, err := r.AgentManager.EnsureAgent(ctx, hsmDevice, nil)
108108- if err != nil {
109109- return fmt.Errorf("failed to ensure HSM agent: %w", err)
110110- }
111111-112112- logger.Info("HSM agent ensured", "device", hsmDevice.Name, "endpoint", agentEndpoint)
113113- return nil
11496}
1159711698// cleanupStaleAgents removes agent deployments for devices that have been unavailable for too long
···192192193193 // Ensure agent pods are running for all devices and create clients
194194 for _, hsmDevice := range hsmDevices {
195195- // EnsureAgent now returns HTTP endpoint for backward compatibility, but we'll use gRPC
196196- _, err = r.AgentManager.EnsureAgent(ctx, hsmDevice, hsmSecret)
195195+ // EnsureAgent ensures agents for all devices in the pool
196196+ err = r.AgentManager.EnsureAgent(ctx, hsmDevice, hsmSecret)
197197 if err != nil {
198198 // Clean up any successful connections before returning error
199199 if err := deviceClients.Close(); err != nil {