Skip to content

Commit 6bb21f9

Browse files
authored
feat(capsules): Implement AttachCapsuleToDeployment and dependency management (#18)
Completes the implementation of Resource Capsules attachment to Kubernetes This enables versioned resources to be attached to container automatically. Key changes: - Complete AttachCapsuleToDeployment to modify K8s Deployments with volumes - Create test suite for validating both real and mock implementations Part of #17
1 parent 4932952 commit 6bb21f9

File tree

2 files changed

+220
-9
lines changed

2 files changed

+220
-9
lines changed

kubernetes.go

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -190,17 +190,107 @@ func (kcm *KubernetesCapsuleManager) DeleteCapsule(name, version string) error {
190190

191191
// AttachCapsuleToDeployment attaches a Resource Capsule to a Kubernetes Deployment
192192
func (kcm *KubernetesCapsuleManager) AttachCapsuleToDeployment(deploymentName, capsuleName, capsuleVersion string) error {
193-
// This would involve updating a Deployment to mount the ConfigMap/Secret
194-
// For this implementation, we'll simulate the attachment
195-
fmt.Printf("[Kubernetes] Attaching capsule %s:%s to deployment %s\n", capsuleName, capsuleVersion, deploymentName)
196-
197-
// In a real implementation, this would:
198-
// 1. Get the existing Deployment
193+
// 1. Get the existing Deployment
194+
deployment, err := kcm.client.AppsV1().Deployments(kcm.namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{})
195+
if err != nil {
196+
return fmt.Errorf("failed to get deployment %s: %v", deploymentName, err)
197+
}
198+
199+
// does capsule exists as a ConfigMap or Secret
200+
configMapName := fmt.Sprintf("%s-%s", capsuleName, capsuleVersion)
201+
secretName := configMapName
202+
203+
// First, determine if the capsule exists as a ConfigMap or Secret
204+
_, configMapErr := kcm.GetConfigMapCapsule(capsuleName, capsuleVersion)
205+
_, secretErr := kcm.GetSecretCapsule(capsuleName, capsuleVersion)
206+
199207
// 2. Add a volume for the ConfigMap/Secret
208+
var volumeName string
209+
var volumeSource v1.VolumeSource
210+
var mountPath string
211+
212+
if configMapErr == nil {
213+
// It's a ConfigMap capsule
214+
volumeName = fmt.Sprintf("capsule-%s-%s", capsuleName, capsuleVersion)
215+
volumeSource = v1.VolumeSource{
216+
ConfigMap: &v1.ConfigMapVolumeSource{
217+
LocalObjectReference: v1.LocalObjectReference{
218+
Name: configMapName,
219+
},
220+
},
221+
}
222+
mountPath = fmt.Sprintf("/capsules/%s/%s", capsuleName, capsuleVersion)
223+
} else if secretErr == nil {
224+
// It's a Secret capsule
225+
volumeName = fmt.Sprintf("capsule-%s-%s", capsuleName, capsuleVersion)
226+
volumeSource = v1.VolumeSource{
227+
Secret: &v1.SecretVolumeSource{
228+
SecretName: secretName,
229+
},
230+
}
231+
mountPath = fmt.Sprintf("/capsules/%s/%s", capsuleName, capsuleVersion)
232+
} else {
233+
return fmt.Errorf("capsule %s:%s not found", capsuleName, capsuleVersion)
234+
}
235+
236+
volumeExists := false
237+
for _, volume := range deployment.Spec.Template.Spec.Volumes {
238+
if volume.Name == volumeName {
239+
volumeExists = true
240+
break
241+
}
242+
}
243+
244+
// Add the volume if it doesn't exist
245+
if !volumeExists {
246+
deployment.Spec.Template.Spec.Volumes = append(
247+
deployment.Spec.Template.Spec.Volumes,
248+
v1.Volume{
249+
Name: volumeName,
250+
VolumeSource: volumeSource,
251+
},
252+
)
253+
}
254+
200255
// 3. Add a volumeMount to the container spec
201-
// 4. Update the Deployment
202-
203-
return nil
256+
for i := range deployment.Spec.Template.Spec.Containers {
257+
container := &deployment.Spec.Template.Spec.Containers[i]
258+
259+
// check if this container already has the mount
260+
mountExists := false
261+
for _, mount := range container.VolumeMounts {
262+
if mount.Name == volumeName {
263+
mountExists = true
264+
break
265+
}
266+
}
267+
268+
if !mountExists {
269+
container.VolumeMounts = append(
270+
container.VolumeMounts,
271+
v1.VolumeMount{
272+
Name: volumeName,
273+
MountPath: mountPath,
274+
ReadOnly: true,
275+
},
276+
)
277+
}
278+
279+
}
280+
281+
//4. Update the deployment
282+
_, err = kcm.client.AppsV1().Deployments(kcm.namespace).Update(
283+
context.TODO(),
284+
deployment,
285+
metav1.UpdateOptions{},
286+
)
287+
if err != nil {
288+
return fmt.Errorf("failed to update deployment %s: %v", deploymentName, err)
289+
}
290+
291+
fmt.Printf("[Kubernetes] Capsule %s:%s attached to deployment %s at path %s\n",
292+
capsuleName, capsuleVersion, deploymentName, mountPath)
293+
return nil
204294
}
205295

206296
// BenchmarkKubernetesResourceAccess benchmarks access to Kubernetes resources

kubernetes_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"testing"
88

9+
appsv1 "k8s.io/api/apps/v1"
910
v1 "k8s.io/api/core/v1"
1011
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1112
"k8s.io/client-go/kubernetes/fake"
@@ -175,6 +176,126 @@ func TestAddKubernetesResourceCapsule(t *testing.T) {
175176
}
176177
}
177178

179+
func TestAttachCapsuleToDeployment(t *testing.T) {
180+
clientset := fake.NewSimpleClientset()
181+
182+
deployment := &appsv1.Deployment {
183+
ObjectMeta: metav1.ObjectMeta {
184+
Name: "test-deployment",
185+
Namespace: "default",
186+
},
187+
Spec: appsv1.DeploymentSpec {
188+
Selector: &metav1.LabelSelector {
189+
MatchLabels: map[string]string {
190+
"app": "test",
191+
},
192+
},
193+
Template: v1.PodTemplateSpec {
194+
ObjectMeta: metav1.ObjectMeta {
195+
Labels: map[string] string {
196+
"app": "test",
197+
},
198+
},
199+
Spec: v1.PodSpec {
200+
Containers: []v1.Container {
201+
{
202+
Name: "test-container",
203+
Image: "nginx:latest",
204+
},
205+
},
206+
},
207+
},
208+
},
209+
}
210+
211+
// Create the deployment in the fake clientset
212+
_, err := clientset.AppsV1().Deployments("default").Create(
213+
context.TODO(),
214+
deployment,
215+
metav1.CreateOptions{},
216+
)
217+
if err != nil {
218+
t.Fatalf("Failed to create test deployment: %v", err)
219+
}
220+
221+
// Create a test ConfigMap capsule
222+
configMap := &v1.ConfigMap{
223+
ObjectMeta: metav1.ObjectMeta{
224+
Name: "test-capsule-1.0",
225+
Namespace: "default",
226+
Labels: map[string]string{
227+
"capsule.docker.io/name": "test-capsule",
228+
"capsule.docker.io/version": "1.0",
229+
},
230+
},
231+
Data: map[string]string{
232+
"test-data": "test value",
233+
},
234+
}
235+
236+
// Create the ConfigMap in the fake clientset
237+
_, err = clientset.CoreV1().ConfigMaps("default").Create(
238+
context.TODO(),
239+
configMap,
240+
metav1.CreateOptions{},
241+
)
242+
if err != nil {
243+
t.Fatalf("Failed to create test ConfigMap: %v", err)
244+
}
245+
246+
// Create a KubernetesCapsuleManager with the fake clientset
247+
kcm := &KubernetesCapsuleManager{
248+
client: clientset,
249+
namespace: "default",
250+
}
251+
252+
// Attach the capsule to the deployment
253+
err = kcm.AttachCapsuleToDeployment("test-deployment", "test-capsule", "1.0")
254+
if err != nil {
255+
t.Fatalf("Failed to attach capsule to deployment: %v", err)
256+
}
257+
258+
// Get the updated deployment
259+
updatedDeployment, err := clientset.AppsV1().Deployments("default").Get(
260+
context.TODO(),
261+
"test-deployment",
262+
metav1.GetOptions{},
263+
)
264+
if err != nil {
265+
t.Fatalf("Failed to get updated deployment: %v", err)
266+
}
267+
268+
// Check that the volume was added
269+
volumeFound := false
270+
for _, volume := range updatedDeployment.Spec.Template.Spec.Volumes {
271+
if volume.Name == "capsule-test-capsule-1.0" {
272+
volumeFound = true
273+
break
274+
}
275+
}
276+
if !volumeFound {
277+
t.Errorf("Volume for capsule was not added to the deployment")
278+
}
279+
280+
// Check that the volume mount was added to the container
281+
container := &updatedDeployment.Spec.Template.Spec.Containers[0]
282+
mountFound := false
283+
for _, mount := range container.VolumeMounts {
284+
if mount.Name == "capsule-test-capsule-1.0" {
285+
mountFound = true
286+
if mount.MountPath != "/capsules/test-capsule/1.0" {
287+
t.Errorf("Unexpected mount path: got %s, want /capsules/test-capsule/1.0", mount.MountPath)
288+
}
289+
break
290+
}
291+
}
292+
if !mountFound {
293+
t.Errorf("Volume mount for capsule was not added to the container")
294+
}
295+
296+
t.Log("Successfully attached capsule to deployment and verified volume and mount")
297+
}
298+
178299
// BenchmarkKubernetesConfigMapAccess benchmarks ConfigMap access performance
179300
func BenchmarkKubernetesConfigMapAccess(b *testing.B) {
180301
mockKCM := NewMockKubernetesCapsuleManager()

0 commit comments

Comments
 (0)