Skip to content

Commit 78b8984

Browse files
committed
Add linekrd agent
Signed-off-by: Ivan Porta <porta.ivan@outlook.com>
1 parent 35ac04a commit 78b8984

3,024 files changed

Lines changed: 5697 additions & 33 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DEVELOPMENT.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ These tools enhance functionality but aren't required for basic development:
1818
- `kubectl` - Kubernetes CLI for k8s tools
1919
- `helm` - Helm package manager for helm tools
2020
- `istioctl` - Istio service mesh CLI for istio tools
21+
- `linkerd` - Linkerd service mesh CLI for linkerd tools
2122
- `cilium` - Cilium CLI for cilium tools
2223

2324
## Project Structure
@@ -32,6 +33,7 @@ These tools enhance functionality but aren't required for basic development:
3233
│ ├── k8s/ # Kubernetes tools
3334
│ ├── helm/ # Helm package manager tools
3435
│ ├── istio/ # Istio service mesh tools
36+
│ ├── linkerd/ # Linkerd service mesh tools
3537
│ ├── cilium/ # Cilium CNI tools
3638
│ ├── argo/ # Argo Rollouts tools
3739
│ ├── prometheus/ # Prometheus monitoring tools
@@ -422,4 +424,4 @@ git commit -m "docs(readme): update installation instructions"
422424
- Check existing issues in the repository
423425
- Review the CLAUDE.md file for project-specific guidance
424426
- Consult Go documentation and best practices
425-
- Ask questions in code reviews or team discussions
427+
- Ask questions in code reviews or team discussions

Dockerfile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ RUN curl -Lo cilium.tar.gz https://github.com/cilium/cilium-cli/releases/downloa
4646
&& rm -rf cilium.tar.gz \
4747
&& /downloads/cilium version
4848

49+
# Install Linkerd CLI
50+
ARG TOOLS_LINKERD_VERSION
51+
RUN curl -Lo /downloads/linkerd https://github.com/linkerd/linkerd2/releases/download/${TOOLS_LINKERD_VERSION}/linkerd2-cli-${TOOLS_LINKERD_VERSION}-linux-${TARGETARCH} \
52+
&& chmod +x /downloads/linkerd \
53+
&& /downloads/linkerd version --client
54+
4955
### STAGE 2: build-tools MCP
5056
ARG BASE_IMAGE_REGISTRY=cgr.dev
5157
ARG BUILDARCH=amd64
@@ -96,6 +102,7 @@ COPY --from=tools --chown=65532:65532 /downloads/istioctl /bin/isti
96102
COPY --from=tools --chown=65532:65532 /downloads/helm /bin/helm
97103
COPY --from=tools --chown=65532:65532 /downloads/kubectl-argo-rollouts /bin/kubectl-argo-rollouts
98104
COPY --from=tools --chown=65532:65532 /downloads/cilium /bin/cilium
105+
COPY --from=tools --chown=65532:65532 /downloads/linkerd /bin/linkerd
99106
# Copy the tool-server binary
100107
COPY --from=builder --chown=65532:65532 /workspace/tool-server /tool-server
101108

@@ -106,4 +113,4 @@ LABEL org.opencontainers.image.description="Kagent MCP tools server"
106113
LABEL org.opencontainers.image.authors="Kagent Creators 🤖"
107114
LABEL org.opencontainers.image.version="$VERSION"
108115

109-
ENTRYPOINT ["/tool-server"]
116+
ENTRYPOINT ["/tool-server"]

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ TOOLS_ARGO_ROLLOUTS_VERSION ?= 1.8.3
141141
TOOLS_KUBECTL_VERSION ?= 1.34.1
142142
TOOLS_HELM_VERSION ?= 3.19.0
143143
TOOLS_CILIUM_VERSION ?= 0.18.7
144+
TOOLS_LINKERD_VERSION ?= edge-25.11.3
144145

145146
# build args
146147
TOOLS_IMAGE_BUILD_ARGS = --build-arg VERSION=$(VERSION)
@@ -151,6 +152,7 @@ TOOLS_IMAGE_BUILD_ARGS += --build-arg TOOLS_ARGO_ROLLOUTS_VERSION=$(TOOLS_ARGO_R
151152
TOOLS_IMAGE_BUILD_ARGS += --build-arg TOOLS_KUBECTL_VERSION=$(TOOLS_KUBECTL_VERSION)
152153
TOOLS_IMAGE_BUILD_ARGS += --build-arg TOOLS_HELM_VERSION=$(TOOLS_HELM_VERSION)
153154
TOOLS_IMAGE_BUILD_ARGS += --build-arg TOOLS_CILIUM_VERSION=$(TOOLS_CILIUM_VERSION)
155+
TOOLS_IMAGE_BUILD_ARGS += --build-arg TOOLS_LINKERD_VERSION=$(TOOLS_LINKERD_VERSION)
154156

155157
.PHONY: buildx-create
156158
buildx-create:
@@ -265,4 +267,4 @@ GOBIN=$(LOCALBIN) go install $${package} ;\
265267
mv $(1) $(1)-$(3) ;\
266268
} ;\
267269
ln -sf $(1)-$(3) $(1)
268-
endef
270+
endef

