Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/inspektor-gadget-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Can you observe system calls for the pod my-pod in the default namespace for few

## Prerequisites

- A kubeconfig file that has access to the AKS cluster. You will need to restart the MCP server if you change the kubeconfig file.
- A kubeconfig file that has access to the AKS cluster.
- The tool requires Inspektor Gadget to be installed in the cluster. If you are running with `--access-level=readwrite` or more, the MCP server will automatically
install Inspektor Gadget (action `deploy` ) in the cluster otherwise you can follow the steps to install it manually: [Inspektor Gadget Installation](https://learn.microsoft.com/en-us/troubleshoot/azure/azure-kubernetes/logs/capture-system-insights-from-aks#how-to-install-inspektor-gadget-in-an-aks-cluster) or
use the official Helm chart: [Inspektor Gadget Helm Chart](https://inspektor-gadget.io/docs/latest/reference/install-kubernetes#installation-with-the-helm-chart):
Expand Down
2 changes: 2 additions & 0 deletions internal/components/inspektorgadget/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const (
deployAction = "deploy"
// undeployAction is the action to remove Inspektor Gadget from the cluster
undeployAction = "undeploy"
// upgradeAction is the action to upgrade Inspektor Gadget in the cluster
upgradeAction = "upgrade"
// isDeployedAction is the action to check if Inspektor Gadget is deployed
isDeployedAction = "is_deployed"
)
Expand Down
104 changes: 69 additions & 35 deletions internal/components/inspektorgadget/gadgetmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"slices"
"strings"
"sync"
"time"

"github.com/inspektor-gadget/inspektor-gadget/pkg/datasource"
Expand All @@ -15,7 +16,6 @@ import (
gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context"
"github.com/inspektor-gadget/inspektor-gadget/pkg/operators"
"github.com/inspektor-gadget/inspektor-gadget/pkg/operators/simple"
"github.com/inspektor-gadget/inspektor-gadget/pkg/runtime"
grpcruntime "github.com/inspektor-gadget/inspektor-gadget/pkg/runtime/grpc"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
Expand All @@ -40,8 +40,8 @@ type GadgetManager interface {
ListGadgets(ctx context.Context) ([]*GadgetInstance, error)
// IsDeployed checks if the Inspektor Gadget is deployed in the environment
IsDeployed(ctx context.Context) (bool, string, error)
// Close closes the gadget manager and releases any resources
Close() error
// GetVersion retrieves the version of Inspektor Gadget installed in the cluster
GetVersion() (string, error)
}

// GadgetInstance represents a running gadget instance
Expand All @@ -60,25 +60,12 @@ func init() {
}

// NewGadgetManager creates a new instance of GadgetManager
func NewGadgetManager() (GadgetManager, error) {
rt := grpcruntime.New(grpcruntime.WithConnectUsingK8SProxy)
if err := rt.Init(nil); err != nil {
return nil, fmt.Errorf("initializing gadget runtime: %w", err)
}

restConfig, err := KubernetesFlags.ToRESTConfig()
if err != nil {
return nil, fmt.Errorf("creating REST config: %w", err)
}
rt.SetRestConfig(restConfig)

return &manager{
runtime: rt,
}, nil
func NewGadgetManager() GadgetManager {
return &manager{}
}

type manager struct {
runtime runtime.Runtime
formatterMu sync.Mutex
}

// RunGadget runs a gadget with the specified image and parameters for a given duration
Expand All @@ -88,15 +75,20 @@ func (g *manager) RunGadget(ctx context.Context, image string, params map[string
ctx,
image,
gadgetcontext.WithDataOperators(
outputDataOperator(func(data []byte) {
g.outputDataOperator(func(data []byte) {
results.Write(data)
results.WriteByte('\n')
}),
),
gadgetcontext.WithTimeout(duration),
)

if err := g.runtime.RunGadget(gadgetCtx, g.runtime.ParamDescs().ToParams(), params); err != nil {
rt, err := getRuntime()
if err != nil {
return "", fmt.Errorf("getting runtime: %w", err)
}

if err := rt.RunGadget(gadgetCtx, rt.ParamDescs().ToParams(), params); err != nil {
return "", fmt.Errorf("running gadget: %w", err)
}

Expand All @@ -118,7 +110,7 @@ func truncateResults(results string, latest bool) string {
return fmt.Sprintf("\n<isTruncated>true</isTruncated>\n<results>%s</results>\n", truncated)
}

func outputDataOperator(cb func(data []byte)) operators.DataOperator {
func (g *manager) outputDataOperator(cb func(data []byte)) operators.DataOperator {
const opPriority = 50000
return simple.New("outputDataOperator",
simple.OnInit(func(gadgetCtx operators.GadgetContext) error {
Expand All @@ -135,6 +127,8 @@ func outputDataOperator(cb func(data []byte)) operators.DataOperator {
}

err := d.Subscribe(func(source datasource.DataSource, data datasource.Data) error {
g.formatterMu.Lock()
defer g.formatterMu.Unlock()
jsonData := jsonFormatter.Marshal(data)
cb(jsonData)
return nil
Expand All @@ -155,10 +149,14 @@ func (g *manager) StartGadget(ctx context.Context, image string, params map[stri
image,
)

p := g.runtime.ParamDescs().ToParams()
rt, err := getRuntime()
if err != nil {
return "", fmt.Errorf("getting runtime: %w", err)
}
p := rt.ParamDescs().ToParams()

newID := make([]byte, 16)
_, err := rand.Read(newID)
_, err = rand.Read(newID)
if err != nil {
return "", fmt.Errorf("generating new gadget ID: %w", err)
}
Expand All @@ -175,7 +173,7 @@ func (g *manager) StartGadget(ctx context.Context, image string, params map[stri
if err = p.Set(grpcruntime.ParamTags, strings.Join(append(tags, "createdBy=aks-mcp"), ",")); err != nil {
return "", fmt.Errorf("setting gadget tags: %w", err)
}
if err := g.runtime.RunGadget(gadgetCtx, p, params); err != nil {
if err = rt.RunGadget(gadgetCtx, p, params); err != nil {
return "", fmt.Errorf("running gadget: %w", err)
}

Expand All @@ -184,8 +182,13 @@ func (g *manager) StartGadget(ctx context.Context, image string, params map[stri

// StopGadget stops a running gadget by its ID
func (g *manager) StopGadget(ctx context.Context, id string) error {
if err := g.runtime.(*grpcruntime.Runtime).RemoveGadgetInstance(ctx, g.runtime.ParamDescs().ToParams(), id); err != nil {
return fmt.Errorf("stopping to gadget: %w", err)
rt, err := getRuntime()
if err != nil {
return fmt.Errorf("getting runtime: %w", err)
}

if err = rt.RemoveGadgetInstance(ctx, rt.ParamDescs().ToParams(), id); err != nil {
return fmt.Errorf("stopping gadget: %w", err)
}
return nil
}
Expand All @@ -200,7 +203,7 @@ func (g *manager) GetResults(ctx context.Context, id string) (string, error) {
to,
id,
gadgetcontext.WithDataOperators(
outputDataOperator(func(data []byte) {
g.outputDataOperator(func(data []byte) {
results.Write(data)
results.WriteByte('\n')
}),
Expand All @@ -210,7 +213,12 @@ func (g *manager) GetResults(ctx context.Context, id string) (string, error) {
gadgetcontext.WithTimeout(time.Second),
)

if err := g.runtime.RunGadget(gadgetCtx, g.runtime.ParamDescs().ToParams(), map[string]string{}); err != nil {
rt, err := getRuntime()
if err != nil {
return "", fmt.Errorf("getting runtime: %w", err)
}

if err = rt.RunGadget(gadgetCtx, rt.ParamDescs().ToParams(), map[string]string{}); err != nil {
return "", fmt.Errorf("attaching to gadget: %w", err)
}

Expand All @@ -219,7 +227,12 @@ func (g *manager) GetResults(ctx context.Context, id string) (string, error) {

// ListGadgets lists all running gadgets and returns their instances
func (g *manager) ListGadgets(ctx context.Context) ([]*GadgetInstance, error) {
instances, err := g.runtime.(*grpcruntime.Runtime).GetGadgetInstances(ctx, g.runtime.ParamDescs().ToParams())
rt, err := getRuntime()
if err != nil {
return nil, fmt.Errorf("getting runtime: %w", err)
}

instances, err := rt.GetGadgetInstances(ctx, rt.ParamDescs().ToParams())
if err != nil {
return nil, fmt.Errorf("listing gadgets: %w", err)
}
Expand Down Expand Up @@ -267,10 +280,31 @@ func (g *manager) IsDeployed(ctx context.Context) (bool, string, error) {
return true, namespaces[0], nil
}

// Close closes the gadget manager and releases any resources
func (g *manager) Close() error {
if g.runtime != nil {
return g.runtime.Close()
func (g *manager) GetVersion() (string, error) {
rt, err := getRuntime()
if err != nil {
return "", fmt.Errorf("getting runtime: %w", err)
}
return nil

info, err := rt.GetInfo()
if err != nil {
return "", fmt.Errorf("getting info: %w", err)
}
return info.ServerVersion, nil
}

// getRuntime sets up a runtime, ensuring we always use the latest kubeconfig
func getRuntime() (*grpcruntime.Runtime, error) {
rt := grpcruntime.New(grpcruntime.WithConnectUsingK8SProxy)
if err := rt.Init(nil); err != nil {
return nil, fmt.Errorf("initializing gadget runtime: %w", err)
}

restConfig, err := KubernetesFlags.ToRESTConfig()
if err != nil {
return nil, fmt.Errorf("creating REST config: %w", err)
}
rt.SetRestConfig(restConfig)

return rt, nil
}
20 changes: 12 additions & 8 deletions internal/components/inspektorgadget/gadgets.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ type Gadget struct {
ParamsFunc func(filterParams map[string]interface{}, gadgetParams map[string]string)
}

func (g *Gadget) getImage(version string) string {
return fmt.Sprintf("%s:%s", g.Image, gadgetVersionFor(version))
}

var gadgets = []Gadget{
{
Name: observeDNS,
Image: "ghcr.io/inspektor-gadget/gadget/trace_dns:latest",
Image: "ghcr.io/inspektor-gadget/gadget/trace_dns",
Description: "Observes DNS queries in the cluster",
Params: map[string]interface{}{
"name": map[string]interface{}{
Expand Down Expand Up @@ -75,7 +79,7 @@ var gadgets = []Gadget{
},
{
Name: observeTCP,
Image: "ghcr.io/inspektor-gadget/gadget/trace_tcp:latest",
Image: "ghcr.io/inspektor-gadget/gadget/trace_tcp",
Description: "Observes TCP traffic in the cluster",
Params: map[string]interface{}{
"source_port": map[string]interface{}{
Expand Down Expand Up @@ -121,7 +125,7 @@ var gadgets = []Gadget{
},
{
Name: observeFileOpen,
Image: "ghcr.io/inspektor-gadget/gadget/trace_open:latest",
Image: "ghcr.io/inspektor-gadget/gadget/trace_open",
Description: "Observes file open operations in the cluster",
Params: map[string]interface{}{
"path": map[string]interface{}{
Expand Down Expand Up @@ -152,7 +156,7 @@ var gadgets = []Gadget{
},
{
Name: observeProcessExecution,
Image: "ghcr.io/inspektor-gadget/gadget/trace_exec:latest",
Image: "ghcr.io/inspektor-gadget/gadget/trace_exec",
Description: "Observes process execution in the cluster",
Params: map[string]interface{}{
"command": map[string]interface{}{
Expand All @@ -172,7 +176,7 @@ var gadgets = []Gadget{
},
{
Name: observeSignal,
Image: "ghcr.io/inspektor-gadget/gadget/trace_signal:latest",
Image: "ghcr.io/inspektor-gadget/gadget/trace_signal",
Description: "Traces signals sent to containers in the cluster",
Params: map[string]interface{}{
"signal": map[string]interface{}{
Expand All @@ -193,7 +197,7 @@ var gadgets = []Gadget{
},
{
Name: observeSystemCalls,
Image: "ghcr.io/inspektor-gadget/gadget/traceloop:latest",
Image: "ghcr.io/inspektor-gadget/gadget/traceloop",
Description: "Observes system calls in the cluster",
Params: map[string]interface{}{
"syscall": map[string]interface{}{
Expand All @@ -215,7 +219,7 @@ var gadgets = []Gadget{
},
{
Name: topFile,
Image: "ghcr.io/inspektor-gadget/gadget/top_file:latest",
Image: "ghcr.io/inspektor-gadget/gadget/top_file",
Description: "Shows top files by read/write operations",
Params: map[string]interface{}{
"max_entries": map[string]interface{}{
Expand All @@ -240,7 +244,7 @@ var gadgets = []Gadget{
},
{
Name: topTCP,
Image: "ghcr.io/inspektor-gadget/gadget/top_tcp:latest",
Image: "ghcr.io/inspektor-gadget/gadget/top_tcp",
Description: "Shows top TCP connections by traffic volume",
Params: map[string]interface{}{
"max_entries": map[string]interface{}{
Expand Down
8 changes: 7 additions & 1 deletion internal/components/inspektorgadget/gadgets_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package inspektorgadget

import "testing"
import (
"strings"
"testing"
)

func TestGadgets(t *testing.T) {
for _, gadget := range gadgets {
Expand All @@ -10,6 +13,9 @@ func TestGadgets(t *testing.T) {
if gadget.Image == "" {
t.Errorf("Gadget image is empty for %s", gadget.Name)
}
if gadget.Image != "" && strings.Contains(gadget.Image, ":") {
t.Errorf("Gadget image %s should not contain a version tag", gadget.Image)
}
if gadget.Description == "" {
t.Errorf("Gadget description is empty for %s", gadget.Name)
}
Expand Down
Loading
Loading