···22name: hsm-secrets-operator
33description: A Kubernetes operator that bridges Pico HSM binary data storage with Kubernetes Secrets
44type: application
55-version: 0.4.5
66-appVersion: v0.4.5
55+version: 0.4.6
66+appVersion: v0.4.6
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:
+110-2
internal/agent/deployment.go
···3333 hsmv1alpha1 "github.com/evanjarrett/hsm-secrets-operator/api/v1alpha1"
3434)
35353636+// ManagerInterface defines the interface for HSM agent management
3737+// This allows for easier testing with mocks
3838+type ManagerInterface interface {
3939+ EnsureAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret) (string, error)
4040+ CleanupAgent(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice) error
4141+ GetAgentEndpoint(hsmDevice *hsmv1alpha1.HSMDevice) string
4242+}
4343+3644const (
3745 // AgentNamePrefix is the prefix for HSM agent deployment names
3846 AgentNamePrefix = "hsm-agent"
···8795 }, &deployment)
88968997 if err == nil {
9090- // Agent exists, ensure it's running and return endpoint
9191- return m.getAgentEndpoint(agentName, agentNamespace), nil
9898+ // Agent exists, but check if volume mounts need updating due to device path changes
9999+ needsUpdate, err := m.agentNeedsUpdate(ctx, &deployment, hsmDevice)
100100+ if err != nil {
101101+ return "", fmt.Errorf("failed to check if agent needs update: %w", err)
102102+ }
103103+104104+ if needsUpdate {
105105+ // Delete existing deployment to trigger recreation with new volume mounts
106106+ if err := m.Delete(ctx, &deployment); err != nil {
107107+ return "", fmt.Errorf("failed to delete outdated agent deployment: %w", err)
108108+ }
109109+ // Continue to create new deployment below
110110+ } else {
111111+ // Agent exists and is up to date, return endpoint
112112+ return m.getAgentEndpoint(agentName, agentNamespace), nil
113113+ }
92114 }
9311594116 if !errors.IsNotFound(err) {
···166188 return fmt.Sprintf("http://%s.%s.svc.cluster.local:%d", agentName, namespace, AgentPort)
167189}
168190191191+// GetAgentEndpoint returns the HTTP endpoint for the agent for a given HSM device
192192+// This implements the ManagerInterface
193193+func (m *Manager) GetAgentEndpoint(hsmDevice *hsmv1alpha1.HSMDevice) string {
194194+ agentName := m.generateAgentName(hsmDevice)
195195+ namespace := hsmDevice.Namespace
196196+ if namespace == "" {
197197+ namespace = m.AgentNamespace
198198+ }
199199+ return m.getAgentEndpoint(agentName, namespace)
200200+}
201201+169202// createAgentDeployment creates the HSM agent deployment
170203func (m *Manager) createAgentDeployment(ctx context.Context, hsmDevice *hsmv1alpha1.HSMDevice, hsmSecret *hsmv1alpha1.HSMSecret, namespace string) error {
171204 agentName := m.generateAgentName(hsmDevice)
···412445func (m *Manager) secretReferencesDevice(hsmSecret *hsmv1alpha1.HSMSecret, hsmDevice *hsmv1alpha1.HSMDevice) bool {
413446 // This is a simplified check - in practice, you might want more sophisticated logic
414447 // to determine which device an HSMSecret should use based on path, device type, etc.
448448+ _ = hsmSecret // TODO: Use for device preference checks
449449+ _ = hsmDevice // TODO: Use for device type compatibility
415450416451 // For now, assume any HSMSecret could use any available device of the right type
417452 // A more sophisticated implementation might check:
···544579 }
545580546581 return volumes
582582+}
583583+584584+// agentNeedsUpdate checks if the agent deployment needs to be updated due to device path changes
585585+func (m *Manager) agentNeedsUpdate(ctx context.Context, deployment *appsv1.Deployment, hsmDevice *hsmv1alpha1.HSMDevice) (bool, error) {
586586+ // Get current HSMPool to check for updated device paths
587587+ poolName := hsmDevice.Name + "-pool"
588588+ pool := &hsmv1alpha1.HSMPool{}
589589+590590+ if err := m.Get(ctx, types.NamespacedName{
591591+ Name: poolName,
592592+ Namespace: hsmDevice.Namespace,
593593+ }, pool); err != nil {
594594+ // If pool doesn't exist, no devices are available, so agent doesn't need update
595595+ if errors.IsNotFound(err) {
596596+ return false, nil
597597+ }
598598+ return false, fmt.Errorf("failed to get HSMPool %s: %w", poolName, err)
599599+ }
600600+601601+ // Extract current volume mounts from deployment
602602+ if len(deployment.Spec.Template.Spec.Containers) == 0 {
603603+ return false, fmt.Errorf("deployment has no containers")
604604+ }
605605+606606+ container := deployment.Spec.Template.Spec.Containers[0]
607607+ currentDeviceMounts := make(map[string]string) // mount name -> device path
608608+609609+ for _, mount := range container.VolumeMounts {
610610+ if mount.Name == "hsm-device" {
611611+ // Find corresponding volume
612612+ for _, vol := range deployment.Spec.Template.Spec.Volumes {
613613+ if vol.Name == mount.Name && vol.HostPath != nil {
614614+ currentDeviceMounts[mount.Name] = vol.HostPath.Path
615615+ break
616616+ }
617617+ }
618618+ }
619619+ }
620620+621621+ // Check if any device paths in the pool differ from current mounts
622622+ for _, device := range pool.Status.AggregatedDevices {
623623+ if device.DevicePath != "" && device.Available {
624624+ // Check if this device path is already mounted
625625+ found := false
626626+ for _, path := range currentDeviceMounts {
627627+ if path == device.DevicePath {
628628+ found = true
629629+ break
630630+ }
631631+ }
632632+ if !found {
633633+ // New device path found that's not in current deployment
634634+ return true, nil
635635+ }
636636+ }
637637+ }
638638+639639+ // Check for stale device paths (mounted paths that are no longer in aggregated devices)
640640+ for _, currentPath := range currentDeviceMounts {
641641+ found := false
642642+ for _, device := range pool.Status.AggregatedDevices {
643643+ if device.DevicePath == currentPath && device.Available {
644644+ found = true
645645+ break
646646+ }
647647+ }
648648+ if !found {
649649+ // Current mount points to a device path that's no longer available
650650+ return true, nil
651651+ }
652652+ }
653653+654654+ return false, nil
547655}
548656549657// Helper functions