README.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,33 @@ Provides Istio service mesh management:
104104
- **istio_waypoint_status**: Get waypoint proxy status
105105
- **istio_ztunnel_config**: Get ztunnel configuration
106106

107-
### 4. Argo Rollouts Tools (`argo.go`)
107+
### 4. Linkerd Tools (`linkerd.go`)
108+
Provides Linkerd service mesh management:
109+
110+
- **linkerd_check**: Run pre-flight or proxy checks
111+
- **linkerd_install**: Install Linkerd control plane manifests
112+
- **linkerd_install_cni**: Install the Linkerd CNI components
113+
- **linkerd_upgrade**: Upgrade Linkerd installation components
114+
- **linkerd_uninstall**: Remove Linkerd components
115+
- **linkerd_version**: Show Linkerd client and server versions
116+
- **linkerd_authz**: Inspect authorizations for a workload
117+
- **linkerd_stat**: Retrieve Linkerd metrics for resources
118+
- **linkerd_top**: Inspect live traffic for workloads
119+
- **linkerd_edges**: Display allowed edges between resources
120+
- **linkerd_routes**: Inspect HTTP routes for a resource
121+
- **linkerd_diagnostics_proxy_metrics**: Collect raw proxy metrics
122+
- **linkerd_diagnostics_controller_metrics**: Fetch controller metrics
123+
- **linkerd_diagnostics_endpoints**: Inspect service discovery endpoints
124+
- **linkerd_diagnostics_policy**: Inspect policy state for an authority
125+
- **linkerd_diagnostics_profile**: Inspect service discovery profile data
126+
- **linkerd_viz_install**: Install the Linkerd viz extension
127+
- **linkerd_viz_uninstall**: Remove the Linkerd viz extension
128+
- **linkerd_viz_top**: Inspect live traffic using viz extension
129+
- **linkerd_viz_stat**: Retrieve viz metrics for resources
130+
- **linkerd_fips_audit**: Audit Linkerd proxies for FIPS compliance
131+
- **linkerd_policy_generate**: Generate policy manifests for workloads
132+
133+
### 5. Argo Rollouts Tools (`argo.go`)
108134
Provides Argo Rollouts progressive delivery functionality:
109135

110136
- **verify_argo_rollouts_controller_install**: Verify controller installation
@@ -115,7 +141,7 @@ Provides Argo Rollouts progressive delivery functionality:
115141
- **verify_gateway_plugin**: Verify Gateway API plugin
116142
- **check_plugin_logs**: Check plugin installation logs
117143

118-
### 5. Cilium Tools (`cilium.go`)
144+
### 6. Cilium Tools (`cilium.go`)
119145
Provides Cilium CNI and networking functionality:
120146

121147
- **cilium_status_and_version**: Get Cilium status and version
@@ -131,36 +157,36 @@ Provides Cilium CNI and networking functionality:
131157
- **toggle_hubble**: Enable/disable Hubble
132158
- **toggle_cluster_mesh**: Enable/disable cluster mesh
133159

134-
### 6. Prometheus Tools (`prometheus.go`)
160+
### 7. Prometheus Tools (`prometheus.go`)
135161
Provides Prometheus monitoring and alerting functionality:
136162

137163
- **prometheus_query**: Execute PromQL queries
138164
- **prometheus_range_query**: Execute PromQL range queries
139165
- **prometheus_labels**: Get available labels
140166
- **prometheus_targets**: Get scraping targets and their status
141167

142-
### 7. Grafana Tools (`grafana.go`)
168+
### 8. Grafana Tools (`grafana.go`)
143169
Provides Grafana dashboard and alerting management:
144170

145171
- **grafana_org_management**: Manage Grafana organizations
146172
- **grafana_dashboard_management**: Manage dashboards
147173
- **grafana_alert_management**: Manage alerts and alert rules
148174
- **grafana_datasource_management**: Manage data sources
149175

150-
### 8. DateTime Tools (`datetime.go`)
176+
### 9. DateTime Tools (`datetime.go`)
151177
Provides time and date utilities:
152178

153179
- **current_date_time**: Get current date and time in ISO 8601 format
154180
- **format_time**: Format timestamps with optional timezone
155181
- **parse_time**: Parse time strings into RFC3339 format
156182

