Skip to content

Commit 78d7e1a

Browse files
add image pull policy to mcpserver (#107)
add image pull policy to mcpserver --------- Signed-off-by: kevin-shelaga <[email protected]>
1 parent 089fda1 commit 78d7e1a

File tree

4 files changed

+217
-2
lines changed

4 files changed

+217
-2
lines changed

api/v1alpha1/mcpserver_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ type MCPServerDeployment struct {
195195
// +optional
196196
Image string `json:"image,omitempty"`
197197

198+
// ImagePullPolicy defines the pull policy for the container image.
199+
// +optional
200+
// +kubebuilder:validation:Enum=Always;Never;IfNotPresent
201+
ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"`
202+
198203
// Port defines the port on which the MCP server will listen.
199204
// +optional
200205
// +kubebuilder:default=3000

config/crd/bases/kagent.dev_mcpservers.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ spec:
9292
description: Image defines the container image to to deploy the
9393
MCP server.
9494
type: string
95+
imagePullPolicy:
96+
description: ImagePullPolicy defines the pull policy for the container
97+
image.
98+
enum:
99+
- Always
100+
- Never
101+
- IfNotPresent
102+
type: string
95103
initContainer:
96104
description: |-
97105
InitContainer defines the configuration for the init container that copies

pkg/controller/mcpserver_controller_test.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,202 @@ var _ = ginkgo.Describe("MCPServer Controller", func() {
262262
gomega.Expect(err).NotTo(gomega.HaveOccurred())
263263
})
264264
})
265+
266+
ginkgo.Context("ImagePullPolicy", func() {
267+
ctx := context.Background()
268+
269+
ginkgo.It("should set ImagePullPolicy to Always when specified for stdio transport", func() {
270+
ginkgo.By("Creating MCPServer with ImagePullPolicy Always for stdio transport")
271+
serverName := "test-stdio-imagepullpolicy"
272+
server := &kagentdevv1alpha1.MCPServer{
273+
ObjectMeta: metav1.ObjectMeta{
274+
Name: serverName,
275+
Namespace: "default",
276+
},
277+
Spec: kagentdevv1alpha1.MCPServerSpec{
278+
TransportType: kagentdevv1alpha1.TransportTypeStdio,
279+
Deployment: kagentdevv1alpha1.MCPServerDeployment{
280+
Image: "test-image:latest",
281+
Port: 3000,
282+
ImagePullPolicy: corev1.PullAlways,
283+
},
284+
},
285+
}
286+
287+
err := k8sClient.Create(ctx, server)
288+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
289+
290+
ginkgo.By("Reconciling the MCPServer")
291+
controllerReconciler := setupController()
292+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
293+
NamespacedName: types.NamespacedName{
294+
Name: serverName,
295+
Namespace: "default",
296+
},
297+
})
298+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
299+
300+
ginkgo.By("Verifying deployment has ImagePullPolicy set to Always")
301+
deployment := &appsv1.Deployment{}
302+
err = k8sClient.Get(ctx, types.NamespacedName{
303+
Name: serverName,
304+
Namespace: "default",
305+
}, deployment)
306+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
307+
308+
container := deployment.Spec.Template.Spec.Containers[0]
309+
gomega.Expect(container.ImagePullPolicy).To(gomega.Equal(corev1.PullAlways))
310+
311+
// Cleanup
312+
err = k8sClient.Delete(ctx, server)
313+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
314+
})
315+
316+
ginkgo.It("should set ImagePullPolicy to Always when specified for HTTP transport", func() {
317+
ginkgo.By("Creating MCPServer with ImagePullPolicy Always for HTTP transport")
318+
serverName := "test-http-imagepullpolicy"
319+
server := &kagentdevv1alpha1.MCPServer{
320+
ObjectMeta: metav1.ObjectMeta{
321+
Name: serverName,
322+
Namespace: "default",
323+
},
324+
Spec: kagentdevv1alpha1.MCPServerSpec{
325+
TransportType: kagentdevv1alpha1.TransportTypeHTTP,
326+
Deployment: kagentdevv1alpha1.MCPServerDeployment{
327+
Image: "test-image:latest",
328+
Port: 3000,
329+
ImagePullPolicy: corev1.PullAlways,
330+
},
331+
HTTPTransport: &kagentdevv1alpha1.HTTPTransport{
332+
TargetPort: 8080,
333+
},
334+
},
335+
}
336+
337+
err := k8sClient.Create(ctx, server)
338+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
339+
340+
ginkgo.By("Reconciling the MCPServer")
341+
controllerReconciler := setupController()
342+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
343+
NamespacedName: types.NamespacedName{
344+
Name: serverName,
345+
Namespace: "default",
346+
},
347+
})
348+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
349+
350+
ginkgo.By("Verifying deployment has ImagePullPolicy set to Always")
351+
deployment := &appsv1.Deployment{}
352+
err = k8sClient.Get(ctx, types.NamespacedName{
353+
Name: serverName,
354+
Namespace: "default",
355+
}, deployment)
356+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
357+
358+
container := deployment.Spec.Template.Spec.Containers[0]
359+
gomega.Expect(container.ImagePullPolicy).To(gomega.Equal(corev1.PullAlways))
360+
361+
// Cleanup
362+
err = k8sClient.Delete(ctx, server)
363+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
364+
})
365+
366+
ginkgo.It("should default ImagePullPolicy to IfNotPresent when not specified for stdio transport", func() {
367+
ginkgo.By("Creating MCPServer without ImagePullPolicy for stdio transport")
368+
serverName := "test-stdio-default-imagepullpolicy"
369+
server := &kagentdevv1alpha1.MCPServer{
370+
ObjectMeta: metav1.ObjectMeta{
371+
Name: serverName,
372+
Namespace: "default",
373+
},
374+
Spec: kagentdevv1alpha1.MCPServerSpec{
375+
TransportType: kagentdevv1alpha1.TransportTypeStdio,
376+
Deployment: kagentdevv1alpha1.MCPServerDeployment{
377+
Image: "test-image:latest",
378+
Port: 3000,
379+
},
380+
},
381+
}
382+
383+
err := k8sClient.Create(ctx, server)
384+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
385+
386+
ginkgo.By("Reconciling the MCPServer")
387+
controllerReconciler := setupController()
388+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
389+
NamespacedName: types.NamespacedName{
390+
Name: serverName,
391+
Namespace: "default",
392+
},
393+
})
394+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
395+
396+
ginkgo.By("Verifying deployment has ImagePullPolicy defaulted to IfNotPresent")
397+
deployment := &appsv1.Deployment{}
398+
err = k8sClient.Get(ctx, types.NamespacedName{
399+
Name: serverName,
400+
Namespace: "default",
401+
}, deployment)
402+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
403+
404+
container := deployment.Spec.Template.Spec.Containers[0]
405+
gomega.Expect(container.ImagePullPolicy).To(gomega.Equal(corev1.PullIfNotPresent))
406+
407+
// Cleanup
408+
err = k8sClient.Delete(ctx, server)
409+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
410+
})
411+
412+
ginkgo.It("should default ImagePullPolicy to IfNotPresent when not specified for HTTP transport", func() {
413+
ginkgo.By("Creating MCPServer without ImagePullPolicy for HTTP transport")
414+
serverName := "test-http-default-imagepullpolicy"
415+
server := &kagentdevv1alpha1.MCPServer{
416+
ObjectMeta: metav1.ObjectMeta{
417+
Name: serverName,
418+
Namespace: "default",
419+
},
420+
Spec: kagentdevv1alpha1.MCPServerSpec{
421+
TransportType: kagentdevv1alpha1.TransportTypeHTTP,
422+
Deployment: kagentdevv1alpha1.MCPServerDeployment{
423+
Image: "test-image:latest",
424+
Port: 3000,
425+
},
426+
HTTPTransport: &kagentdevv1alpha1.HTTPTransport{
427+
TargetPort: 8080,
428+
},
429+
},
430+
}
431+
432+
err := k8sClient.Create(ctx, server)
433+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
434+
435+
ginkgo.By("Reconciling the MCPServer")
436+
controllerReconciler := setupController()
437+
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
438+
NamespacedName: types.NamespacedName{
439+
Name: serverName,
440+
Namespace: "default",
441+
},
442+
})
443+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
444+
445+
ginkgo.By("Verifying deployment has ImagePullPolicy defaulted to IfNotPresent")
446+
deployment := &appsv1.Deployment{}
447+
err = k8sClient.Get(ctx, types.NamespacedName{
448+
Name: serverName,
449+
Namespace: "default",
450+
}, deployment)
451+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
452+
453+
container := deployment.Spec.Template.Spec.Containers[0]
454+
gomega.Expect(container.ImagePullPolicy).To(gomega.Equal(corev1.PullIfNotPresent))
455+
456+
// Cleanup
457+
err = k8sClient.Delete(ctx, server)
458+
gomega.Expect(err).NotTo(gomega.HaveOccurred())
459+
})
460+
})
265461
})
266462

