diff --git a/kubernetes.go b/kubernetes.go index be14faf..2f63fd2 100644 --- a/kubernetes.go +++ b/kubernetes.go @@ -190,17 +190,107 @@ func (kcm *KubernetesCapsuleManager) DeleteCapsule(name, version string) error { // AttachCapsuleToDeployment attaches a Resource Capsule to a Kubernetes Deployment func (kcm *KubernetesCapsuleManager) AttachCapsuleToDeployment(deploymentName, capsuleName, capsuleVersion string) error { - // This would involve updating a Deployment to mount the ConfigMap/Secret - // For this implementation, we'll simulate the attachment - fmt.Printf("[Kubernetes] Attaching capsule %s:%s to deployment %s\n", capsuleName, capsuleVersion, deploymentName) - - // In a real implementation, this would: - // 1. Get the existing Deployment + // 1. Get the existing Deployment + deployment, err := kcm.client.AppsV1().Deployments(kcm.namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get deployment %s: %v", deploymentName, err) + } + + // does capsule exists as a ConfigMap or Secret + configMapName := fmt.Sprintf("%s-%s", capsuleName, capsuleVersion) + secretName := configMapName + + // First, determine if the capsule exists as a ConfigMap or Secret + _, configMapErr := kcm.GetConfigMapCapsule(capsuleName, capsuleVersion) + _, secretErr := kcm.GetSecretCapsule(capsuleName, capsuleVersion) + // 2. Add a volume for the ConfigMap/Secret + var volumeName string + var volumeSource v1.VolumeSource + var mountPath string + + if configMapErr == nil { + // It's a ConfigMap capsule + volumeName = fmt.Sprintf("capsule-%s-%s", capsuleName, capsuleVersion) + volumeSource = v1.VolumeSource{ + ConfigMap: &v1.ConfigMapVolumeSource{ + LocalObjectReference: v1.LocalObjectReference{ + Name: configMapName, + }, + }, + } + mountPath = fmt.Sprintf("/capsules/%s/%s", capsuleName, capsuleVersion) + } else if secretErr == nil { + // It's a Secret capsule + volumeName = fmt.Sprintf("capsule-%s-%s", capsuleName, capsuleVersion) + volumeSource = v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: secretName, + }, + } + mountPath = fmt.Sprintf("/capsules/%s/%s", capsuleName, capsuleVersion) + } else { + return fmt.Errorf("capsule %s:%s not found", capsuleName, capsuleVersion) + } + + volumeExists := false + for _, volume := range deployment.Spec.Template.Spec.Volumes { + if volume.Name == volumeName { + volumeExists = true + break + } + } + + // Add the volume if it doesn't exist + if !volumeExists { + deployment.Spec.Template.Spec.Volumes = append( + deployment.Spec.Template.Spec.Volumes, + v1.Volume{ + Name: volumeName, + VolumeSource: volumeSource, + }, + ) + } + // 3. Add a volumeMount to the container spec - // 4. Update the Deployment - - return nil + for i := range deployment.Spec.Template.Spec.Containers { + container := &deployment.Spec.Template.Spec.Containers[i] + + // check if this container already has the mount + mountExists := false + for _, mount := range container.VolumeMounts { + if mount.Name == volumeName { + mountExists = true + break + } + } + + if !mountExists { + container.VolumeMounts = append( + container.VolumeMounts, + v1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + ReadOnly: true, + }, + ) + } + + } + + //4. Update the deployment + _, err = kcm.client.AppsV1().Deployments(kcm.namespace).Update( + context.TODO(), + deployment, + metav1.UpdateOptions{}, + ) + if err != nil { + return fmt.Errorf("failed to update deployment %s: %v", deploymentName, err) + } + + fmt.Printf("[Kubernetes] Capsule %s:%s attached to deployment %s at path %s\n", + capsuleName, capsuleVersion, deploymentName, mountPath) + return nil } // BenchmarkKubernetesResourceAccess benchmarks access to Kubernetes resources diff --git a/kubernetes_test.go b/kubernetes_test.go index 7eb13cb..c34ed40 100644 --- a/kubernetes_test.go +++ b/kubernetes_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -175,6 +176,126 @@ func TestAddKubernetesResourceCapsule(t *testing.T) { } } +func TestAttachCapsuleToDeployment(t *testing.T) { + clientset := fake.NewSimpleClientset() + + deployment := &appsv1.Deployment { + ObjectMeta: metav1.ObjectMeta { + Name: "test-deployment", + Namespace: "default", + }, + Spec: appsv1.DeploymentSpec { + Selector: &metav1.LabelSelector { + MatchLabels: map[string]string { + "app": "test", + }, + }, + Template: v1.PodTemplateSpec { + ObjectMeta: metav1.ObjectMeta { + Labels: map[string] string { + "app": "test", + }, + }, + Spec: v1.PodSpec { + Containers: []v1.Container { + { + Name: "test-container", + Image: "nginx:latest", + }, + }, + }, + }, + }, + } + + // Create the deployment in the fake clientset + _, err := clientset.AppsV1().Deployments("default").Create( + context.TODO(), + deployment, + metav1.CreateOptions{}, + ) + if err != nil { + t.Fatalf("Failed to create test deployment: %v", err) + } + + // Create a test ConfigMap capsule + configMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-capsule-1.0", + Namespace: "default", + Labels: map[string]string{ + "capsule.docker.io/name": "test-capsule", + "capsule.docker.io/version": "1.0", + }, + }, + Data: map[string]string{ + "test-data": "test value", + }, + } + + // Create the ConfigMap in the fake clientset + _, err = clientset.CoreV1().ConfigMaps("default").Create( + context.TODO(), + configMap, + metav1.CreateOptions{}, + ) + if err != nil { + t.Fatalf("Failed to create test ConfigMap: %v", err) + } + + // Create a KubernetesCapsuleManager with the fake clientset + kcm := &KubernetesCapsuleManager{ + client: clientset, + namespace: "default", + } + + // Attach the capsule to the deployment + err = kcm.AttachCapsuleToDeployment("test-deployment", "test-capsule", "1.0") + if err != nil { + t.Fatalf("Failed to attach capsule to deployment: %v", err) + } + + // Get the updated deployment + updatedDeployment, err := clientset.AppsV1().Deployments("default").Get( + context.TODO(), + "test-deployment", + metav1.GetOptions{}, + ) + if err != nil { + t.Fatalf("Failed to get updated deployment: %v", err) + } + + // Check that the volume was added + volumeFound := false + for _, volume := range updatedDeployment.Spec.Template.Spec.Volumes { + if volume.Name == "capsule-test-capsule-1.0" { + volumeFound = true + break + } + } + if !volumeFound { + t.Errorf("Volume for capsule was not added to the deployment") + } + + // Check that the volume mount was added to the container + container := &updatedDeployment.Spec.Template.Spec.Containers[0] + mountFound := false + for _, mount := range container.VolumeMounts { + if mount.Name == "capsule-test-capsule-1.0" { + mountFound = true + if mount.MountPath != "/capsules/test-capsule/1.0" { + t.Errorf("Unexpected mount path: got %s, want /capsules/test-capsule/1.0", mount.MountPath) + } + break + } + } + if !mountFound { + t.Errorf("Volume mount for capsule was not added to the container") + } + + t.Log("Successfully attached capsule to deployment and verified volume and mount") +} + // BenchmarkKubernetesConfigMapAccess benchmarks ConfigMap access performance func BenchmarkKubernetesConfigMapAccess(b *testing.B) { mockKCM := NewMockKubernetesCapsuleManager()