mirror of
https://github.com/openshift/openshift-mcp-server.git
synced 2025-10-17 14:27:48 +03:00
feat(helm): support for helm uninstall
This commit is contained in:
@@ -2,6 +2,7 @@ package helm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"helm.sh/helm/v3/pkg/action"
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
"helm.sh/helm/v3/pkg/cli"
|
||||
@@ -28,7 +29,7 @@ func NewHelm(kubernetes Kubernetes) *Helm {
|
||||
}
|
||||
|
||||
func (h *Helm) Install(ctx context.Context, chart string, values map[string]interface{}, name string, namespace string) (string, error) {
|
||||
cfg, err := h.newAction(namespace, false)
|
||||
cfg, err := h.newAction(h.kubernetes.NamespaceOrDefault(namespace), false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -82,6 +83,24 @@ func (h *Helm) List(namespace string, allNamespaces bool) (string, error) {
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
func (h *Helm) Uninstall(name string, namespace string) (string, error) {
|
||||
cfg, err := h.newAction(h.kubernetes.NamespaceOrDefault(namespace), false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uninstall := action.NewUninstall(cfg)
|
||||
uninstall.IgnoreNotFound = true
|
||||
uninstall.Wait = true
|
||||
uninstall.Timeout = 5 * time.Minute
|
||||
uninstalledRelease, err := uninstall.Run(name)
|
||||
if uninstalledRelease == nil && err == nil {
|
||||
return fmt.Sprintf("Release %s not found", name), nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Uninstalled release %s %s", uninstalledRelease.Release.Name, uninstalledRelease.Info), nil
|
||||
}
|
||||
|
||||
func (h *Helm) newAction(namespace string, allNamespaces bool) (*action.Configuration, error) {
|
||||
cfg := new(action.Configuration)
|
||||
applicableNamespace := ""
|
||||
|
||||
@@ -21,6 +21,11 @@ func (s *Server) initHelm() []server.ServerTool {
|
||||
mcp.WithString("namespace", mcp.Description("Namespace to list Helm releases from (Optional, all namespaces if not provided)")),
|
||||
mcp.WithBoolean("all_namespaces", mcp.Description("If true, lists all Helm releases in all namespaces ignoring the namespace argument (Optional)")),
|
||||
), s.helmList},
|
||||
{mcp.NewTool("helm_uninstall",
|
||||
mcp.WithDescription("Uninstall a Helm release in the current or provided namespace"),
|
||||
mcp.WithString("name", mcp.Description("Name of the Helm release to uninstall"), mcp.Required()),
|
||||
mcp.WithString("namespace", mcp.Description("Namespace to uninstall the Helm release from (Optional, current namespace if not provided)")),
|
||||
), s.helmUninstall},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,3 +69,20 @@ func (s *Server) helmList(_ context.Context, ctr mcp.CallToolRequest) (*mcp.Call
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
}
|
||||
|
||||
func (s *Server) helmUninstall(_ context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var name string
|
||||
ok := false
|
||||
if name, ok = ctr.Params.Arguments["name"].(string); !ok {
|
||||
return NewTextResult("", fmt.Errorf("failed to uninstall helm chart, missing argument name")), nil
|
||||
}
|
||||
namespace := ""
|
||||
if v, ok := ctr.Params.Arguments["namespace"].(string); ok {
|
||||
namespace = v
|
||||
}
|
||||
ret, err := s.k.Helm.Uninstall(name, namespace)
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to uninstall helm chart '%s': %w", name, err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sigs.k8s.io/yaml"
|
||||
@@ -58,13 +61,7 @@ func TestHelmList(t *testing.T) {
|
||||
testCase(t, func(c *mcpContext) {
|
||||
c.withEnvTest()
|
||||
kc := c.newKubernetesClient()
|
||||
secrets, err := kc.CoreV1().Secrets("default").List(c.ctx, metav1.ListOptions{})
|
||||
for _, secret := range secrets.Items {
|
||||
if strings.HasPrefix(secret.Name, "sh.helm.release.v1.") {
|
||||
_ = kc.CoreV1().Secrets("default").Delete(c.ctx, secret.Name, metav1.DeleteOptions{})
|
||||
}
|
||||
}
|
||||
_ = kc.CoreV1().Secrets("default").Delete(c.ctx, "release-to-list", metav1.DeleteOptions{})
|
||||
clearHelmReleases(c.ctx, kc)
|
||||
toolResult, err := c.callTool("helm_list", map[string]interface{}{})
|
||||
t.Run("helm_list with no releases, returns not found", func(t *testing.T) {
|
||||
if err != nil {
|
||||
@@ -79,8 +76,8 @@ func TestHelmList(t *testing.T) {
|
||||
})
|
||||
_, _ = kc.CoreV1().Secrets("default").Create(c.ctx, &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "release-to-list",
|
||||
Labels: map[string]string{"owner": "helm"},
|
||||
Name: "sh.helm.release.v1.release-to-list",
|
||||
Labels: map[string]string{"owner": "helm", "name": "release-to-list"},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"release": []byte(base64.StdEncoding.EncodeToString([]byte("{" +
|
||||
@@ -149,3 +146,64 @@ func TestHelmList(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestHelmUninstall(t *testing.T) {
|
||||
testCase(t, func(c *mcpContext) {
|
||||
c.withEnvTest()
|
||||
kc := c.newKubernetesClient()
|
||||
clearHelmReleases(c.ctx, kc)
|
||||
toolResult, err := c.callTool("helm_uninstall", map[string]interface{}{
|
||||
"name": "release-to-uninstall",
|
||||
})
|
||||
t.Run("helm_uninstall with no releases, returns not found", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
}
|
||||
if toolResult.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
}
|
||||
if toolResult.Content[0].(mcp.TextContent).Text != "Release release-to-uninstall not found" {
|
||||
t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
|
||||
}
|
||||
})
|
||||
_, _ = kc.CoreV1().Secrets("default").Create(c.ctx, &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "sh.helm.release.v1.existent-release-to-uninstall.v0",
|
||||
Labels: map[string]string{"owner": "helm", "name": "existent-release-to-uninstall"},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"release": []byte(base64.StdEncoding.EncodeToString([]byte("{" +
|
||||
"\"name\":\"existent-release-to-uninstall\"," +
|
||||
"\"info\":{\"status\":\"deployed\"}" +
|
||||
"}"))),
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
toolResult, err = c.callTool("helm_uninstall", map[string]interface{}{
|
||||
"name": "existent-release-to-uninstall",
|
||||
})
|
||||
t.Run("helm_uninstall with deployed release, returns uninstalled", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
}
|
||||
if toolResult.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
}
|
||||
if !strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "Uninstalled release existent-release-to-uninstall") {
|
||||
t.Fatalf("unexpected result %v", toolResult.Content[0].(mcp.TextContent).Text)
|
||||
}
|
||||
_, err = kc.CoreV1().Secrets("default").Get(c.ctx, "sh.helm.release.v1.existent-release-to-uninstall.v0", metav1.GetOptions{})
|
||||
if !errors.IsNotFound(err) {
|
||||
t.Fatalf("expected release to be deleted, but it still exists")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func clearHelmReleases(ctx context.Context, kc *kubernetes.Clientset) {
|
||||
secrets, _ := kc.CoreV1().Secrets("default").List(ctx, metav1.ListOptions{})
|
||||
for _, secret := range secrets.Items {
|
||||
if strings.HasPrefix(secret.Name, "sh.helm.release.v1.") {
|
||||
_ = kc.CoreV1().Secrets("default").Delete(ctx, secret.Name, metav1.DeleteOptions{})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ func TestTools(t *testing.T) {
|
||||
"events_list",
|
||||
"helm_install",
|
||||
"helm_list",
|
||||
"helm_uninstall",
|
||||
"namespaces_list",
|
||||
"pods_list",
|
||||
"pods_list_in_namespace",
|
||||
|
||||
Reference in New Issue
Block a user