267463
// Helper functions to reduce code duplication

pkg/controller/transportadapter/transportadapter_translator.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ func (t *transportAdapterTranslator) translateTransportAdapterDeployment(
126126
}
127127
}
128128

129+
// Determine the main container pull policy to use
130+
mainContainerPullPolicy := server.Spec.Deployment.ImagePullPolicy
131+
if mainContainerPullPolicy == "" {
132+
mainContainerPullPolicy = corev1.PullIfNotPresent
133+
}
134+
129135
var template corev1.PodSpec
130136
switch server.Spec.TransportType {
131137
case v1alpha1.TransportTypeStdio:
@@ -149,7 +155,7 @@ func (t *transportAdapterTranslator) translateTransportAdapterDeployment(
149155
Containers: []corev1.Container{{
150156
Name: "mcp-server",
151157
Image: image,
152-
ImagePullPolicy: corev1.PullIfNotPresent,
158+
ImagePullPolicy: mainContainerPullPolicy,
153159
Command: []string{
154160
"/adapterbin/agentgateway",
155161
},
@@ -200,7 +206,7 @@ func (t *transportAdapterTranslator) translateTransportAdapterDeployment(
200206
{
201207
Name: "mcp-server",
202208
Image: image,
203-
ImagePullPolicy: corev1.PullIfNotPresent,
209+
ImagePullPolicy: mainContainerPullPolicy,
204210
Command: cmd,
205211
Args: server.Spec.Deployment.Args,
206212
Env: convertEnvVars(server.Spec.Deployment.Env),

0 commit comments

Comments
 (0)