157-
### 9. Documentation Tools (`docs.go`)
183+
### 10. Documentation Tools (`docs.go`)
158184
Provides documentation query functionality:
159185

160186
- **query_documentation**: Query documentation for supported products (simplified implementation)
161187
- **list_supported_products**: List supported products for documentation queries
162188

163-
### 10. Common Tools (`common.go`)
189+
### 11. Common Tools (`common.go`)
164190
Provides general utility functions:
165191

166192
- **shell**: Execute shell commands
@@ -174,6 +200,7 @@ Provides general utility functions:
174200
- `kubectl` (for Kubernetes tools)
175201
- `helm` (for Helm tools)
176202
- `istioctl` (for Istio tools)
203+
- `linkerd` (for Linkerd tools)
177204
- `cilium` (for Cilium tools)
178205

179206
### Building

cmd/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/kagent-dev/tools/pkg/helm"
2323
"github.com/kagent-dev/tools/pkg/istio"
2424
"github.com/kagent-dev/tools/pkg/k8s"
25+
"github.com/kagent-dev/tools/pkg/linkerd"
2526
"github.com/kagent-dev/tools/pkg/prometheus"
2627
"github.com/kagent-dev/tools/pkg/utils"
2728
"github.com/spf13/cobra"
@@ -291,6 +292,7 @@ func registerMCP(mcp *server.MCPServer, enabledToolProviders []string, kubeconfi
291292
"cilium": cilium.RegisterTools,
292293
"helm": helm.RegisterTools,
293294
"istio": istio.RegisterTools,
295+
"linkerd": linkerd.RegisterTools,
294296
"k8s": func(s *server.MCPServer) { k8s.RegisterTools(s, nil, kubeconfig) },
295297
"prometheus": prometheus.RegisterTools,
296298
"utils": utils.RegisterTools,
3.85 KB
Binary file not shown.

internal/cmd/cmd.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"context"
56
"os/exec"
67
"time"
@@ -50,6 +51,48 @@ func (e *DefaultShellExecutor) Exec(ctx context.Context, command string, args ..
5051
return output, err
5152
}
5253

54+
// ExecWithStreams executes a command and captures stdout/stderr separately
55+
func (e *DefaultShellExecutor) ExecWithStreams(ctx context.Context, command string, args ...string) ([]byte, []byte, error) {
56+
log := logger.WithContext(ctx)
57+
startTime := time.Now()
58+
59+
log.Info("executing command",
60+
"command", command,
61+
"args", args,
62+
)
63+
64+
cmd := exec.CommandContext(ctx, command, args...)
65+
var stdout bytes.Buffer
66+
var stderr bytes.Buffer
67+
cmd.Stdout = &stdout
68+
cmd.Stderr = &stderr
69+
70+
err := cmd.Run()
71+
duration := time.Since(startTime)
72+
73+
stdoutStr := stdout.String()
74+
stderrStr := stderr.String()
75+
76+
if err != nil {
77+
log.Error("command execution failed",
78+
"command", command,
79+
"args", args,
80+
"error", err,
81+
"stdout", stdoutStr,
82+
"stderr", stderrStr,
83+
"duration", duration.Seconds(),
84+
)
85+
} else {
86+
log.Info("command execution successful",
87+
"command", command,
88+
"args", args,
89+
"duration", duration.Seconds(),
90+
)
91+
}
92+
93+
return stdout.Bytes(), stderr.Bytes(), err
94+
}
95+
5396
// Context key for shell executor injection
5497
type contextKey string
5598

internal/cmd/mock.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,7 @@ func (m *MockShellExecutor) AddPartialMatcherString(command string, args []strin
7070
}{command, args, output, err})
7171
}
7272

