Skip to main content
Glama
gadgetmanager.go10.9 kB
// Copyright 2025 The Inspektor Gadget authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package gadgetmanager import ( "context" "crypto/rand" "encoding/hex" "fmt" "strings" "sync" "time" "github.com/gopacket/gopacket" "github.com/gopacket/gopacket/layers" "github.com/inspektor-gadget/inspektor-gadget/pkg/environment" "k8s.io/cli-runtime/pkg/genericclioptions" "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource" igjson "github.com/inspektor-gadget/inspektor-gadget/pkg/datasource/formatters/json" gadgetcontext "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-context" "github.com/inspektor-gadget/inspektor-gadget/pkg/gadget-service/api" "github.com/inspektor-gadget/inspektor-gadget/pkg/operators" "github.com/inspektor-gadget/inspektor-gadget/pkg/operators/simple" grpcruntime "github.com/inspektor-gadget/inspektor-gadget/pkg/runtime/grpc" ) const maxResultLen = 64 * 1024 // 64kb // GadgetManager is an interface for managing gadgets. type GadgetManager interface { // Run starts a gadget with the given image and parameters, returning the output as a string. Run(image string, params map[string]string, timeout time.Duration) (string, error) // RunDetached starts a gadget with the given image and parameters in the background, returning its ID. RunDetached(image string, params map[string]string) (string, error) // GetResults returns the stored result buffer from a gadget GetResults(id string) (string, error) // Stop stops a gadget Stop(id string) error // GetInfo retrieves information about a gadget image via runtime. GetInfo(ctx context.Context, image string) (*api.GadgetInfo, error) // GetVersion retrieves the version of Inspektor Gadget installed in the cluster GetVersion() (string, error) // ListGadgets lists all running gadget instances ListGadgets(ctx context.Context) ([]*GadgetInstance, error) } // GadgetInstance represents a running gadget instance type GadgetInstance struct { ID string `json:"id"` GadgetImage string `json:"gadgetImage"` Params string `json:"params"` CreatedBy string `json:"createdBy,omitempty"` StartedAt string `json:"startedAt,omitempty"` } type gadgetManager struct { k8sConfig *genericclioptions.ConfigFlags formatterMu sync.Mutex env string remoteAddr string } // NewGadgetManager creates a new GadgetManager instance. func NewGadgetManager(env string, linuxRemoteAddress string, k8sConfig *genericclioptions.ConfigFlags) (GadgetManager, error) { if env != "kubernetes" && env != "linux" { return nil, fmt.Errorf("unsupported gadget manager environment: %s", env) } if env == "linux" && linuxRemoteAddress == "" { return nil, fmt.Errorf("linuxRemoteAddress must be set when environment is linux") } return &gadgetManager{ k8sConfig: k8sConfig, env: env, remoteAddr: linuxRemoteAddress, }, nil } func (g *gadgetManager) Run(image string, params map[string]string, timeout time.Duration) (string, error) { var res strings.Builder gadgetCtx := gadgetcontext.New( context.Background(), image, gadgetcontext.WithDataOperators( g.outputOperator(func(buf []byte) { res.Write(buf) res.WriteByte('\n') }), ), gadgetcontext.WithTimeout(timeout), ) runtime, err := g.getRuntime() if err != nil { return "", fmt.Errorf("getting runtime: %w", err) } if err = runtime.RunGadget(gadgetCtx, runtime.ParamDescs().ToParams(), params); err != nil { return "", fmt.Errorf("running gadget: %w", err) } return truncateResults(res.String(), false), nil } func (g *gadgetManager) RunDetached(image string, params map[string]string) (string, error) { gadgetCtx := gadgetcontext.New( context.Background(), image, ) runtime, err := g.getRuntime() if err != nil { return "", fmt.Errorf("getting runtime: %w", err) } p := runtime.ParamDescs().ToParams() newID := make([]byte, 16) rand.Read(newID) idString := hex.EncodeToString(newID) p.Set(grpcruntime.ParamTags, "createdBy=ig-mcp-server") p.Set(grpcruntime.ParamID, idString) p.Set(grpcruntime.ParamDetach, "true") if err = runtime.RunGadget(gadgetCtx, p, params); err != nil { return "", fmt.Errorf("running gadget: %w", err) } return idString, nil } func (g *gadgetManager) Stop(id string) error { runtime, err := g.getRuntime() if err != nil { return fmt.Errorf("getting runtime: %w", err) } if err = runtime.RemoveGadgetInstance(context.Background(), runtime.ParamDescs().ToParams(), id); err != nil { return fmt.Errorf("stopping to gadget: %w", err) } return nil } func (g *gadgetManager) GetResults(id string) (string, error) { var res strings.Builder to, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() gadgetCtx := gadgetcontext.New( to, id, gadgetcontext.WithDataOperators( g.outputOperator(func(buf []byte) { res.Write(buf) res.WriteByte('\n') }), ), gadgetcontext.WithID(id), gadgetcontext.WithUseInstance(true), gadgetcontext.WithTimeout(time.Second), ) runtime, err := g.getRuntime() if err != nil { return "", fmt.Errorf("getting runtime: %w", err) } if err = runtime.RunGadget(gadgetCtx, runtime.ParamDescs().ToParams(), map[string]string{}); err != nil { return "", fmt.Errorf("attaching to gadget: %w", err) } return truncateResults(res.String(), true), nil } func (g *gadgetManager) GetInfo(ctx context.Context, image string) (*api.GadgetInfo, error) { gadgetCtx := gadgetcontext.New( ctx, image, ) runtime, err := g.getRuntime() if err != nil { return nil, fmt.Errorf("getting runtime: %w", err) } info, err := runtime.GetGadgetInfo(gadgetCtx, runtime.ParamDescs().ToParams(), nil) if err != nil { return nil, fmt.Errorf("get gadget info: %w", err) } return info, nil } func (g *gadgetManager) ListGadgets(ctx context.Context) ([]*GadgetInstance, error) { rt, err := g.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) } var gadgetInstances []*GadgetInstance for _, instance := range instances { inst := gadgetInstanceFromAPI(instance) if inst != nil { gadgetInstances = append(gadgetInstances, inst) } } return gadgetInstances, nil } func (g *gadgetManager) GetVersion() (string, error) { rt, err := g.getRuntime() if err != nil { return "", fmt.Errorf("getting runtime: %w", err) } info, err := rt.GetInfo() if err != nil { return "", fmt.Errorf("getting info: %w", err) } return info.ServerVersion, nil } func truncateResults(results string, latest bool) string { if len(results) <= maxResultLen { return fmt.Sprintf("\n<results>%s</results>\n", results) } var truncated string if latest { truncated = results[len(results)-maxResultLen:] } else { truncated = results[:maxResultLen] + "…" } return fmt.Sprintf("\n<isTruncated>true</isTruncated>\n<results>%s</results>\n", truncated) } func (g *gadgetManager) getRuntime() (*grpcruntime.Runtime, error) { if g.env == "kubernetes" { environment.Environment = environment.Kubernetes rt := grpcruntime.New(grpcruntime.WithConnectUsingK8SProxy) if err := rt.Init(rt.GlobalParamDescs().ToParams()); err != nil { return nil, fmt.Errorf("initializing gadget runtime: %w", err) } restConfig, err := g.k8sConfig.ToRESTConfig() if err != nil { return nil, fmt.Errorf("creating REST config: %w", err) } rt.SetRestConfig(restConfig) return rt, nil } if g.env == "linux" { environment.Environment = environment.Local rt := grpcruntime.New() gp := rt.GlobalParamDescs().ToParams() err := gp.Set(grpcruntime.ParamRemoteAddress, g.remoteAddr) if err != nil { return nil, fmt.Errorf("setting remote address: %w", err) } if err = rt.Init(gp); err != nil { return nil, fmt.Errorf("initializing gadget runtime: %w", err) } return rt, nil } return nil, fmt.Errorf("unsupported gadget manager environment: %s", g.env) } func (g *gadgetManager) outputOperator(cb func(buf []byte)) operators.DataOperator { const opPriority = 50000 return simple.New("outputOperator", simple.OnInit(func(gadgetCtx operators.GadgetContext) error { for _, d := range gadgetCtx.GetDataSources() { // skip data sources that have the annotation "cli.default-output-mode" if m, ok := d.Annotations()["cli.default-output-mode"]; ok && m == "none" { continue } // handle adding a raw string field for certain content types restField := d.Annotations()["ebpf.rest.name"] var restAcc datasource.FieldAccessor var restStrAcc datasource.FieldAccessor var err error if restField != "" { restAcc = d.GetField(restField) ct, ok := restAcc.Annotations()["content-type"] if ok && ct == "application/x-raw-packet" { restStrAcc, err = d.AddField(restField+"_string", api.Kind_String) if err != nil { return fmt.Errorf("adding raw string field accessor: %w", err) } } } jsonFormatter, _ := igjson.New(d, igjson.WithShowAll(true), ) d.Subscribe(func(source datasource.DataSource, data datasource.Data) error { g.formatterMu.Lock() defer g.formatterMu.Unlock() if restAcc != nil && restStrAcc != nil { pktStr := gopacket.NewPacket(restAcc.Get(data), layers.LinkTypeEthernet, gopacket.Default).String() err = restStrAcc.Set(data, []byte(pktStr)) if err != nil { return fmt.Errorf("setting raw string field: %w", err) } } jsonData := jsonFormatter.Marshal(data) cb(jsonData) return nil }, opPriority) } return nil }), ) } func gadgetInstanceFromAPI(instance *api.GadgetInstance) *GadgetInstance { if instance == nil { return nil } var createdBy string for _, tag := range instance.Tags { if strings.HasPrefix(tag, "createdBy=") { createdBy = strings.TrimPrefix(tag, "createdBy=") break } } var params []string for k, v := range instance.GadgetConfig.ParamValues { if v == "" { continue } params = append(params, fmt.Sprintf("%s=%q", k, v)) } return &GadgetInstance{ ID: instance.Id, Params: strings.Join(params, ","), GadgetImage: instance.GadgetConfig.ImageName, CreatedBy: createdBy, StartedAt: time.Unix(instance.TimeCreated, 0).Format(time.RFC3339), } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/inspektor-gadget/ig-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server