73-
// Exec records the call and returns a mocked output or error
74-
func (m *MockShellExecutor) Exec(ctx context.Context, command string, args ...string) ([]byte, error) {
75-
m.mu.Lock()
76-
defer m.mu.Unlock()
77-
73+
func (m *MockShellExecutor) execute(command string, args []string) ([]byte, error) {
7874
m.callLog = append(m.callLog, MockCall{Command: command, Args: args})
7975

8076
// Check for exact match first
@@ -95,6 +91,22 @@ func (m *MockShellExecutor) Exec(ctx context.Context, command string, args ...st
9591
return nil, fmt.Errorf("no mock found for command: %s %v", command, args)
9692
}
9793

94+
// Exec records the call and returns a mocked output or error
95+
func (m *MockShellExecutor) Exec(ctx context.Context, command string, args ...string) ([]byte, error) {
96+
m.mu.Lock()
97+
defer m.mu.Unlock()
98+
return m.execute(command, args)
99+
}
100+
101+
// ExecWithStreams records the call and returns separated stdout/stderr
102+
func (m *MockShellExecutor) ExecWithStreams(ctx context.Context, command string, args ...string) ([]byte, []byte, error) {
103+
m.mu.Lock()
104+
defer m.mu.Unlock()
105+
106+
output, err := m.execute(command, args)
107+
return output, nil, err
108+
}
109+
98110
// GetCallLog returns the history of commands executed
99111
func (m *MockShellExecutor) GetCallLog() []MockCall {
100112
m.mu.Lock()

internal/commands/builder.go

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type CommandBuilder struct {
3838
force bool
3939
wait bool
4040
validate bool
41+
stdoutOnly bool
4142
cached bool
4243
cacheTTL time.Duration
4344
cacheKey string
@@ -200,6 +201,12 @@ func (cb *CommandBuilder) WithValidation(validate bool) *CommandBuilder {
200201
return cb
201202
}
202203

204+
// WithStdoutOnly forces the command to only capture stdout on success
205+
func (cb *CommandBuilder) WithStdoutOnly(stdoutOnly bool) *CommandBuilder {
206+
cb.stdoutOnly = stdoutOnly
207+
return cb
208+
}
209+
203210
// WithCache enables caching of the command result
204211
func (cb *CommandBuilder) WithCache(cached bool) *CommandBuilder {
205212
cb.cached = cached
@@ -409,28 +416,76 @@ func (cb *CommandBuilder) executeWithCache(ctx context.Context, command string,
409416
// executeCommand executes the actual command
410417
func (cb *CommandBuilder) executeCommand(ctx context.Context, command string, args []string) (string, error) {
411418
executor := cmd.GetShellExecutor(ctx)
419+
420+
if cb.stdoutOnly {
421+
if streamExec, ok := executor.(interface {
422+
ExecWithStreams(ctx context.Context, command string, args ...string) ([]byte, []byte, error)
423+
}); ok {
424+
stdout, stderr, err := streamExec.ExecWithStreams(ctx, command, args...)
425+
if err != nil {
426+
combined := combineCommandOutput(stdout, stderr)
427+
return cb.handleCommandError(command, args, combined, err)
428+
}
429+
430+
if len(stderr) > 0 {
431+
logger.WithContext(ctx).Warn("command produced stderr output",
432+
"command", command,
433+
"args", args,
434+
"stderr", string(stderr),
435+
)
436+
}
437+
438+
return string(stdout), nil
439+
}
440+
441+
logger.WithContext(ctx).Warn("stdout-only requested but shell executor does not support split output; falling back to combined output",
442+
"command", command,
443+
"args", args,
444+
)
445+
}
446+
412447
output, err := executor.Exec(ctx, command, args...)
413448
if err != nil {
414-
// Create appropriate error based on command type
415-
var toolError *errors.ToolError
416-
switch command {
417-
case "kubectl":
418-
toolError = errors.NewKubernetesError(strings.Join(args, " "), err)
419-
case "helm":
420-
toolError = errors.NewHelmError(strings.Join(args, " "), err)
421-
case "istioctl":
422-
toolError = errors.NewIstioError(strings.Join(args, " "), err)
423-
case "cilium":
424-
toolError = errors.NewCiliumError(strings.Join(args, " "), err)
425-
default:
426-
toolError = errors.NewCommandError(command, err)
427-
}
428-
return string(output), toolError
449+
return cb.handleCommandError(command, args, string(output), err)
429450
}
430451

431452
return string(output), nil
432453
}
433454

455+
func (cb *CommandBuilder) handleCommandError(command string, args []string, output string, err error) (string, error) {
456+
var toolError *errors.ToolError
457+
switch command {
458+
case "kubectl":
459+
toolError = errors.NewKubernetesError(strings.Join(args, " "), err)
460+
case "helm":
461+
toolError = errors.NewHelmError(strings.Join(args, " "), err)
462+
case "istioctl":
463+
toolError = errors.NewIstioError(strings.Join(args, " "), err)
464+
case "cilium":
465+
toolError = errors.NewCiliumError(strings.Join(args, " "), err)
466+
default:
467+
toolError = errors.NewCommandError(command, err)
468+
}
469+
return output, toolError
470+
}
471+
472+
func combineCommandOutput(stdout, stderr []byte) string {
473+
switch {
474+
case len(stdout) == 0:
475+
return string(stderr)
476+
case len(stderr) == 0:
477+
return string(stdout)
478+
default:
479+
combined := make([]byte, len(stdout), len(stdout)+1+len(stderr))
480+
copy(combined, stdout)
481+
if combined[len(combined)-1] != '\n' {
482+
combined = append(combined, '\n')
483+
}
484+
combined = append(combined, stderr...)
485+
return string(combined)
486+
}
487+
}
488+
434489
// Common command patterns as helper functions
435490

436491
// GetPods creates a command to get pods

0 commit comments

Comments
 (0)